diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d6e688a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,41 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Reproducible code** +If applicable, add a minimum reproducible code snippet. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml new file mode 100644 index 0000000..5ff0000 --- /dev/null +++ b/.github/actions/ci/action.yml @@ -0,0 +1,61 @@ +name: ci +description: Executes Dart specific CI steps. + +inputs: + type: + description: The type of CI to run. + required: true + relay-endpoint: + description: 'The endpoint of the relay e.g. relay.walletconnect.com' + required: false + default: 'wss://relay.walletconnect.com' + project-id: + description: 'Reown project id' + required: true + +runs: + using: composite + steps: + # Setup Dependencies + - uses: ./.github/actions/dependencies + + # Run Core Unit and Integration Tests + - name: Run Core tests + if: inputs.type == 'integration-tests' + shell: bash + working-directory: packages/reown_core + env: + RELAY_ENDPOINT: ${{ inputs.relay-endpoint }} + PROJECT_ID: ${{ inputs.project-id }} + run: flutter test --dart-define=RELAY_ENDPOINT=$RELAY_ENDPOINT --dart-define=PROJECT_ID=$PROJECT_ID + + # Run Sign Unit and Integration Tests + - name: Run Sign tests + if: inputs.type == 'integration-tests' + shell: bash + working-directory: packages/reown_sign + env: + RELAY_ENDPOINT: ${{ inputs.relay-endpoint }} + PROJECT_ID: ${{ inputs.project-id }} + run: flutter test --dart-define=RELAY_ENDPOINT=$RELAY_ENDPOINT --dart-define=PROJECT_ID=$PROJECT_ID + + # Run AppKit Unit and Integration Tests + - name: Run AppKit tests + if: inputs.type == 'integration-tests' + shell: bash + working-directory: packages/reown_appkit + env: + RELAY_ENDPOINT: ${{ inputs.relay-endpoint }} + PROJECT_ID: ${{ inputs.project-id }} + run: flutter test --dart-define=RELAY_ENDPOINT=$RELAY_ENDPOINT --dart-define=PROJECT_ID=$PROJECT_ID + + # Run WalletKit Unit and Integration Tests + - name: Run AppKit tests + if: inputs.type == 'integration-tests' + shell: bash + working-directory: packages/reown_walletkit + env: + RELAY_ENDPOINT: ${{ inputs.relay-endpoint }} + PROJECT_ID: ${{ inputs.project-id }} + run: flutter test --dart-define=RELAY_ENDPOINT=$RELAY_ENDPOINT --dart-define=PROJECT_ID=$PROJECT_ID + \ No newline at end of file diff --git a/.github/actions/dependencies/action.yml b/.github/actions/dependencies/action.yml new file mode 100644 index 0000000..8b4bf4a --- /dev/null +++ b/.github/actions/dependencies/action.yml @@ -0,0 +1,43 @@ +name: ci +description: Installs dependencies + +runs: + using: composite + steps: + # Install Flutter SDK + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.19.5' + + # Get package dependencies and generate files + - name: Get package dependencies and generate files + shell: bash + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + + # Get example app dependencies and generate files + - name: Get example app dependencies and generate files + shell: bash + working-directory: example/wallet + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + + # Get example app dependencies and generate files + - name: Get example app dependencies and generate files + shell: bash + working-directory: example/dapp + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + + - name: Verify formatting and analyze project source + shell: bash + run: dart format --output=none --set-exit-if-changed . + + # - name: Analyze project source + # shell: bash + # run: flutter analyze + \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..73f3d26 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,24 @@ +# Description + + + +Resolves # (issue) + +## How Has This Been Tested? + + + + + +## Due Dilligence + +* [ ] Breaking change +* [ ] Requires a documentation update \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8a6312b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: ci + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + + workflow_dispatch: + inputs: + relay-endpoint: + description: 'The endpoint of the relay e.g. relay.walletconnect.com' + required: false + default: 'wss://relay.walletconnect.com' + project-id: + description: 'Reown project id' + required: true + + +concurrency: + # Support push/pr as event types with different behaviors each: + # 1. push: queue up builds by branch + # 2. pr: only allow one run per PR + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref_name }} + # If there is already a workflow running for the same pull request, cancel it + # For non-PR triggers queue up builds + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + test-type: [integration-tests] + + steps: + - uses: actions/checkout@v3 + + - name: Install and set Flutter version + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.19.5' + + - uses: ./.github/actions/ci + with: + type: ${{ matrix.test-type }} + project-id: ${{ secrets.PROJECT_ID }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..27f6bc3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,47 @@ +name: Publish to pub.dev + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' + +jobs: + publish: + name: Publish to pub.dev + permissions: + id-token: write + runs-on: ubuntu-latest + steps: + # Checkout the repo + - uses: actions/checkout@v3 + + # Setup Dart SDK + - uses: dart-lang/setup-dart@v1 + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + # Publish --dry-run + - name: Check Publish Warnings + shell: bash + run: flutter pub publish --dry-run + + # Publish + - name: Publish Package + shell: bash + run: flutter pub publish -f + + # Notify + - name: Notify Channel + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: 'INCOMING_WEBHOOK' + with: + payload: |- + { + "text":"🚀 reown_flutter\'s *${{ github.ref_name }}* SDK was just published at https://pub.dev/packages/${{ github.ref_name }}" + } + +# Launch locally +# act -j publish --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/publish.yml diff --git a/.github/workflows/release_dapp_android.yml b/.github/workflows/release_dapp_android.yml new file mode 100644 index 0000000..a14d7fc --- /dev/null +++ b/.github/workflows/release_dapp_android.yml @@ -0,0 +1,72 @@ +name: Android Dapp (production) deploy + +on: + workflow_dispatch: + pull_request: + types: + - closed + +jobs: + build: + if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master') || github.event_name == 'workflow_dispatch' + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Install Java 17 + - name: Install Java 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + architecture: x86_64 + cache: 'gradle' + + # Cache Gradle + - name: Cache Gradle + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + - name: Add Secrets file + working-directory: packages/reown_appkit/example/base/android + run: touch secrets.properties && echo "${{ secrets.SECRETS_FILE }}" >> secrets.properties + + - name: Add Keystore file + working-directory: packages/reown_appkit/example/base/android + run: echo "${{ secrets.KEYSTORE }}" | base64 --decode >> app/debug.keystore + + # Fastlane + - name: Fastlane + working-directory: packages/reown_appkit/example/base/android + env: + FLAVOR: "production" + PROJECT_ID: ${{ secrets.DAPP_PROJECT_ID }} + SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + FIREBASE_DAPP_ID: ${{ secrets.FIREBASE_DAPP_ID }} + DOWNLOAD_URL: ${{ secrets.DOWNLOAD_URL_DAPP }} + run: | + PUBSPEC_FILE=../../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + fastlane add_plugin firebase_app_distribution + fastlane release_firebase project_id:$PROJECT_ID app_version:$VERSION_NUMBER + +# Launch locally +# Remove build: if: +# act -j build --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/release_dapp_android.yml diff --git a/.github/workflows/release_dapp_android_internal.yml b/.github/workflows/release_dapp_android_internal.yml new file mode 100644 index 0000000..5db693c --- /dev/null +++ b/.github/workflows/release_dapp_android_internal.yml @@ -0,0 +1,75 @@ +name: Android Dapp (internal) deploy + +on: + workflow_dispatch: + pull_request: + types: + - closed + +jobs: + build: + if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop') || github.event_name == 'workflow_dispatch' + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Install Java 17 + - name: Install Java 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + architecture: x86_64 + cache: 'gradle' + + # Cache Gradle + - name: Cache Gradle + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + - name: Add Secrets file + # working-directory: example/dapp/android + working-directory: packages/reown_appkit/example/base/android + run: touch secrets.properties && echo "${{ secrets.SECRETS_FILE }}" >> secrets.properties + + - name: Add Keystore file + # working-directory: example/dapp/android + working-directory: packages/reown_appkit/example/base/android + run: echo "${{ secrets.KEYSTORE }}" | base64 --decode >> app/debug.keystore + + # Fastlane + - name: Fastlane + # working-directory: example/dapp/android + working-directory: packages/reown_appkit/example/base/android + env: + FLAVOR: "internal" + PROJECT_ID: ${{ secrets.DAPP_PROJECT_ID }} + SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + FIREBASE_DAPP_ID: ${{ secrets.FIREBASE_DAPP_ID_INTERNAL }} + DOWNLOAD_URL: ${{ secrets.DOWNLOAD_URL_DAPP_INTERNAL }} + run: | + PUBSPEC_FILE=../../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + fastlane add_plugin firebase_app_distribution + fastlane release_firebase project_id:$PROJECT_ID app_version:$VERSION_NUMBER + +# Launch locally +# Remove build: if: +# act -j build --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/release_dapp_android_internal.yml diff --git a/.github/workflows/release_dapp_ios.yml b/.github/workflows/release_dapp_ios.yml new file mode 100644 index 0000000..a746199 --- /dev/null +++ b/.github/workflows/release_dapp_ios.yml @@ -0,0 +1,76 @@ +name: iOS Dapp (production) deploy + +on: + workflow_dispatch: + pull_request: + types: + - closed + +jobs: + build: + if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master') || github.event_name == 'workflow_dispatch' + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Cache + - name: Cache + uses: actions/cache@v3 + with: + path: | + .build + SourcePackagesCache + DerivedDataCache + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + # Build App + - name: Build App + # working-directory: example/dapp + working-directory: packages/reown_appkit/example/base + env: + PROJECT_ID: ${{ secrets.DAPP_PROJECT_ID }} + run: | + PUBSPEC_FILE=../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + flutter build ios --flavor production --build-name $VERSION_NUMBER --dart-define="PROJECT_ID=$PROJECT_ID" --config-only --release + + # Fastlane + - name: Fastlane + # working-directory: example/dapp/ios + working-directory: packages/reown_appkit/example/base/ios + env: + BUNDLE_ID: "com.walletconnect.flutterdapp" + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_DAPP_ID: ${{ secrets.APPLE_DAPP_ID }} + PROJECT_ID: ${{ secrets.DAPP_PROJECT_ID }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + GH_BASIC_AUTH: ${{ secrets.GH_BASIC_AUTH }} + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }} + MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} + SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + TESTFLIGHT_URL: ${{ secrets.TESTFLIGHT_URL_DAPP }} + run: | + PUBSPEC_FILE=../../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + fastlane release_testflight username:$APPLE_ID token:$GH_BASIC_AUTH project_id:$PROJECT_ID app_id:$APPLE_DAPP_ID app_store_key_id:$APP_STORE_KEY_ID apple_issuer_id:$APPLE_ISSUER_ID app_store_connect_key:"$APP_STORE_CONNECT_KEY" match_git_url:$MATCH_GIT_URL app_version:$VERSION_NUMBER flavor:production + +# Launch locally +# act -j build --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/release_dapp_ios.yml diff --git a/.github/workflows/release_dapp_ios_internal.yml b/.github/workflows/release_dapp_ios_internal.yml new file mode 100644 index 0000000..2248b14 --- /dev/null +++ b/.github/workflows/release_dapp_ios_internal.yml @@ -0,0 +1,76 @@ +name: iOS Dapp (internal) deploy + +on: + workflow_dispatch: + pull_request: + types: + - closed + +jobs: + build: + if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop') || github.event_name == 'workflow_dispatch' + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Cache + - name: Cache + uses: actions/cache@v3 + with: + path: | + .build + SourcePackagesCache + DerivedDataCache + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + # Build App + - name: Build App + # working-directory: example/dapp + working-directory: packages/reown_appkit/example/base + env: + PROJECT_ID: ${{ secrets.DAPP_PROJECT_ID }} + run: | + PUBSPEC_FILE=../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + flutter build ios --flavor internal --build-name $VERSION_NUMBER --dart-define="PROJECT_ID=$PROJECT_ID" --config-only --release + + # Fastlane + - name: Fastlane + # working-directory: example/dapp/ios + working-directory: packages/reown_appkit/example/base/ios + env: + BUNDLE_ID: "com.walletconnect.flutterdapp.internal" + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_DAPP_ID: ${{ secrets.APPLE_DAPP_ID_INTERNAL }} + PROJECT_ID: ${{ secrets.DAPP_PROJECT_ID }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + GH_BASIC_AUTH: ${{ secrets.GH_BASIC_AUTH }} + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }} + MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} + SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + TESTFLIGHT_URL: ${{ secrets.TESTFLIGHT_URL_DAPP_INTERNAL }} + run: | + PUBSPEC_FILE=../../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + fastlane release_testflight username:$APPLE_ID token:$GH_BASIC_AUTH project_id:$PROJECT_ID app_id:$APPLE_DAPP_ID app_store_key_id:$APP_STORE_KEY_ID apple_issuer_id:$APPLE_ISSUER_ID app_store_connect_key:"$APP_STORE_CONNECT_KEY" match_git_url:$MATCH_GIT_URL app_version:$VERSION_NUMBER flavor:internal + +# Launch locally +# act -j build --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/release_dapp_ios_internal.yml diff --git a/.github/workflows/release_modal_android.yml b/.github/workflows/release_modal_android.yml new file mode 100644 index 0000000..908d53c --- /dev/null +++ b/.github/workflows/release_modal_android.yml @@ -0,0 +1,122 @@ +name: Build Android App Release + +on: + workflow_dispatch: + push: + # branches: + # - 'release_v*' + tags: + - v[0-9]+.[0-9]+.[0-9]+ + +jobs: + build_with_signing: + name: Build Android App Release + runs-on: macos-latest + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create temp firebase key + env: + FIREBASE_KEY_BASE64: ${{ secrets.FIREBASE_KEY_BASE64 }} + run: | + # create variables + FIREBASE_KEY_PATH=$RUNNER_TEMP/flutter-c7c2c-6df892fe6ddb.json + + # import certificate and provisioning profile from secrets + echo -n "$FIREBASE_KEY_BASE64" | base64 --decode -o $FIREBASE_KEY_PATH + # Setup Java 11 + - name: Setup Java 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + architecture: x86_64 + cache: 'gradle' + - name: Cache Gradle + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + # Install Flutter SDK + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.19.5' + # Get package dependencies and generate files + - name: Get package dependencies and generate files + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + # Get example app dependencies and generate files + - name: Get example app dependencies and generate files + working-directory: example + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + # Build Android example app + - name: Build Android APK + working-directory: example + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + APPKIT_AUTH: ${{ secrets.APPKIT_AUTH }} + APPKIT_PROJECT_ID: ${{ secrets.APPKIT_PROJECT_ID }} + AUTH_SERVICE_URL: ${{ secrets.AUTH_SERVICE_URL }} + run: | + # Get app version from file + GRADLE_FILE=android/gradle.properties + VERSION_FILE=$GITHUB_WORKSPACE/lib/version.dart + + VERSION=`echo $(cat $VERSION_FILE) | sed "s/[^']*'\([^']*\)'.*/\1/"` + + # Set versionName on gradle.properties + awk -F"=" -v newval="$VERSION" 'BEGIN{OFS=FS} $1=="versionName"{$2=newval}1' $GRADLE_FILE > "$GRADLE_FILE.tmp" && mv "$GRADLE_FILE.tmp" $GRADLE_FILE + + # Increment versionCode (build number) on gradle.properties + awk -F"=" 'BEGIN{OFS=FS} $1=="versionCode"{$2=$2+1}1' $GRADLE_FILE > "$GRADLE_FILE.tmp" && mv "$GRADLE_FILE.tmp" $GRADLE_FILE + + # Get new versionCode + NEXT_BUILD=$(grep 'versionCode' $GRADLE_FILE | cut -d'=' -f2) + + # Build Android app with flutter + flutter build apk --build-name $VERSION --build-number $NEXT_BUILD --dart-define="PROJECT_ID=$PROJECT_ID" --dart-define="APPKIT_AUTH=$APPKIT_AUTH" --dart-define="APPKIT_PROJECT_ID=$APPKIT_PROJECT_ID" --dart-define="AUTH_SERVICE_URL=$AUTH_SERVICE_URL" --release --flavor stable + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v3 + # Setup Firebase + - name: Setup Firebase + uses: w9jds/setup-firebase@main + with: + tools-version: 13.0.1 + firebase_token: ${{ secrets.FIREBASE_TOKEN }} + - name: Upload APK + working-directory: example/build/app/outputs/flutter-apk + env: + APP_ID: ${{ secrets.ANDROID_APP_ID }} + run: | + firebase appdistribution:distribute app-stable-release.apk --app $APP_ID --release-notes "Web3Modal Flutter stable release" --groups "flutter-team, javascript-team, kotlin-team" + - name: Notify Channel + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: 'INCOMING_WEBHOOK' + with: + payload: |- + { + "text":"🤖 New *Android* build *${{ github.ref_name }}* stable version for *Web3Modal Flutter* was just deployed. Test at https://appdistribution.firebase.dev/i/a8efff56e3f0fdb0" + } + + # Clean up Flutter envs + - name: Clean up + if: ${{ always() }} + run: | + rm $RUNNER_TEMP/flutter-c7c2c-6df892fe6ddb.json + flutter clean + cd example + flutter clean \ No newline at end of file diff --git a/.github/workflows/release_modal_android_internal.yml b/.github/workflows/release_modal_android_internal.yml new file mode 100644 index 0000000..29bb0c7 --- /dev/null +++ b/.github/workflows/release_modal_android_internal.yml @@ -0,0 +1,121 @@ +name: Build Android App Internal (beta) + +on: + workflow_dispatch: + push: + # branches: + # - master + tags: + - 'v[0-9]+.[0-9]+.[0-9]-beta[0-9]**' + +jobs: + build_with_signing: + name: Build Android App Internal (beta) + runs-on: macos-latest + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create temp firebase key + env: + FIREBASE_KEY_BASE64: ${{ secrets.FIREBASE_KEY_BASE64 }} + run: | + # create variables + FIREBASE_KEY_PATH=$RUNNER_TEMP/flutter-c7c2c-6df892fe6ddb.json + + # import certificate and provisioning profile from secrets + echo -n "$FIREBASE_KEY_BASE64" | base64 --decode -o $FIREBASE_KEY_PATH + # Setup Java 11 + - name: Setup Java 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + architecture: x86_64 + cache: 'gradle' + - name: Cache Gradle + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + # Install Flutter SDK + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.19.5' + # Get package dependencies and generate files + - name: Get package dependencies and generate files + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + # Get example app dependencies and generate files + - name: Get example app dependencies and generate files + working-directory: example + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + # Build Android example app + - name: Build Android APK + working-directory: example + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + APPKIT_AUTH: ${{ secrets.APPKIT_AUTH }} + APPKIT_PROJECT_ID: ${{ secrets.APPKIT_PROJECT_ID }} + AUTH_SERVICE_URL: ${{ secrets.AUTH_SERVICE_URL }} + run: | + # Get app version from file + GRADLE_FILE=android/gradle.properties + VERSION_FILE=$GITHUB_WORKSPACE/lib/version.dart + + VERSION=`echo $(cat $VERSION_FILE) | sed "s/[^']*'\([^']*\)'.*/\1/"` + + # Set versionName on gradle.properties + awk -F"=" -v newval="$VERSION" 'BEGIN{OFS=FS} $1=="versionName"{$2=newval}1' $GRADLE_FILE > "$GRADLE_FILE.tmp" && mv "$GRADLE_FILE.tmp" $GRADLE_FILE + + # Increment versionCode (build number) on gradle.properties + awk -F"=" 'BEGIN{OFS=FS} $1=="versionCode"{$2=$2+1}1' $GRADLE_FILE > "$GRADLE_FILE.tmp" && mv "$GRADLE_FILE.tmp" $GRADLE_FILE + + # Get new versionCode + NEXT_BUILD=$(grep 'versionCode' $GRADLE_FILE | cut -d'=' -f2) + + # Build Android app with flutter + flutter build apk --build-name $VERSION --build-number $NEXT_BUILD --dart-define="PROJECT_ID=$PROJECT_ID" --dart-define="APPKIT_AUTH=$APPKIT_AUTH" --dart-define="APPKIT_PROJECT_ID=$APPKIT_PROJECT_ID" --dart-define="AUTH_SERVICE_URL=$AUTH_SERVICE_URL" --release --flavor beta + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v3 + # Setup Firebase + - name: Setup Firebase + uses: w9jds/setup-firebase@main + with: + tools-version: 13.0.1 + firebase_token: ${{ secrets.FIREBASE_TOKEN }} + - name: Upload APK + working-directory: example/build/app/outputs/flutter-apk + env: + APP_ID: ${{ secrets.ANDROID_APP_ID_INTERNAL }} + run: | + firebase appdistribution:distribute app-beta-release.apk --app $APP_ID --release-notes "Web3Modal Flutter beta release" --groups "flutter-team, javascript-team, kotlin-team" + - name: Notify Channel + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: 'INCOMING_WEBHOOK' + with: + payload: |- + { + "text":"🤖 New *Android* build *${{ github.ref_name }}* version for *Web3Modal Flutter* was just deployed. Test at https://appdistribution.firebase.dev/i/a47ee97e86fbdfff" + } + # Clean up Flutter envs + - name: Clean up + if: ${{ always() }} + run: | + rm $RUNNER_TEMP/flutter-c7c2c-6df892fe6ddb.json + flutter clean + cd example + flutter clean \ No newline at end of file diff --git a/.github/workflows/release_modal_ios.yml b/.github/workflows/release_modal_ios.yml new file mode 100644 index 0000000..c51f444 --- /dev/null +++ b/.github/workflows/release_modal_ios.yml @@ -0,0 +1,129 @@ +name: Build iOS App Release + +on: + workflow_dispatch: + push: + # branches: + # - 'release_v*' #${{ github.ref_name }} + tags: + - v[0-9]+.[0-9]+.[0-9]+ + +jobs: + build_with_signing: + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + # Install the Apple certificate and provisioning profile + - name: Install the Apple certificate and provisioning profile + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.APPLE_DISTRIBUTION_CERT }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + # create variables + BUILD_CERT_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/FlutterAppStoreProfileWithPush.mobileprovision + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $BUILD_CERT_PATH + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $BUILD_CERT_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + # Create p8 Auth Key from secrets + - name: Create p8 Auth Key + env: + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + run: | + # Create private_keys folder + KEY_PATH=$GITHUB_WORKSPACE/example/build/ios/ipa/private_keys + mkdir -p $KEY_PATH + AUTH_KEY_PATH=$KEY_PATH/AuthKey_$APP_STORE_KEY_ID.p8 + + # import certificate and provisioning profile from secrets + echo -n "$APP_STORE_CONNECT_KEY" | base64 --decode -o $AUTH_KEY_PATH + # Install Flutter SDK + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.19.5' + # Get package dependencies and generate files + - name: Get package dependencies and generate files + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + # Get example app dependencies and generate files + - name: Get example app dependencies and generate files + working-directory: example + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + # Build ios example app + - name: Build ios example app + working-directory: example + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + APPKIT_AUTH: ${{ secrets.APPKIT_AUTH }} + APPKIT_PROJECT_ID: ${{ secrets.APPKIT_PROJECT_ID }} + AUTH_SERVICE_URL: ${{ secrets.AUTH_SERVICE_URL }} + run: | + # Get app version from file + FILE_VALUE=$(echo | grep "^version: " pubspec.yaml) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + # Build ios app with flutter + flutter build ios --build-name $VERSION_NUMBER --dart-define="PROJECT_ID=$PROJECT_ID" --dart-define="APPKIT_AUTH=$APPKIT_AUTH" --dart-define="APPKIT_PROJECT_ID=$APPKIT_PROJECT_ID" --dart-define="AUTH_SERVICE_URL=$AUTH_SERVICE_URL" --config-only --release + + cd ios + agvtool new-marketing-version $VERSION_NUMBER + agvtool next-version -all + + # Archive and export + xcodebuild -workspace "$GITHUB_WORKSPACE/example/ios/Runner.xcworkspace" -scheme Runner -sdk iphoneos -destination generic/platform=iOS -archivePath "$GITHUB_WORKSPACE/example/ios/Runner.xcarchive" archive + xcodebuild -exportArchive -allowProvisioningUpdates -sdk iphoneos -archivePath "$GITHUB_WORKSPACE/example/ios/Runner.xcarchive" -exportOptionsPlist "$GITHUB_WORKSPACE/example/ios/Runner/ExportOptionsRelease.plist" -exportPath "$GITHUB_WORKSPACE/example/build/ios/ipa" -authenticationKeyIssuerID $APPLE_ISSUER_ID -authenticationKeyID $APP_STORE_KEY_ID -authenticationKeyPath "$GITHUB_WORKSPACE/example/build/ios/ipa/private_keys/AuthKey_$APP_STORE_KEY_ID.p8" + # Upload IPA to Testflight + - name: Upload IPA to Testflight + working-directory: example/build/ios/ipa + env: + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + run: xcrun altool --upload-app --type ios -f web3modal_flutter.ipa --apiKey $APP_STORE_KEY_ID --apiIssuer $APPLE_ISSUER_ID + - name: Notify Channel + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: 'INCOMING_WEBHOOK' + with: + payload: |- + { + "text":"🍎 New *iOS* build *${{ github.ref_name }}* stable version for *Web3Modal Flutter* was just deployed." + } + # Clean up + - name: Clean up + if: ${{ always() }} + run: | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db + rm ~/Library/MobileDevice/Provisioning\ Profiles/FlutterAppStoreProfileWithPush.mobileprovision + flutter clean + cd example + flutter clean \ No newline at end of file diff --git a/.github/workflows/release_modal_ios_internal.yml b/.github/workflows/release_modal_ios_internal.yml new file mode 100644 index 0000000..e6d75b8 --- /dev/null +++ b/.github/workflows/release_modal_ios_internal.yml @@ -0,0 +1,137 @@ +name: Build iOS App Internal (beta) + +on: + workflow_dispatch: + push: + # branches: + # - master + tags: + - 'v[0-9]+.[0-9]+.[0-9]-beta[0-9]**' + +jobs: + build_with_signing: + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + # Install the Apple certificate and provisioning profile + - name: Install the Apple certificate and provisioning profile + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.APPLE_DISTRIBUTION_CERT }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_INTERNAL_BASE64 }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + # create variables + BUILD_CERT_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/FlutterAppStoreProfileInternal.mobileprovision + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $BUILD_CERT_PATH + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $BUILD_CERT_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + # Create p8 Auth Key from secrets + - name: Create p8 Auth Key + env: + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + run: | + # Create private_keys folder + KEY_PATH=$GITHUB_WORKSPACE/example/build/ios/ipa/private_keys + mkdir -p $KEY_PATH + AUTH_KEY_PATH=$KEY_PATH/AuthKey_$APP_STORE_KEY_ID.p8 + + # import certificate and provisioning profile from secrets + echo -n "$APP_STORE_CONNECT_KEY" | base64 --decode -o $AUTH_KEY_PATH + # Install Flutter SDK + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.19.5' + # Get package dependencies and generate files + - name: Get package dependencies and generate files + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + # Get example app dependencies and generate files + - name: Get example app dependencies and generate files + working-directory: example + run: | + flutter pub get + flutter pub run build_runner build --delete-conflicting-outputs + # Build ios example app + - name: Build ios example app + working-directory: example + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + APPKIT_AUTH: ${{ secrets.APPKIT_AUTH }} + APPKIT_PROJECT_ID: ${{ secrets.APPKIT_PROJECT_ID }} + AUTH_SERVICE_URL: ${{ secrets.AUTH_SERVICE_URL }} + run: | + # Get app version from file + FILE_VALUE=$(echo | grep "^version: " pubspec.yaml) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + # Change bundleId in Runner scheme + sed -i '' 's/com.web3modal.flutterExample/com.web3modal.flutterExample.internal/g' ios/Runner.xcodeproj/project.pbxproj + sed -i '' 's/com.web3modal.flutterExample/com.web3modal.flutterExample.internal/g' ios/Runner/Info.plist + + # Change provisioning prfile in project.pbxproj + sed -i '' 's/FlutterAppStoreProfileWithPush/FlutterAppStoreProfileInternal/g' ios/Runner.xcodeproj/project.pbxproj + + # Build ios app with flutter + flutter build ios --build-name $VERSION_NUMBER --dart-define="PROJECT_ID=$PROJECT_ID" --dart-define="APPKIT_AUTH=$APPKIT_AUTH" --dart-define="APPKIT_PROJECT_ID=$APPKIT_PROJECT_ID" --dart-define="AUTH_SERVICE_URL=$AUTH_SERVICE_URL" --config-only --release + + cd ios + agvtool new-marketing-version $VERSION_NUMBER + agvtool next-version -all + + # Archive and export + xcodebuild -workspace "$GITHUB_WORKSPACE/example/ios/Runner.xcworkspace" -scheme Runner -sdk iphoneos -destination generic/platform=iOS -archivePath "$GITHUB_WORKSPACE/example/ios/Runner.xcarchive" archive + xcodebuild -exportArchive -allowProvisioningUpdates -sdk iphoneos -archivePath "$GITHUB_WORKSPACE/example/ios/Runner.xcarchive" -exportOptionsPlist "$GITHUB_WORKSPACE/example/ios/Runner/ExportOptionsInternal.plist" -exportPath "$GITHUB_WORKSPACE/example/build/ios/ipa" -authenticationKeyIssuerID $APPLE_ISSUER_ID -authenticationKeyID $APP_STORE_KEY_ID -authenticationKeyPath "$GITHUB_WORKSPACE/example/build/ios/ipa/private_keys/AuthKey_$APP_STORE_KEY_ID.p8" + # Upload IPA to Testflight + - name: Upload IPA to Testflight + working-directory: example/build/ios/ipa + env: + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + run: | + xcrun altool --upload-app --type ios -f web3modal_flutter.ipa --apiKey $APP_STORE_KEY_ID --apiIssuer $APPLE_ISSUER_ID + - name: Notify Channel + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: 'INCOMING_WEBHOOK' + with: + payload: |- + { + "text":"🍎 New *iOS* build *${{ github.ref_name }}* version for *Web3Modal Flutter* was just deployed. Test at https://testflight.apple.com/join/pzF2SUVm" + } + # Clean up + - name: Clean up + if: ${{ always() }} + run: | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db + rm ~/Library/MobileDevice/Provisioning\ Profiles/FlutterAppStoreProfileInternal.mobileprovision + flutter clean + cd example + flutter clean diff --git a/.github/workflows/release_wallet_android.yml b/.github/workflows/release_wallet_android.yml new file mode 100644 index 0000000..adde384 --- /dev/null +++ b/.github/workflows/release_wallet_android.yml @@ -0,0 +1,76 @@ +name: Android Wallet (production) deploy + +on: + workflow_dispatch: + pull_request: + types: + - closed + +jobs: + build: + # if: github.event.pull_request.merged == true + if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master') || github.event_name == 'workflow_dispatch' + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Install Java 17 + - name: Install Java 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + architecture: x86_64 + cache: 'gradle' + + # Cache Gradle + - name: Cache Gradle + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + - name: Add Secrets file + # working-directory: example/wallet/android + working-directory: packages/reown_walletkit/example/android + run: touch secrets.properties && echo "${{ secrets.SECRETS_FILE }}" >> secrets.properties + + - name: Add Keystore file + # working-directory: example/wallet/android + working-directory: packages/reown_walletkit/example/android + run: echo "${{ secrets.KEYSTORE }}" | base64 --decode >> app/debug.keystore + + # Fastlane + - name: Fastlane + # working-directory: example/wallet/android + working-directory: packages/reown_walletkit/example/android + env: + FLAVOR: "production" + PROJECT_ID: ${{ secrets.WALLET_PROJECT_ID }} + SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + FIREBASE_WALLET_ID: ${{ secrets.FIREBASE_WALLET_ID }} + DOWNLOAD_URL: ${{ secrets.DOWNLOAD_URL_WALLET }} + run: | + PUBSPEC_FILE=../../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + fastlane add_plugin firebase_app_distribution + fastlane release_firebase project_id:$PROJECT_ID app_version:$VERSION_NUMBER + +# Launch locally +# Remove build: if: +# act -j build --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/release_wallet_android.yml diff --git a/.github/workflows/release_wallet_android_internal.yml b/.github/workflows/release_wallet_android_internal.yml new file mode 100644 index 0000000..dfc4ad3 --- /dev/null +++ b/.github/workflows/release_wallet_android_internal.yml @@ -0,0 +1,75 @@ +name: Android Wallet (internal) deploy + +on: + workflow_dispatch: + pull_request: + types: + - closed + +jobs: + build: + if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop') || github.event_name == 'workflow_dispatch' + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Install Java 17 + - name: Install Java 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + architecture: x86_64 + cache: 'gradle' + + # Cache Gradle + - name: Cache Gradle + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + - name: Add Secrets file + # working-directory: example/wallet/android + working-directory: packages/reown_walletkit/example/android + run: touch secrets.properties && echo "${{ secrets.SECRETS_FILE }}" >> secrets.properties + + - name: Add Keystore file + # working-directory: example/wallet/android + working-directory: packages/reown_walletkit/example/android + run: echo "${{ secrets.KEYSTORE }}" | base64 --decode >> app/debug.keystore + + # Fastlane + - name: Fastlane + # working-directory: example/wallet/android + working-directory: packages/reown_walletkit/example/android + env: + FLAVOR: "internal" + PROJECT_ID: ${{ secrets.WALLET_PROJECT_ID }} + SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + FIREBASE_WALLET_ID: ${{ secrets.FIREBASE_WALLET_ID_INTERNAL }} + DOWNLOAD_URL: ${{ secrets.DOWNLOAD_URL_WALLET_INTERNAL }} + run: | + PUBSPEC_FILE=../../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + fastlane add_plugin firebase_app_distribution + fastlane release_firebase project_id:$PROJECT_ID app_version:$VERSION_NUMBER + +# Launch locally +# Remove build: if: +# act -j build --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/release_wallet_android.yml diff --git a/.github/workflows/release_wallet_ios.yml b/.github/workflows/release_wallet_ios.yml new file mode 100644 index 0000000..e30fbd7 --- /dev/null +++ b/.github/workflows/release_wallet_ios.yml @@ -0,0 +1,76 @@ +name: iOS Wallet (production) deploy + +on: + workflow_dispatch: + pull_request: + types: + - closed + +jobs: + build: + if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master') || github.event_name == 'workflow_dispatch' + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Cache + - name: Cache + uses: actions/cache@v3 + with: + path: | + .build + SourcePackagesCache + DerivedDataCache + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + # Build App + - name: Build App + # working-directory: example/wallet + working-directory: packages/reown_walletkit + env: + PROJECT_ID: ${{ secrets.WALLET_PROJECT_ID }} + run: | + PUBSPEC_FILE=../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + flutter build ios --flavor production --build-name $VERSION_NUMBER --dart-define="PROJECT_ID=$PROJECT_ID" --config-only --release + + # Fastlane + - name: Fastlane + # working-directory: example/wallet/ios + working-directory: packages/reown_walletkit/example/ios + env: + BUNDLE_ID: "com.walletconnect.flutterwallet" + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_DAPP_ID: ${{ secrets.APPLE_WALLET_ID }} + PROJECT_ID: ${{ secrets.WALLET_PROJECT_ID }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + GH_BASIC_AUTH: ${{ secrets.GH_BASIC_AUTH }} + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }} + MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} + SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + TESTFLIGHT_URL: ${{ secrets.TESTFLIGHT_URL_WALLET }} + run: | + PUBSPEC_FILE=../../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + fastlane release_testflight username:$APPLE_ID token:$GH_BASIC_AUTH project_id:$PROJECT_ID app_id:$APPLE_DAPP_ID app_store_key_id:$APP_STORE_KEY_ID apple_issuer_id:$APPLE_ISSUER_ID app_store_connect_key:"$APP_STORE_CONNECT_KEY" match_git_url:$MATCH_GIT_URL app_version:$VERSION_NUMBER flavor:production + +# Launch locally +# act -j build --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/release_wallet_ios.yml diff --git a/.github/workflows/release_wallet_ios_internal.yml b/.github/workflows/release_wallet_ios_internal.yml new file mode 100644 index 0000000..e04edc4 --- /dev/null +++ b/.github/workflows/release_wallet_ios_internal.yml @@ -0,0 +1,76 @@ +name: iOS Wallet (internal) deploy + +on: + workflow_dispatch: + pull_request: + types: + - closed + +jobs: + build: + if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop') || github.event_name == 'workflow_dispatch' + runs-on: macos-latest-xlarge + + steps: + # Checkout the repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Cache + - name: Cache + uses: actions/cache@v3 + with: + path: | + .build + SourcePackagesCache + DerivedDataCache + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies + + # Build App + - name: Build App + # working-directory: example/wallet + working-directory: packages/reown_walletkit/example + env: + PROJECT_ID: ${{ secrets.WALLET_PROJECT_ID }} + run: | + PUBSPEC_FILE=../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + flutter build ios --flavor internal --build-name $VERSION_NUMBER --dart-define="PROJECT_ID=$PROJECT_ID" --config-only --release + + # Fastlane + - name: Fastlane + # working-directory: example/wallet/ios + working-directory: packages/reown_walletkit/example/ios + env: + BUNDLE_ID: "com.walletconnect.flutterwallet.internal" + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_DAPP_ID: ${{ secrets.APPLE_WALLET_ID_INTERNAL }} + PROJECT_ID: ${{ secrets.WALLET_PROJECT_ID }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + GH_BASIC_AUTH: ${{ secrets.GH_BASIC_AUTH }} + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }} + MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} + SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + TESTFLIGHT_URL: ${{ secrets.TESTFLIGHT_URL_WALLET_INTERNAL }} + run: | + PUBSPEC_FILE=../../../pubspec.yaml + FILE_VALUE=$(echo | grep "^version: " $PUBSPEC_FILE) + PARTS=(${FILE_VALUE//:/ }) + FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=(${FULL_VERSION//-/ }) + + fastlane release_testflight username:$APPLE_ID token:$GH_BASIC_AUTH project_id:$PROJECT_ID app_id:$APPLE_DAPP_ID app_store_key_id:$APP_STORE_KEY_ID apple_issuer_id:$APPLE_ISSUER_ID app_store_connect_key:"$APP_STORE_CONNECT_KEY" match_git_url:$MATCH_GIT_URL app_version:$VERSION_NUMBER flavor:internal + +# Launch locally +# act -j build --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret -W .github/workflows/release_wallet_ios_internal.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..474a12e --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..bc3f0ae --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "300451adae589accbece3490f4396f10bdf15e6e" + channel: "stable" + +project_type: package diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..212a53d --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Reown, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..237012c --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# **Reown - Flutter** + +The communications protocol for web3, Reown brings the ecosystem together by enabling hundreds of wallets and apps to securely connect and interact. This repository contains Flutter implementation of WalletConnect protocol for Flutter applications. + +## SDK Chart + +| [Core SDK](packages/reown_core) | [Sign SDK](packages/reown_sign) | [WalletKit](packages/reown_walletkit) | [AppKit](packages/reown_appkit) | +|--------------------------|---------------------------|--------------------------------|--------------------------| +| 1.0.0 | 1.0.0 | 1.0.0 | 1.0.0 | + +## License + +Reown is released under the Apache 2.0 license. [See LICENSE](/LICENSE) for details. \ No newline at end of file diff --git a/packages/reown_appkit/.gitignore b/packages/reown_appkit/.gitignore new file mode 100644 index 0000000..474a12e --- /dev/null +++ b/packages/reown_appkit/.gitignore @@ -0,0 +1,58 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/packages/reown_appkit/.metadata b/packages/reown_appkit/.metadata new file mode 100644 index 0000000..5579401 --- /dev/null +++ b/packages/reown_appkit/.metadata @@ -0,0 +1,27 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "300451adae589accbece3490f4396f10bdf15e6e" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 300451adae589accbece3490f4396f10bdf15e6e + base_revision: 300451adae589accbece3490f4396f10bdf15e6e + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/reown_appkit/CHANGELOG.md b/packages/reown_appkit/CHANGELOG.md new file mode 100644 index 0000000..69b4a02 --- /dev/null +++ b/packages/reown_appkit/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +Initial release. diff --git a/packages/reown_appkit/LICENSE b/packages/reown_appkit/LICENSE new file mode 100644 index 0000000..212a53d --- /dev/null +++ b/packages/reown_appkit/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Reown, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/reown_appkit/README.md b/packages/reown_appkit/README.md new file mode 100644 index 0000000..1bf04d9 --- /dev/null +++ b/packages/reown_appkit/README.md @@ -0,0 +1,17 @@ +# **Reown - AppKit Flutter** + +The all-in-one stack to build apps for the new internet
+With a layered feature stack spanning onboarding to payments, messaging and more, AppKit enables apps to build powerful top-to-bottom web3 experiences made to last, all through one seamless integration. + +Read more about it on our [website](https://reown.com/appkit) + + + +## Documentation + +For a full reference please check the [Official Documentation](https://docs.reown.com/appkit/flutter/core/installation) + +## Example + +Please check the [example](https://github.com/WalletConnect/Web3ModalFlutter/tree/master/packages/reown_appkit/example/modal) folder for the example. + diff --git a/packages/reown_appkit/analysis_options.yaml b/packages/reown_appkit/analysis_options.yaml new file mode 100644 index 0000000..b677fc6 --- /dev/null +++ b/packages/reown_appkit/analysis_options.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:lints/recommended.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + non_constant_identifier_names: false + constant_identifier_names: false + avoid_print: true + prefer_single_quotes: true + sort_pub_dependencies: true + avoid_unnecessary_containers: true + cancel_subscriptions: true + +analyzer: + exclude: + - '**.freezed.dart' + - '**.g.dart' + - '**/*.freezed.dart' + - '**/*.g.dart' + - '**/generated_plugin_registrant.dart' + errors: + invalid_annotation_target: ignore +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reown_appkit/build.yaml b/packages/reown_appkit/build.yaml new file mode 100644 index 0000000..71e3c33 --- /dev/null +++ b/packages/reown_appkit/build.yaml @@ -0,0 +1,16 @@ +targets: + $default: + builders: + build_version: + options: + output: lib/version.dart + freezed: + generate_for: + - lib/**.dart + - test/shared/shared_test_utils.dart + json_serializable: + options: + explicit_to_json: true + generate_for: + - lib/**.dart + - test/shared/shared_test_utils.dart diff --git a/packages/reown_appkit/example/base/.gitignore b/packages/reown_appkit/example/base/.gitignore new file mode 100644 index 0000000..0e140c0 --- /dev/null +++ b/packages/reown_appkit/example/base/.gitignore @@ -0,0 +1,59 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/packages/reown_appkit/example/base/.metadata b/packages/reown_appkit/example/base/.metadata new file mode 100644 index 0000000..cdf0334 --- /dev/null +++ b/packages/reown_appkit/example/base/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: web + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/reown_appkit/example/base/README.md b/packages/reown_appkit/example/base/README.md new file mode 100644 index 0000000..a65265d --- /dev/null +++ b/packages/reown_appkit/example/base/README.md @@ -0,0 +1,7 @@ +# dapp + +An example dApp built using flutter. + +## To Run + +`flutter run --dart-define=PROJECT_ID=xxx` \ No newline at end of file diff --git a/packages/reown_appkit/example/base/analysis_options.yaml b/packages/reown_appkit/example/base/analysis_options.yaml new file mode 100644 index 0000000..b677fc6 --- /dev/null +++ b/packages/reown_appkit/example/base/analysis_options.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:lints/recommended.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + non_constant_identifier_names: false + constant_identifier_names: false + avoid_print: true + prefer_single_quotes: true + sort_pub_dependencies: true + avoid_unnecessary_containers: true + cancel_subscriptions: true + +analyzer: + exclude: + - '**.freezed.dart' + - '**.g.dart' + - '**/*.freezed.dart' + - '**/*.g.dart' + - '**/generated_plugin_registrant.dart' + errors: + invalid_annotation_target: ignore +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reown_appkit/example/base/android/.gitignore b/packages/reown_appkit/example/base/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/packages/reown_appkit/example/base/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/reown_appkit/example/base/android/Gemfile b/packages/reown_appkit/example/base/android/Gemfile new file mode 100644 index 0000000..cdd3a6b --- /dev/null +++ b/packages/reown_appkit/example/base/android/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "fastlane" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/packages/reown_appkit/example/base/android/Gemfile.lock b/packages/reown_appkit/example/base/android/Gemfile.lock new file mode 100644 index 0000000..1d021b0 --- /dev/null +++ b/packages/reown_appkit/example/base/android/Gemfile.lock @@ -0,0 +1,228 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.3.0) + aws-partitions (1.956.0) + aws-sdk-core (3.201.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.8.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.111.0) + faraday (1.10.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.3.1) + fastlane (2.221.1) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-firebase_app_distribution (0.9.1) + google-apis-firebaseappdistribution_v1 (~> 0.3.0) + google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-firebaseappdistribution_v1 (0.3.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-firebaseappdistribution_v1alpha (0.2.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.6) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.6.2) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.15.0) + multipart-post (2.4.1) + nanaimo (0.3.0) + naturally (2.2.1) + nkf (0.2.0) + optparse (0.5.0) + os (1.1.4) + plist (3.7.1) + public_suffix (6.0.0) + rake (13.2.1) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.9) + strscan + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.5) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + strscan (3.1.0) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.5.0) + word_wrap (1.0.0) + xcodeproj (1.24.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + fastlane + fastlane-plugin-firebase_app_distribution + +BUNDLED WITH + 2.5.14 diff --git a/packages/reown_appkit/example/base/android/app/build.gradle b/packages/reown_appkit/example/base/android/app/build.gradle new file mode 100644 index 0000000..6ab904e --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/build.gradle @@ -0,0 +1,109 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('secrets.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.walletconnect.flutterdapp" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + // Specifies one flavor dimension. + flavorDimensions = ["version"] + + productFlavors { + internal { + dimension "version" + applicationIdSuffix ".internal" + manifestPlaceholders = [ + appIcon: "@mipmap/ic_launcher_internal", + applicationLabel: "FL Dapp (internal)", + ] + } + production { + dimension "version" + manifestPlaceholders = [ + appIcon: "@mipmap/ic_launcher", + applicationLabel: "FL Dapp", + ] + } + } + + signingConfigs { + debug { + storeFile file(keystoreProperties['WC_FILENAME']) + storePassword keystoreProperties['WC_STORE_PASSWORD'] + keyAlias keystoreProperties['WC_KEYSTORE_ALIAS'] + keyPassword keystoreProperties['WC_KEY_PASSWORD'] + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } + + namespace 'com.walletconnect.flutterdapp' +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/reown_appkit/example/base/android/app/src/debug/AndroidManifest.xml b/packages/reown_appkit/example/base/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml b/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4215461 --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/android/app/src/main/ic_launcher-playstore.png b/packages/reown_appkit/example/base/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..bbea762 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/ic_launcher-playstore.png differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/ic_launcher_internal-playstore.png b/packages/reown_appkit/example/base/android/app/src/main/ic_launcher_internal-playstore.png new file mode 100644 index 0000000..cbb9ef7 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/ic_launcher_internal-playstore.png differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/kotlin/com/example/dapp/MainActivity.kt b/packages/reown_appkit/example/base/android/app/src/main/kotlin/com/example/dapp/MainActivity.kt new file mode 100644 index 0000000..c531c20 --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/kotlin/com/example/dapp/MainActivity.kt @@ -0,0 +1,65 @@ +package com.example.dapp + +import io.flutter.embedding.android.FlutterActivity +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.annotation.NonNull + +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +class MainActivity: FlutterActivity() { + private val eventsChannel = "com.walletconnect.flutterdapp/events" + private val methodsChannel = "com.walletconnect.flutterdapp/methods" + + private var initialLink: String? = null + private var linksReceiver: BroadcastReceiver? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val intent: Intent? = intent + initialLink = intent?.data?.toString() + + EventChannel(flutterEngine?.dartExecutor?.binaryMessenger, eventsChannel).setStreamHandler( + object : EventChannel.StreamHandler { + override fun onListen(args: Any?, events: EventChannel.EventSink) { + linksReceiver = createChangeReceiver(events) + } + override fun onCancel(args: Any?) { + linksReceiver = null + } + } + ) + + MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, methodsChannel).setMethodCallHandler { call, result -> + if (call.method == "initialLink") { + if (initialLink != null) { + result.success(initialLink) + } + } + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + if (intent.action === Intent.ACTION_VIEW) { + linksReceiver?.onReceive(this.applicationContext, intent) + } + } + + fun createChangeReceiver(events: EventChannel.EventSink): BroadcastReceiver? { + return object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val dataString = intent.dataString ?: + events.error("UNAVAILABLE", "Link unavailable", null) + events.success(dataString) + } + } + } +} diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/reown_appkit/example/base/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/drawable/ic_launcher_background.xml b/packages/reown_appkit/example/base/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/drawable/ic_launcher_internal_background.xml b/packages/reown_appkit/example/base/android/app/src/main/res/drawable/ic_launcher_internal_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/drawable/ic_launcher_internal_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/drawable/launch_background.xml b/packages/reown_appkit/example/base/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..c4a603d --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal.xml b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal.xml new file mode 100644 index 0000000..c87a157 --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal_round.xml b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal_round.xml new file mode 100644 index 0000000..c87a157 --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..c4a603d --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..4cb5c1e Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..4482ea7 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..da4f9d6 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..effa105 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..b7d20b0 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..da1b55f Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..e66eaba Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..93e464c Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..b9fb3e8 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..a0ca446 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..484b1cb Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..eb82d85 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..c3c8239 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..3ce0046 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..5cc18a9 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..5b0cae3 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..462b01b Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..5276092 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..9cf8da3 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..8d1771a Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..a9755e4 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..7d6ceae Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..53f36b0 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..af8dfaa Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..683e871 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..04d8335 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..5509505 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_foreground.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..fd9d02a Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..7fa4e47 Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..30cee2f Binary files /dev/null and b/packages/reown_appkit/example/base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/values-night/styles.xml b/packages/reown_appkit/example/base/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/reown_appkit/example/base/android/app/src/main/res/values/styles.xml b/packages/reown_appkit/example/base/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/reown_appkit/example/base/android/app/src/profile/AndroidManifest.xml b/packages/reown_appkit/example/base/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/packages/reown_appkit/example/base/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/reown_appkit/example/base/android/build.gradle b/packages/reown_appkit/example/base/android/build.gradle new file mode 100644 index 0000000..482db1b --- /dev/null +++ b/packages/reown_appkit/example/base/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.9.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.1.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/reown_appkit/example/base/android/fastlane/Appfile b/packages/reown_appkit/example/base/android/fastlane/Appfile new file mode 100644 index 0000000..09bd2c5 --- /dev/null +++ b/packages/reown_appkit/example/base/android/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one +package_name("com.walletconnect.flutterdapp") # e.g. com.krausefx.app diff --git a/packages/reown_appkit/example/base/android/fastlane/Fastfile b/packages/reown_appkit/example/base/android/fastlane/Fastfile new file mode 100644 index 0000000..228e7db --- /dev/null +++ b/packages/reown_appkit/example/base/android/fastlane/Fastfile @@ -0,0 +1,98 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + + # Helper method to read the changelog + def read_changelog + changelog_file = '../../../../CHANGELOG.md' + changelog = "" + + if File.exist?(changelog_file) + File.open(changelog_file, 'r') do |file| + changelog = file.read + end + + # Split the changelog into entries based on the version header pattern + entries = changelog.split(/^##\s/) + + # Get the latest entry, which is the first one after splitting + latest_entry = entries[1] + + # Re-add the '##' header to the latest entry and remove empty lines + changelog = latest_entry.strip if latest_entry + changelog = changelog.gsub /^$\n/, '' + else + UI.user_error!("CHANGELOG.md file not found") + end + + changelog + end + + + lane :release_firebase do |options| + + slack_url = ENV['SLACK_URL'] + firebase_token = ENV['FIREBASE_TOKEN'] + firebase_dapp_id = ENV['FIREBASE_DAPP_ID'] + _flavor = ENV['FLAVOR'] + _download_url = ENV['DOWNLOAD_URL'] + + _latest_release = firebase_app_distribution_get_latest_release( + app: "#{firebase_dapp_id}", + ) + if _latest_release && _latest_release[:buildVersion] && !_latest_release[:buildVersion].empty? + _new_build_number = _latest_release[:buildVersion].to_i + 1 + else + _new_build_number = 1 + end + + _app_version = "#{options[:app_version]}" + _project_id = "#{options[:project_id]}" + + # gradle(task: 'assemble', build_type: 'Release') + sh "flutter build apk --build-name #{_app_version} --build-number #{_new_build_number} --dart-define='PROJECT_ID=#{_project_id}' --flavor #{_flavor} --release" + + changelog = read_changelog + + firebase_app_distribution( + app: "#{firebase_dapp_id}", + groups: "flutter-team, javascript-team, kotlin-team, rust-team, unity, wc-testers", + android_artifact_path: "../build/app/outputs/flutter-apk/app-#{_flavor}-release.apk", + release_notes: changelog, + android_artifact_type: "APK", + ) + + slack( + message: "📱 Flutter Dapp #{_app_version}-#{_flavor} (#{_new_build_number}) for 🤖 Android successfully released!\n\n", + default_payloads: [], + attachment_properties: { + fields: [ + { + title: "CHANGELOG", + value: changelog, + }, + { + title: "LINK", + value: "#{_download_url}", + }, + ] + } + ) + + end +end diff --git a/packages/reown_appkit/example/base/android/fastlane/Pluginfile b/packages/reown_appkit/example/base/android/fastlane/Pluginfile new file mode 100644 index 0000000..b18539b --- /dev/null +++ b/packages/reown_appkit/example/base/android/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-firebase_app_distribution' diff --git a/packages/reown_appkit/example/base/android/gradle.properties b/packages/reown_appkit/example/base/android/gradle.properties new file mode 100644 index 0000000..b9a9a24 --- /dev/null +++ b/packages/reown_appkit/example/base/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/packages/reown_appkit/example/base/android/gradle/wrapper/gradle-wrapper.properties b/packages/reown_appkit/example/base/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8bc9958 --- /dev/null +++ b/packages/reown_appkit/example/base/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip diff --git a/packages/reown_appkit/example/base/android/settings.gradle b/packages/reown_appkit/example/base/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/packages/reown_appkit/example/base/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/reown_appkit/example/base/ios/.gitignore b/packages/reown_appkit/example/base/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/reown_appkit/example/base/ios/Flutter/AppFrameworkInfo.plist b/packages/reown_appkit/example/base/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/packages/reown_appkit/example/base/ios/Flutter/Debug-internal.xcconfig b/packages/reown_appkit/example/base/ios/Flutter/Debug-internal.xcconfig new file mode 100644 index 0000000..4e71b8d --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Flutter/Debug-internal.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-internal.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_appkit/example/base/ios/Flutter/Debug-production.xcconfig b/packages/reown_appkit/example/base/ios/Flutter/Debug-production.xcconfig new file mode 100644 index 0000000..b3b72ba --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Flutter/Debug-production.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-production.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_appkit/example/base/ios/Flutter/Debug.xcconfig b/packages/reown_appkit/example/base/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..4e71b8d --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-internal.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_appkit/example/base/ios/Flutter/Release-internal.xcconfig b/packages/reown_appkit/example/base/ios/Flutter/Release-internal.xcconfig new file mode 100644 index 0000000..daaf1c7 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Flutter/Release-internal.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-internal.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_appkit/example/base/ios/Flutter/Release-production.xcconfig b/packages/reown_appkit/example/base/ios/Flutter/Release-production.xcconfig new file mode 100644 index 0000000..f54fd05 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Flutter/Release-production.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-production.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_appkit/example/base/ios/Flutter/Release.xcconfig b/packages/reown_appkit/example/base/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..daaf1c7 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-internal.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_appkit/example/base/ios/Gemfile b/packages/reown_appkit/example/base/ios/Gemfile new file mode 100644 index 0000000..7a118b4 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/packages/reown_appkit/example/base/ios/Gemfile.lock b/packages/reown_appkit/example/base/ios/Gemfile.lock new file mode 100644 index 0000000..550231d --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Gemfile.lock @@ -0,0 +1,220 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.3.0) + aws-partitions (1.956.0) + aws-sdk-core (3.201.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.8.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.111.0) + faraday (1.10.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.3.1) + fastlane (2.221.1) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.6) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.6.2) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.15.0) + multipart-post (2.4.1) + nanaimo (0.3.0) + naturally (2.2.1) + nkf (0.2.0) + optparse (0.5.0) + os (1.1.4) + plist (3.7.1) + public_suffix (6.0.0) + rake (13.2.1) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.9) + strscan + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.5) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + strscan (3.1.0) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.5.0) + word_wrap (1.0.0) + xcodeproj (1.24.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.5.14 diff --git a/packages/reown_appkit/example/base/ios/Podfile b/packages/reown_appkit/example/base/ios/Podfile new file mode 100644 index 0000000..2562dc1 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug-production' => :debug, + 'Profile-production' => :release, + 'Release-production' => :release, + 'Debug-internal' => :debug, + 'Profile-internal' => :release, + 'Release-internal' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/reown_appkit/example/base/ios/Podfile.lock b/packages/reown_appkit/example/base/ios/Podfile.lock new file mode 100644 index 0000000..b6f12b6 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Podfile.lock @@ -0,0 +1,83 @@ +PODS: + - appcheck (1.0.3): + - Flutter + - coinbase_wallet_sdk (0.0.1): + - CoinbaseWalletSDK/CrossPlatform (= 1.0.4) + - Flutter + - CoinbaseWalletSDK/Client (1.0.4) + - CoinbaseWalletSDK/CrossPlatform (1.0.4): + - CoinbaseWalletSDK/Client + - connectivity_plus (0.0.1): + - Flutter + - FlutterMacOS + - Flutter (1.0.0) + - package_info_plus (0.4.5): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite (0.0.3): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter + +DEPENDENCIES: + - appcheck (from `.symlinks/plugins/appcheck/ios`) + - coinbase_wallet_sdk (from `.symlinks/plugins/coinbase_wallet_sdk/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) + - Flutter (from `Flutter`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + +SPEC REPOS: + trunk: + - CoinbaseWalletSDK + +EXTERNAL SOURCES: + appcheck: + :path: ".symlinks/plugins/appcheck/ios" + coinbase_wallet_sdk: + :path: ".symlinks/plugins/coinbase_wallet_sdk/ios" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/darwin" + Flutter: + :path: Flutter + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite: + :path: ".symlinks/plugins/sqflite/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + +SPEC CHECKSUMS: + appcheck: e1ab9d4e03736f03e0401554a134d1ed502d7629 + coinbase_wallet_sdk: 7ccd4e1a7940deba6ba9bd81beece999a2268c15 + CoinbaseWalletSDK: ea1f37512bbc69ebe07416e3b29bf840f5cc3152 + connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1 + +PODFILE CHECKSUM: 0772a2bd8cd4c7aaeb2576ddfaf6b03be722593b + +COCOAPODS: 1.15.2 diff --git a/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.pbxproj b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6437c7a --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,859 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0964B3132C49545400AE1CDA /* Info-internal.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0964B3122C49545400AE1CDA /* Info-internal.plist */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 92B107A01DA9BFE13AE94BEF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51D7456F29714BCA579AE584 /* Pods_Runner.framework */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0389ADBF49B36FF8EF3FF10F /* Pods-Runner.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-production.xcconfig"; sourceTree = ""; }; + 0964B30E2C494D2000AE1CDA /* Debug-production.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Debug-production.xcconfig"; path = "Flutter/Debug-production.xcconfig"; sourceTree = ""; }; + 0964B30F2C494D2000AE1CDA /* Release-internal.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Release-internal.xcconfig"; path = "Flutter/Release-internal.xcconfig"; sourceTree = ""; }; + 0964B3102C494D2000AE1CDA /* Debug-internal.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Debug-internal.xcconfig"; path = "Flutter/Debug-internal.xcconfig"; sourceTree = ""; }; + 0964B3112C494D2000AE1CDA /* Release-production.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Release-production.xcconfig"; path = "Flutter/Release-production.xcconfig"; sourceTree = ""; }; + 0964B3122C49545400AE1CDA /* Info-internal.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-internal.plist"; sourceTree = ""; }; + 09969A8C2C73BC9100B14363 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 0B37FE275F6AD47AD7B9F5FD /* Pods-Runner.profile-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-production.xcconfig"; sourceTree = ""; }; + 11FCBDE5DEDE8D525B6B991E /* Pods-Runner.debug-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-internal.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 352BB1B55A21C7D1731084E8 /* Pods-Runner.debug-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-production.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3F79388B1397C876ED2819AC /* Pods-Runner.profile-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-internal.xcconfig"; sourceTree = ""; }; + 51D7456F29714BCA579AE584 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E7AC93372A74CCE50D6F8DB4 /* Pods-Runner.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-internal.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 92B107A01DA9BFE13AE94BEF /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 337F646810BF58BC9EACB5E5 /* Pods */ = { + isa = PBXGroup; + children = ( + 352BB1B55A21C7D1731084E8 /* Pods-Runner.debug-production.xcconfig */, + 11FCBDE5DEDE8D525B6B991E /* Pods-Runner.debug-internal.xcconfig */, + 0389ADBF49B36FF8EF3FF10F /* Pods-Runner.release-production.xcconfig */, + E7AC93372A74CCE50D6F8DB4 /* Pods-Runner.release-internal.xcconfig */, + 0B37FE275F6AD47AD7B9F5FD /* Pods-Runner.profile-production.xcconfig */, + 3F79388B1397C876ED2819AC /* Pods-Runner.profile-internal.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 0964B3102C494D2000AE1CDA /* Debug-internal.xcconfig */, + 0964B30E2C494D2000AE1CDA /* Debug-production.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 0964B30F2C494D2000AE1CDA /* Release-internal.xcconfig */, + 0964B3112C494D2000AE1CDA /* Release-production.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 337F646810BF58BC9EACB5E5 /* Pods */, + E810EF87987B4BB060C2D081 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 09969A8C2C73BC9100B14363 /* Runner.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 0964B3122C49545400AE1CDA /* Info-internal.plist */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + E810EF87987B4BB060C2D081 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 51D7456F29714BCA579AE584 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 1D6CBBAE1D8060C96151B8C3 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + D2DBFBF4E9D3D9BCB7D585CB /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0964B3132C49545400AE1CDA /* Info-internal.plist in Resources */, + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1D6CBBAE1D8060C96151B8C3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; + D2DBFBF4E9D3D9BCB7D585CB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 0964B3082C4944E700AE1CDA /* Debug-internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-internal"; + }; + 0964B3092C4944E700AE1CDA /* Debug-internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-internal"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "Runner/Info-internal.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp Internal"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp.internal; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.walletconnect.flutterdapp.internal"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-internal"; + }; + 0964B30A2C4944EF00AE1CDA /* Release-internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-internal"; + }; + 0964B30B2C4944EF00AE1CDA /* Release-internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-internal"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "Runner/Info-internal.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp Internal"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp.internal; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterdapp.internal"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-internal"; + }; + 0964B30C2C4944F600AE1CDA /* Profile-internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-internal"; + }; + 0964B30D2C4944F600AE1CDA /* Profile-internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-internal"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + "DEVELOPMENT_TEAM[sdk=macosx*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + ENABLE_HARDENED_RUNTIME = NO; + INFOPLIST_FILE = "Runner/Info-internal.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp Internal"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp.internal; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterdapp.internal"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-internal"; + }; + 249021D3217E4FDB00AE95B9 /* Profile-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-production"; + }; + 249021D4217E4FDB00AE95B9 /* Profile-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterdapp 1724090152"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-production"; + }; + 97C147031CF9000F007C117D /* Debug-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-production"; + }; + 97C147041CF9000F007C117D /* Release-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-production"; + }; + 97C147061CF9000F007C117D /* Debug-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.walletconnect.flutterdapp"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-production"; + }; + 97C147071CF9000F007C117D /* Release-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterdapp 1724090152"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-production"; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug-production */, + 0964B3082C4944E700AE1CDA /* Debug-internal */, + 97C147041CF9000F007C117D /* Release-production */, + 0964B30A2C4944EF00AE1CDA /* Release-internal */, + 249021D3217E4FDB00AE95B9 /* Profile-production */, + 0964B30C2C4944F600AE1CDA /* Profile-internal */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-internal"; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug-production */, + 0964B3092C4944E700AE1CDA /* Debug-internal */, + 97C147071CF9000F007C117D /* Release-production */, + 0964B30B2C4944EF00AE1CDA /* Release-internal */, + 249021D4217E4FDB00AE95B9 /* Profile-production */, + 0964B30D2C4944F600AE1CDA /* Profile-internal */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-internal"; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/reown_appkit/example/base/ios/Runner.xcodeproj/xcshareddata/xcschemes/internal.xcscheme b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/xcshareddata/xcschemes/internal.xcscheme new file mode 100644 index 0000000..a6e93f1 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/xcshareddata/xcschemes/internal.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme new file mode 100644 index 0000000..8c0bdda --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/reown_appkit/example/base/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/reown_appkit/example/base/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_appkit/example/base/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_appkit/example/base/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/reown_appkit/example/base/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/reown_appkit/example/base/ios/Runner/AppDelegate.swift b/packages/reown_appkit/example/base/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..805d27d --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/AppDelegate.swift @@ -0,0 +1,98 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + + private static let EVENTS_CHANNEL = "com.walletconnect.flutterdapp/events" + private static let METHODS_CHANNEL = "com.walletconnect.flutterdapp/methods" + + private var eventsChannel: FlutterEventChannel? + private var methodsChannel: FlutterMethodChannel? + var initialLink: String? + + private let linkStreamHandler = LinkStreamHandler() + + override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + GeneratedPluginRegistrant.register(with: self) + + let controller = window.rootViewController as! FlutterViewController + eventsChannel = FlutterEventChannel(name: AppDelegate.EVENTS_CHANNEL, binaryMessenger: controller.binaryMessenger) + eventsChannel?.setStreamHandler(linkStreamHandler) + + methodsChannel = FlutterMethodChannel(name: AppDelegate.METHODS_CHANNEL, binaryMessenger: controller.binaryMessenger) + methodsChannel?.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in + if (call.method == "initialLink") { + if let link = self?.initialLink { + let handled = self?.linkStreamHandler.handleLink(link) + if (handled == true) { + self?.initialLink = nil + } + } + } + }) + + // Add your deep link handling logic here + if let url = launchOptions?[.url] as? URL { + self.initialLink = url.absoluteString + } + + if let userActivityDictionary = launchOptions?[.userActivityDictionary] as? [String: Any], + let userActivity = userActivityDictionary["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity, + userActivity.activityType == NSUserActivityTypeBrowsingWeb { + + handleIncomingUniversalLink(userActivity: userActivity) + } + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return linkStreamHandler.handleLink(url.absoluteString) + } + + override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb { + handleIncomingUniversalLink(userActivity: userActivity) + return true + } + return false + } + + private func handleIncomingUniversalLink(userActivity: NSUserActivity) { + if let url = userActivity.webpageURL { + // Handle the URL, navigate to appropriate screen + print("App launched with Universal Link: \(url.absoluteString)") + let handled = linkStreamHandler.handleLink(url.absoluteString) + if (!handled){ + self.initialLink = url.absoluteString + } + } + } +} + +class LinkStreamHandler: NSObject, FlutterStreamHandler { + var eventSink: FlutterEventSink? + var queuedLinks = [String]() + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + self.eventSink = events + queuedLinks.forEach({ events($0) }) + queuedLinks.removeAll() + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + self.eventSink = nil + return nil + } + + func handleLink(_ link: String) -> Bool { + guard let eventSink = eventSink else { + queuedLinks.append(link) + return false + } + eventSink(link) + return true + } +} diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/100.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/100.png new file mode 100644 index 0000000..1562d69 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/100.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/1024.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/1024.png new file mode 100644 index 0000000..70e8759 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/1024.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/114.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/114.png new file mode 100644 index 0000000..739ca0e Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/114.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/120.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/120.png new file mode 100644 index 0000000..9289ddf Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/120.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/144.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/144.png new file mode 100644 index 0000000..54378b5 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/144.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/152.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/152.png new file mode 100644 index 0000000..b662208 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/152.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/167.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/167.png new file mode 100644 index 0000000..84c6ef8 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/167.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/180.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/180.png new file mode 100644 index 0000000..b51c501 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/180.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/20.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/20.png new file mode 100644 index 0000000..489f465 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/20.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/29.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/29.png new file mode 100644 index 0000000..2391946 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/29.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/40.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/40.png new file mode 100644 index 0000000..619433e Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/40.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/50.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/50.png new file mode 100644 index 0000000..bef95ed Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/50.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/57.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/57.png new file mode 100644 index 0000000..0b66b60 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/57.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/58.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/58.png new file mode 100644 index 0000000..ed6d763 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/58.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/60.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/60.png new file mode 100644 index 0000000..00d81c4 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/60.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/72.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/72.png new file mode 100644 index 0000000..73b1217 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/72.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/76.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/76.png new file mode 100644 index 0000000..78908a0 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/76.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/80.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/80.png new file mode 100644 index 0000000..df0c2bd Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/80.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/87.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/87.png new file mode 100644 index 0000000..d842fb7 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/87.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/Contents.json b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/Contents.json new file mode 100644 index 0000000..4fdf882 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/Contents.json @@ -0,0 +1,158 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/Contents.json b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/reown_appkit/example/base/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/reown_appkit/example/base/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/ios/Runner/Base.lproj/Main.storyboard b/packages/reown_appkit/example/base/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/ios/Runner/Info-internal.plist b/packages/reown_appkit/example/base/ios/Runner/Info-internal.plist new file mode 100644 index 0000000..0db52ba --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Info-internal.plist @@ -0,0 +1,73 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Flutter Dapp Internal + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.walletconnect.flutterdapp.internal + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Flutter Dapp Internal + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.walletconnect.flutterdapp.internal + CFBundleURLSchemes + + wcflutterdapp-internal + + + + CFBundleVersion + 2 + LSApplicationCategoryType + + LSApplicationQueriesSchemes + + wcflutterwallet-internal + walletapp + + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/reown_appkit/example/base/ios/Runner/Info.plist b/packages/reown_appkit/example/base/ios/Runner/Info.plist new file mode 100644 index 0000000..cc81ca1 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Info.plist @@ -0,0 +1,73 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Flutter Dapp + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.walletconnect.flutterdapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Flutter Dapp + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.walletconnect.flutterdapp + CFBundleURLSchemes + + wcflutterdapp + + + + CFBundleVersion + 2 + ITSAppUsesNonExemptEncryption + + LSApplicationCategoryType + + LSApplicationQueriesSchemes + + wcflutterwallet + walletapp + + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/reown_appkit/example/base/ios/Runner/Runner-Bridging-Header.h b/packages/reown_appkit/example/base/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/reown_appkit/example/base/ios/Runner/Runner.entitlements b/packages/reown_appkit/example/base/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..84ca64b --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/Runner.entitlements @@ -0,0 +1,11 @@ + + + + + com.apple.developer.associated-domains + + applinks:lab.web3modal.com + applinks:dev.lab.web3modal.com + + + diff --git a/packages/reown_appkit/example/base/ios/Runner/RunnerProfile-internal.entitlements b/packages/reown_appkit/example/base/ios/Runner/RunnerProfile-internal.entitlements new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/Runner/RunnerProfile-internal.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/reown_appkit/example/base/ios/fastlane/Appfile b/packages/reown_appkit/example/base/ios/fastlane/Appfile new file mode 100644 index 0000000..de1b076 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/fastlane/Appfile @@ -0,0 +1,5 @@ +itc_team_id("123564616") # App Store Connect Team ID +team_id("W5R8AG9K22") # Developer Portal Team ID + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/packages/reown_appkit/example/base/ios/fastlane/Fastfile b/packages/reown_appkit/example/base/ios/fastlane/Fastfile new file mode 100644 index 0000000..ed18ff1 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/fastlane/Fastfile @@ -0,0 +1,142 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" + +default_platform(:ios) + +platform :ios do + + # Helper method to read the changelog + def read_changelog + changelog_file = '../../../../CHANGELOG.md' + changelog = "" + + if File.exist?(changelog_file) + File.open(changelog_file, 'r') do |file| + changelog = file.read + end + + # Split the changelog into entries based on the version header pattern + entries = changelog.split(/^##\s/) + + # Get the latest entry, which is the first one after splitting + latest_entry = entries[1] + + # Re-add the '##' header to the latest entry and remove empty lines + changelog = latest_entry.strip if latest_entry + changelog = changelog.gsub /^$\n/, '' + else + UI.user_error!("CHANGELOG.md file not found") + end + + changelog + end + + lane :release_testflight do |options| + + # Setup the keychain and match to work with CI + setup_ci + + match_password = ENV['MATCH_PASSWORD'] + slack_url = ENV['SLACK_URL'] + app_identifier = ENV['BUNDLE_ID'] + _testflight_url = ENV['TESTFLIGHT_URL'] + + api_key = app_store_connect_api_key( + key_id: options[:app_store_key_id], + issuer_id: options[:apple_issuer_id], + key_content: options[:app_store_connect_key], + duration: 1200, + in_house: false, + ) + + match( + readonly: false, + type: "appstore", + app_identifier: "#{app_identifier}", + git_url: options[:match_git_url], + git_basic_authorization: options[:token], + api_key: api_key, + include_all_certificates: true, + force_for_new_devices: true, + force_for_new_certificates: true, + verbose: true, + ) + + number = latest_testflight_build_number( + app_identifier: "#{app_identifier}", + username: options[:username], + ) + if number && !number.to_s.empty? + new_build_number = number.to_i + 1 + else + # Handle the case where there is no previous build number + new_build_number = 1 + end + + increment_build_number( + build_number: new_build_number, + xcodeproj: "Runner.xcodeproj" + ) + + _flavor = options[:flavor] + + gym( + configuration: "Release-#{_flavor}", + # project: "Runner.xcodeproj", + workspace: "Runner.xcworkspace", + scheme: _flavor, + export_method: "app-store", + clean: true, + xcargs: "PROJECT_ID='#{options[:project_id]}'" + ) + + changelog = read_changelog + + upload_to_testflight( + apple_id: options[:app_id], + app_version: options[:app_version], + build_number: "#{new_build_number}", + app_identifier: "#{app_identifier}", + changelog: changelog, + distribute_external: true, + notify_external_testers: true, + skip_waiting_for_build_processing: false, + groups: ["External Testers"] + ) + + slack( + message: "📱 Flutter Dapp #{options[:app_version]}-#{_flavor} (#{new_build_number}) for 🍎 iOS successfully released!\n\n", + default_payloads: [], + attachment_properties: { + fields: [ + { + title: "CHANGELOG", + value: changelog, + }, + { + title: "LINK", + value: "#{_testflight_url}", + }, + ] + } + ) + + clean_build_artifacts() + + end + +end diff --git a/packages/reown_appkit/example/base/ios/fastlane/README.md b/packages/reown_appkit/example/base/ios/fastlane/README.md new file mode 100644 index 0000000..8b40179 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/fastlane/README.md @@ -0,0 +1,32 @@ +fastlane documentation +---- + +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +```sh +xcode-select --install +``` + +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) + +# Available Actions + +## iOS + +### ios release_testflight + +```sh +[bundle exec] fastlane ios release_testflight +``` + + + +---- + +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/packages/reown_appkit/example/base/ios/fastlane/report.xml b/packages/reown_appkit/example/base/ios/fastlane/report.xml new file mode 100644 index 0000000..f0b30e6 --- /dev/null +++ b/packages/reown_appkit/example/base/ios/fastlane/report.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/lib/main.dart b/packages/reown_appkit/example/base/lib/main.dart new file mode 100644 index 0000000..0464bd7 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/main.dart @@ -0,0 +1,373 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; +import 'package:reown_appkit_dapp/models/page_data.dart'; +import 'package:reown_appkit_dapp/pages/connect_page.dart'; +import 'package:reown_appkit_dapp/pages/pairings_page.dart'; +import 'package:reown_appkit_dapp/pages/sessions_page.dart'; +import 'package:reown_appkit_dapp/utils/constants.dart'; +import 'package:reown_appkit_dapp/utils/crypto/chain_data.dart'; +import 'package:reown_appkit_dapp/utils/crypto/helpers.dart'; +import 'package:reown_appkit_dapp/utils/dart_defines.dart'; +import 'package:reown_appkit_dapp/utils/deep_link_handler.dart'; +import 'package:reown_appkit_dapp/utils/string_constants.dart'; +import 'package:reown_appkit_dapp/widgets/event_widget.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + DeepLinkHandler.initListener(); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: StringConstants.appTitle, + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + bool _initializing = true; + + ReownAppKit? _appKit; + + List _pageDatas = []; + int _selectedIndex = 0; + + @override + void initState() { + super.initState(); + initialize(); + } + + String get _flavor { + String flavor = '-${const String.fromEnvironment('FLUTTER_APP_FLAVOR')}'; + return flavor.replaceAll('-production', ''); + } + + String _universalLink() { + Uri link = Uri.parse('https://lab.web3modal.com/flutter_appkit'); + if (_flavor.isNotEmpty) { + return link + .replace(path: '${link.path}_internal') + .replace(host: 'dev.${link.host}') + .toString(); + } + return link.toString(); + } + + Redirect _constructRedirect() { + return Redirect( + native: 'wcflutterdapp$_flavor://', + // universal: _universalLink(), + // // enable linkMode on Wallet so Dapps can use relay-less connection + // // universal: value must be set on cloud config as well + // linkMode: true, + ); + } + + Future initialize() async { + _appKit = ReownAppKit( + core: ReownCore( + projectId: DartDefines.projectId, + logLevel: LogLevel.error, + ), + metadata: PairingMetadata( + name: 'Flutter Dapp Sample', + description: 'Reown\'s sample dapp with Flutter', + url: _universalLink(), + icons: [ + 'https://images.prismic.io/wallet-connect/65785a56531ac2845a260732_WalletConnect-App-Logo-1024X1024.png' + ], + redirect: _constructRedirect(), + ), + ); + + _appKit!.core.addLogListener(_logListener); + + // Register event handlers + _appKit!.core.relayClient.onRelayClientError.subscribe( + _relayClientError, + ); + _appKit!.core.relayClient.onRelayClientConnect.subscribe(_setState); + _appKit!.core.relayClient.onRelayClientDisconnect.subscribe(_setState); + _appKit!.core.relayClient.onRelayClientMessage.subscribe( + _onRelayMessage, + ); + + _appKit!.onSessionPing.subscribe(_onSessionPing); + _appKit!.onSessionEvent.subscribe(_onSessionEvent); + _appKit!.onSessionUpdate.subscribe(_onSessionUpdate); + _appKit!.onSessionConnect.subscribe(_onSessionConnect); + _appKit!.onSessionAuthResponse.subscribe(_onSessionAuthResponse); + + await _appKit!.init(); + await _registerEventHandlers(); + + DeepLinkHandler.init(_appKit!); + DeepLinkHandler.checkInitialLink(); + + // Loop through all the chain data + for (final ChainMetadata chain in ChainData.allChains) { + // Loop through the events for that chain + for (final event in getChainEvents(chain.type)) { + _appKit!.registerEventHandler( + chainId: chain.chainId, + event: event, + ); + } + } + + setState(() { + _pageDatas = [ + PageData( + page: ConnectPage(appKit: _appKit!), + title: StringConstants.connectPageTitle, + icon: Icons.home, + ), + PageData( + page: PairingsPage(appKit: _appKit!), + title: StringConstants.pairingsPageTitle, + icon: Icons.vertical_align_center_rounded, + ), + PageData( + page: SessionsPage(appKit: _appKit!), + title: StringConstants.sessionsPageTitle, + icon: Icons.workspaces_filled, + ), + ]; + + _initializing = false; + }); + } + + Future _registerEventHandlers() async { + final onLine = _appKit!.core.connectivity.isOnline.value; + if (!onLine) { + await Future.delayed(const Duration(milliseconds: 500)); + _registerEventHandlers(); + return; + } + + // Loop through all the chain data + for (final ChainMetadata chain in ChainData.allChains) { + // Loop through the events for that chain + for (final event in getChainEvents(chain.type)) { + _appKit!.registerEventHandler( + chainId: chain.chainId, + event: event, + ); + } + } + } + + void _onSessionConnect(SessionConnect? event) { + debugPrint('[SampleDapp] _onSessionConnect $event'); + Future.delayed(const Duration(milliseconds: 500), () { + setState(() => _selectedIndex = 2); + }); + } + + void _onSessionAuthResponse(SessionAuthResponse? response) { + debugPrint('[SampleDapp] _onSessionAuthResponse $response'); + if (response?.session != null) { + Future.delayed(const Duration(milliseconds: 500), () { + setState(() => _selectedIndex = 2); + }); + } + } + + void _setState(dynamic args) => setState(() {}); + + void _relayClientError(ErrorEvent? event) { + debugPrint('[SampleDapp] _relayClientError ${event?.error}'); + _setState(''); + } + + @override + void dispose() { + // Unregister event handlers + _appKit!.core.removeLogListener(_logListener); + + _appKit!.core.relayClient.onRelayClientError.unsubscribe( + _relayClientError, + ); + _appKit!.core.relayClient.onRelayClientConnect.unsubscribe(_setState); + _appKit!.core.relayClient.onRelayClientDisconnect.unsubscribe(_setState); + _appKit!.core.relayClient.onRelayClientMessage.unsubscribe( + _onRelayMessage, + ); + + _appKit!.onSessionPing.unsubscribe(_onSessionPing); + _appKit!.onSessionEvent.unsubscribe(_onSessionEvent); + _appKit!.onSessionUpdate.unsubscribe(_onSessionUpdate); + _appKit!.onSessionConnect.subscribe(_onSessionConnect); + _appKit!.onSessionAuthResponse.subscribe(_onSessionAuthResponse); + + super.dispose(); + } + + void _logListener(LogEvent event) { + if (event.level == Level.debug) { + // TODO send to mixpanel + log('${event.message}'); + } else { + debugPrint('${event.message}'); + } + } + + @override + Widget build(BuildContext context) { + if (_initializing) { + return const Center( + child: CircularProgressIndicator( + color: StyleConstants.primaryColor, + ), + ); + } + + final List navRail = []; + if (MediaQuery.of(context).size.width >= Constants.smallScreen) { + navRail.add(_buildNavigationRail()); + } + navRail.add( + Expanded( + child: _pageDatas[_selectedIndex].page, + ), + ); + + return Scaffold( + appBar: AppBar( + title: Text(_pageDatas[_selectedIndex].title), + centerTitle: true, + actions: [ + const Text('Relay '), + CircleAvatar( + radius: 6.0, + backgroundColor: _appKit!.core.relayClient.isConnected + ? Colors.green + : Colors.red, + ), + const SizedBox(width: 16.0), + ], + ), + body: Center( + child: Container( + constraints: BoxConstraints( + maxWidth: Constants.smallScreen.toDouble(), + ), + child: Row( + children: navRail, + ), + ), + ), + bottomNavigationBar: + MediaQuery.of(context).size.width < Constants.smallScreen + ? _buildBottomNavBar() + : null, + ); + } + + Widget _buildBottomNavBar() { + return BottomNavigationBar( + currentIndex: _selectedIndex, + unselectedItemColor: Colors.grey, + selectedItemColor: Colors.indigoAccent, + showUnselectedLabels: true, + type: BottomNavigationBarType.fixed, + // called when one tab is selected + onTap: (index) => setState(() => _selectedIndex = index), + // bottom tab items + items: _pageDatas + .map( + (e) => BottomNavigationBarItem( + icon: Icon(e.icon), + label: e.title, + ), + ) + .toList(), + ); + } + + Widget _buildNavigationRail() { + return NavigationRail( + selectedIndex: _selectedIndex, + onDestinationSelected: (index) => setState(() => _selectedIndex = index), + labelType: NavigationRailLabelType.selected, + destinations: _pageDatas + .map( + (e) => NavigationRailDestination( + icon: Icon(e.icon), + label: Text(e.title), + ), + ) + .toList(), + ); + } + + void _onSessionPing(SessionPing? args) { + debugPrint('[SampleDapp] _onSessionPing $args'); + showDialog( + context: context, + builder: (BuildContext context) { + return EventWidget( + title: StringConstants.receivedPing, + content: 'Topic: ${args!.topic}', + ); + }, + ); + } + + void _onSessionEvent(SessionEvent? args) { + debugPrint('[SampleDapp] _onSessionEvent $args'); + showDialog( + context: context, + builder: (BuildContext context) { + return EventWidget( + title: StringConstants.receivedEvent, + content: + 'Topic: ${args!.topic}\nEvent Name: ${args.name}\nEvent Data: ${args.data}', + ); + }, + ); + } + + void _onSessionUpdate(SessionUpdate? args) { + debugPrint('[SampleDapp] _onSessionUpdate $args'); + } + + void _onRelayMessage(MessageEvent? args) async { + if (args != null) { + try { + final payloadString = await _appKit!.core.crypto.decode( + args.topic, + args.message, + ); + final data = jsonDecode(payloadString ?? '{}') as Map; + debugPrint('[SampleDapp] _onRelayMessage data $data'); + } catch (e) { + debugPrint('[SampleDapp] _onRelayMessage error $e'); + } + } + } +} diff --git a/packages/reown_appkit/example/base/lib/models/accounts.dart b/packages/reown_appkit/example/base/lib/models/accounts.dart new file mode 100644 index 0000000..309725e --- /dev/null +++ b/packages/reown_appkit/example/base/lib/models/accounts.dart @@ -0,0 +1,76 @@ +import 'package:flutter/foundation.dart'; + +class AccountDetails { + final String address; + final String chain; + + const AccountDetails({ + required this.address, + required this.chain, + }); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AccountDetails && + other.address == address && + other.chain == chain; + } + + @override + int get hashCode => address.hashCode ^ chain.hashCode; +} + +class Account { + final int id; + final String name; + final String mnemonic; + final String privateKey; + final List details; + + const Account({ + required this.id, + required this.name, + required this.mnemonic, + required this.privateKey, + required this.details, + }); + + Account copyWith({ + int? id, + String? name, + String? mnemonic, + String? privateKey, + List? details, + }) { + return Account( + id: id ?? this.id, + name: name ?? this.name, + mnemonic: mnemonic ?? this.mnemonic, + privateKey: privateKey ?? this.privateKey, + details: details ?? this.details, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Account && + other.id == id && + other.name == name && + other.mnemonic == mnemonic && + other.privateKey == privateKey && + listEquals(other.details, details); + } + + @override + int get hashCode { + return id.hashCode ^ + name.hashCode ^ + mnemonic.hashCode ^ + privateKey.hashCode ^ + details.hashCode; + } +} diff --git a/packages/reown_appkit/example/base/lib/models/chain_metadata.dart b/packages/reown_appkit/example/base/lib/models/chain_metadata.dart new file mode 100644 index 0000000..834a0ba --- /dev/null +++ b/packages/reown_appkit/example/base/lib/models/chain_metadata.dart @@ -0,0 +1,51 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +enum ChainType { + eip155, + solana, + kadena, + cosmos, + polkadot, +} + +class ChainMetadata { + final String chainId; + final String name; + final String logo; + final bool isTestnet; + final Color color; + final ChainType type; + final List rpc; + + const ChainMetadata({ + required this.chainId, + required this.name, + required this.logo, + this.isTestnet = false, + required this.color, + required this.type, + required this.rpc, + }); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ChainMetadata && + other.chainId == chainId && + other.name == name && + other.logo == logo && + other.isTestnet == isTestnet && + listEquals(other.rpc, rpc); + } + + @override + int get hashCode { + return chainId.hashCode ^ + name.hashCode ^ + logo.hashCode ^ + rpc.hashCode ^ + isTestnet.hashCode; + } +} diff --git a/packages/reown_appkit/example/base/lib/models/eth/ethereum_sign_message.dart b/packages/reown_appkit/example/base/lib/models/eth/ethereum_sign_message.dart new file mode 100644 index 0000000..f5dda50 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/models/eth/ethereum_sign_message.dart @@ -0,0 +1,19 @@ +enum WCSignType { + message, + personalMessage, + typedMessageV2, + typedMessageV3, + typedMessageV4, +} + +class EthereumSignMessage { + final String data; + final String address; + final WCSignType type; + + const EthereumSignMessage({ + required this.data, + required this.address, + required this.type, + }); +} diff --git a/packages/reown_appkit/example/base/lib/models/page_data.dart b/packages/reown_appkit/example/base/lib/models/page_data.dart new file mode 100644 index 0000000..1a9f4e4 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/models/page_data.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class PageData { + Widget page; + String title; + IconData icon; + + PageData({ + required this.page, + required this.title, + required this.icon, + }); +} diff --git a/packages/reown_appkit/example/base/lib/models/solana/solana_sign_transaction.dart b/packages/reown_appkit/example/base/lib/models/solana/solana_sign_transaction.dart new file mode 100644 index 0000000..294aded --- /dev/null +++ b/packages/reown_appkit/example/base/lib/models/solana/solana_sign_transaction.dart @@ -0,0 +1,72 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'solana_sign_transaction.g.dart'; + +@JsonSerializable(includeIfNull: false) +class SolanaSignTransaction { + final String feePayer; + final String recentBlockhash; + final List instructions; + + SolanaSignTransaction({ + required this.feePayer, + required this.recentBlockhash, + required this.instructions, + }); + + factory SolanaSignTransaction.fromJson(Map json) => + _$SolanaSignTransactionFromJson(json); + + Map toJson() => _$SolanaSignTransactionToJson(this); + + @override + String toString() { + return 'SolanaSignTransaction(feePayer: $feePayer, recentBlockhash: $recentBlockhash, instructions: $instructions)'; + } +} + +@JsonSerializable(includeIfNull: false) +class SolanaInstruction { + final String programId; + final List keys; + final String data; + + SolanaInstruction({ + required this.programId, + required this.keys, + required this.data, + }); + + factory SolanaInstruction.fromJson(Map json) => + _$SolanaInstructionFromJson(json); + + Map toJson() => _$SolanaInstructionToJson(this); + + @override + String toString() { + return 'SolanaInstruction(programId: $programId, keys: $keys, data: $data)'; + } +} + +@JsonSerializable(includeIfNull: false) +class SolanaKeyMetadata { + final String pubkey; + final bool isSigner; + final bool isWritable; + + SolanaKeyMetadata({ + required this.pubkey, + required this.isSigner, + required this.isWritable, + }); + + factory SolanaKeyMetadata.fromJson(Map json) => + _$SolanaKeyMetadataFromJson(json); + + Map toJson() => _$SolanaKeyMetadataToJson(this); + + @override + String toString() { + return 'SolanaKeyMetadata(pubkey: $pubkey, isSigner: $isSigner, isWritable: $isWritable)'; + } +} diff --git a/packages/reown_appkit/example/base/lib/models/solana/solana_sign_transaction.g.dart b/packages/reown_appkit/example/base/lib/models/solana/solana_sign_transaction.g.dart new file mode 100644 index 0000000..ada6a96 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/models/solana/solana_sign_transaction.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'solana_sign_transaction.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SolanaSignTransaction _$SolanaSignTransactionFromJson( + Map json) => + SolanaSignTransaction( + feePayer: json['feePayer'] as String, + recentBlockhash: json['recentBlockhash'] as String, + instructions: (json['instructions'] as List) + .map((e) => SolanaInstruction.fromJson(e as Map)) + .toList(), + ); + +Map _$SolanaSignTransactionToJson( + SolanaSignTransaction instance) => + { + 'feePayer': instance.feePayer, + 'recentBlockhash': instance.recentBlockhash, + 'instructions': instance.instructions, + }; + +SolanaInstruction _$SolanaInstructionFromJson(Map json) => + SolanaInstruction( + programId: json['programId'] as String, + keys: (json['keys'] as List) + .map((e) => SolanaKeyMetadata.fromJson(e as Map)) + .toList(), + data: json['data'] as String, + ); + +Map _$SolanaInstructionToJson(SolanaInstruction instance) => + { + 'programId': instance.programId, + 'keys': instance.keys, + 'data': instance.data, + }; + +SolanaKeyMetadata _$SolanaKeyMetadataFromJson(Map json) => + SolanaKeyMetadata( + pubkey: json['pubkey'] as String, + isSigner: json['isSigner'] as bool, + isWritable: json['isWritable'] as bool, + ); + +Map _$SolanaKeyMetadataToJson(SolanaKeyMetadata instance) => + { + 'pubkey': instance.pubkey, + 'isSigner': instance.isSigner, + 'isWritable': instance.isWritable, + }; diff --git a/packages/reown_appkit/example/base/lib/pages/connect_page.dart b/packages/reown_appkit/example/base/lib/pages/connect_page.dart new file mode 100644 index 0000000..cdb4826 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/pages/connect_page.dart @@ -0,0 +1,582 @@ +import 'dart:async'; + +import 'package:fl_toast/fl_toast.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; +import 'package:reown_appkit_dapp/utils/constants.dart'; +import 'package:reown_appkit_dapp/utils/crypto/chain_data.dart'; +import 'package:reown_appkit_dapp/utils/crypto/eip155.dart'; +import 'package:reown_appkit_dapp/utils/crypto/polkadot.dart'; +import 'package:reown_appkit_dapp/utils/crypto/solana.dart'; +import 'package:reown_appkit_dapp/utils/sample_wallets.dart'; +import 'package:reown_appkit_dapp/utils/string_constants.dart'; +import 'package:reown_appkit_dapp/widgets/chain_button.dart'; + +class ConnectPage extends StatefulWidget { + const ConnectPage({ + super.key, + required this.appKit, + }); + + final ReownAppKit appKit; + + @override + ConnectPageState createState() => ConnectPageState(); +} + +class ConnectPageState extends State { + bool _testnetOnly = false; + final List _selectedChains = []; + bool _shouldDismissQrCode = true; + + @override + void initState() { + super.initState(); + widget.appKit.onSessionConnect.subscribe(_onSessionConnect); + } + + @override + void dispose() { + widget.appKit.onSessionConnect.unsubscribe(_onSessionConnect); + super.dispose(); + } + + void _selectChain(ChainMetadata chain) { + setState(() { + if (_selectedChains.contains(chain)) { + _selectedChains.remove(chain); + } else { + _selectedChains.add(chain); + } + _updateNamespaces(); + }); + } + + Map requiredNamespaces = {}; + Map optionalNamespaces = {}; + + void _updateNamespaces() { + optionalNamespaces = {}; + + final evmChains = + _selectedChains.where((e) => e.type == ChainType.eip155).toList(); + if (evmChains.isNotEmpty) { + optionalNamespaces['eip155'] = RequiredNamespace( + chains: evmChains.map((c) => c.chainId).toList(), + methods: EIP155.methods.values.toList(), + events: EIP155.events.values.toList(), + ); + } + + final solanaChains = + _selectedChains.where((e) => e.type == ChainType.solana).toList(); + if (solanaChains.isNotEmpty) { + optionalNamespaces['solana'] = RequiredNamespace( + chains: solanaChains.map((c) => c.chainId).toList(), + methods: Solana.methods.values.toList(), + events: Solana.events.values.toList(), + ); + } + + final polkadotChains = + _selectedChains.where((e) => e.type == ChainType.polkadot).toList(); + if (polkadotChains.isNotEmpty) { + optionalNamespaces['polkadot'] = RequiredNamespace( + chains: polkadotChains.map((c) => c.chainId).toList(), + methods: Polkadot.methods.values.toList(), + events: Polkadot.events.values.toList(), + ); + } + + if (optionalNamespaces.isEmpty) { + requiredNamespaces = {}; + } else { + // WalletConnectModal still requires to have requiredNamespaces + // this has to be changed in that SDK + requiredNamespaces = { + 'eip155': const RequiredNamespace( + chains: ['eip155:1'], + methods: ['personal_sign', 'eth_signTransaction'], + events: ['chainChanged'], + ), + }; + } + } + + @override + Widget build(BuildContext context) { + // Build the list of chain buttons, clear if the textnet changed + final testChains = ChainData.allChains.where((e) => e.isTestnet).toList(); + final mainChains = ChainData.allChains.where((e) => !e.isTestnet).toList(); + final List chains = _testnetOnly ? testChains : mainChains; + + final List evmChainButtons = []; + final List nonEvmChainButtons = []; + + final evmChains = chains.where((e) => e.type == ChainType.eip155); + final nonEvmChains = chains.where((e) => e.type != ChainType.eip155); + + for (final ChainMetadata chain in evmChains) { + // Build the button + evmChainButtons.add( + ChainButton( + chain: chain, + onPressed: () => _selectChain(chain), + selected: _selectedChains.contains(chain), + ), + ); + } + + for (final ChainMetadata chain in nonEvmChains) { + // Build the button + nonEvmChainButtons.add( + ChainButton( + chain: chain, + onPressed: () => _selectChain(chain), + selected: _selectedChains.contains(chain), + ), + ); + } + + return ListView( + padding: const EdgeInsets.symmetric(horizontal: StyleConstants.linear8), + children: [ + const Text( + 'Flutter Dapp', + style: StyleConstants.subtitleText, + textAlign: TextAlign.center, + ), + const SizedBox(height: StyleConstants.linear8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + StringConstants.testnetsOnly, + style: StyleConstants.buttonText, + ), + Switch( + value: _testnetOnly, + onChanged: (value) { + setState(() { + _selectedChains.clear(); + _testnetOnly = value; + }); + }, + ), + ], + ), + const Text('EVM Chains:', style: StyleConstants.buttonText), + const SizedBox(height: StyleConstants.linear8), + Wrap( + spacing: 10.0, + children: evmChainButtons, + ), + const Divider(), + const Text('Non EVM Chains:', style: StyleConstants.buttonText), + Wrap( + spacing: 10.0, + children: nonEvmChainButtons, + ), + const SizedBox(height: StyleConstants.linear8), + const Divider(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: StyleConstants.linear8), + const Text( + 'Connect Session Propose:', + style: StyleConstants.buttonText, + ), + const SizedBox(height: StyleConstants.linear8), + Column( + children: WCSampleWallets.getSampleWallets().map((wallet) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: ElevatedButton( + style: _buttonStyle, + onPressed: _selectedChains.isEmpty + ? null + : () { + _onConnect( + nativeLink: '${wallet['schema']}', + closeModal: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(); + } + }, + showToast: (m) async { + showPlatformToast( + child: Text(m), + context: context, + ); + }, + ); + }, + child: Text( + '${wallet['name']}', + style: StyleConstants.buttonText, + ), + ), + ); + }).toList(), + ), + const SizedBox(height: StyleConstants.linear8), + const Divider(), + const Text( + '1-Click Auth with LinkMode:', + style: StyleConstants.buttonText, + ), + const SizedBox(height: StyleConstants.linear8), + Column( + children: WCSampleWallets.getSampleWallets().map((wallet) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: ElevatedButton( + style: _buttonStyle, + onPressed: _selectedChains.isEmpty + ? null + : () { + _sessionAuthenticate( + nativeLink: '${wallet['schema']}', + universalLink: '${wallet['universal']}', + closeModal: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(); + } + }, + showToast: (message) { + showPlatformToast( + child: Text(message), + context: context, + ); + }, + ); + }, + child: Text( + '${wallet['name']}', + style: StyleConstants.buttonText, + ), + ), + ); + }).toList(), + ), + ], + ), + const SizedBox(height: StyleConstants.linear16), + const Divider(height: 1.0), + const SizedBox(height: StyleConstants.linear16), + const Text( + 'Redirect:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Native: '), + Expanded( + child: Text( + '${widget.appKit.metadata.redirect?.native}', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Universal: '), + Expanded( + child: Text( + '${widget.appKit.metadata.redirect?.universal}', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + ], + ), + Row( + children: [ + const Text('Link Mode: '), + Text( + '${widget.appKit.metadata.redirect?.linkMode}', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: StyleConstants.linear8), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox.shrink(); + } + final v = snapshot.data!.version; + final b = snapshot.data!.buildNumber; + const f = String.fromEnvironment('FLUTTER_APP_FLAVOR'); + // return Text('App Version: $v-$f ($b) - SDK v$packageVersion'); + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('App Version: '), + Expanded( + child: Text( + '$v-$f ($b) - SDK v$packageVersion', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + ], + ); + }, + ), + const SizedBox(height: StyleConstants.linear16), + ], + ); + } + + Future _onConnect({ + required String nativeLink, + VoidCallback? closeModal, + Function(String message)? showToast, + }) async { + debugPrint('[SampleDapp] Creating connection with $nativeLink'); + // It is currently safer to send chains approvals on optionalNamespaces + // but depending on Wallet implementation you may need to send some (for innstance eip155:1) as required + final connectResponse = await widget.appKit.connect( + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + ); + + try { + final encodedUri = Uri.encodeComponent(connectResponse.uri.toString()); + final uri = '$nativeLink?uri=$encodedUri'; + await ReownCoreUtils.openURL(uri); + } catch (e) { + _showQrCode(connectResponse.uri.toString()); + } + + debugPrint('[SampleDapp] Awaiting session proposal settlement'); + try { + await connectResponse.session.future; + showToast?.call(StringConstants.connectionEstablished); + } on JsonRpcError catch (e) { + showToast?.call(e.message.toString()); + } + closeModal?.call(); + } + + void _sessionAuthenticate({ + required String nativeLink, + required String universalLink, + VoidCallback? closeModal, + Function(String message)? showToast, + }) async { + debugPrint( + '[SampleDapp] Creating authenticate with $nativeLink, $universalLink'); + final methods1 = requiredNamespaces['eip155']?.methods ?? []; + final methods2 = optionalNamespaces['eip155']?.methods ?? []; + final authResponse = await widget.appKit.authenticate( + params: SessionAuthRequestParams( + chains: _selectedChains.map((e) => e.chainId).toList(), + domain: Uri.parse(widget.appKit.metadata.url).authority, + nonce: AuthUtils.generateNonce(), + uri: widget.appKit.metadata.url, + statement: 'Welcome to example flutter app', + methods: {...methods1, ...methods2}.toList(), + ), + walletUniversalLink: universalLink, + ); + + debugPrint('[SampleDapp] authResponse.uri ${authResponse.uri}'); + try { + // If response uri is not universalLink show QR Code + if (authResponse.uri?.authority != Uri.parse(universalLink).authority) { + _showQrCode('${authResponse.uri}', walletScheme: nativeLink); + } else { + await ReownCoreUtils.openURL(authResponse.uri.toString()); + } + } catch (e) { + debugPrint('[SampleDapp] authResponse error $e'); + _showQrCode('${authResponse.uri}', walletScheme: nativeLink); + } + + try { + debugPrint('[SampleDapp] Awaiting 1-CA session'); + final response = await authResponse.completer.future; + + if (response.session != null) { + showToast?.call( + '${StringConstants.authSucceeded} and ${StringConstants.connectionEstablished}', + ); + } else { + final error = response.error ?? response.jsonRpcError; + showToast?.call(error.toString()); + } + } catch (e) { + debugPrint('[SampleDapp] 1-CA $e'); + showToast?.call(StringConstants.connectionFailed); + } + closeModal?.call(); + } + + Future _showQrCode(String uri, {String walletScheme = ''}) async { + // Show the QR code + debugPrint('[SampleDapp] Showing QR Code: $uri'); + _shouldDismissQrCode = true; + if (kIsWeb) { + await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + insetPadding: const EdgeInsets.all(0.0), + contentPadding: const EdgeInsets.all(0.0), + backgroundColor: Colors.white, + content: SizedBox( + width: 400.0, + child: AspectRatio( + aspectRatio: 0.8, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: _QRCodeView( + uri: uri, + ), + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ) + ], + ); + }, + ); + _shouldDismissQrCode = false; + return; + } + Navigator.push( + context, + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => QRCodeScreen( + uri: uri, + walletScheme: walletScheme, + ), + ), + ); + } + + void _onSessionConnect(SessionConnect? event) async { + if (event == null) return; + + setState(() => _selectedChains.clear()); + + if (_shouldDismissQrCode && Navigator.canPop(context)) { + _shouldDismissQrCode = false; + Navigator.pop(context); + } + } + + ButtonStyle get _buttonStyle => ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return StyleConstants.grayColor; + } + return StyleConstants.primaryColor; + }, + ), + minimumSize: MaterialStateProperty.all(const Size( + 1000.0, + StyleConstants.linear48, + )), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + StyleConstants.linear8, + ), + ), + ), + ); +} + +class QRCodeScreen extends StatefulWidget { + const QRCodeScreen({ + super.key, + required this.uri, + this.walletScheme = '', + }); + final String uri; + final String walletScheme; + + @override + State createState() => _QRCodeScreenState(); +} + +class _QRCodeScreenState extends State { + @override + Widget build(BuildContext context) { + return Material( + child: Scaffold( + appBar: AppBar(title: const Text(StringConstants.scanQrCode)), + body: _QRCodeView( + uri: widget.uri, + walletScheme: widget.walletScheme, + ), + ), + ); + } +} + +class _QRCodeView extends StatelessWidget { + const _QRCodeView({ + required this.uri, + this.walletScheme = '', + }); + final String uri; + final String walletScheme; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView(data: uri), + const SizedBox( + height: StyleConstants.linear16, + ), + ElevatedButton( + onPressed: () { + Clipboard.setData( + ClipboardData(text: uri.toString()), + ).then( + (_) => showPlatformToast( + child: const Text(StringConstants.copiedToClipboard), + context: context, + ), + ); + }, + child: const Text('Copy URL to Clipboard'), + ), + Visibility( + visible: walletScheme.isNotEmpty, + child: ElevatedButton( + onPressed: () async { + final encodedUri = Uri.encodeComponent(uri); + await ReownCoreUtils.openURL('$walletScheme?uri=$encodedUri'); + }, + child: const Text('Open Test Wallet'), + ), + ), + ], + ); + } +} diff --git a/packages/reown_appkit/example/base/lib/pages/pairings_page.dart b/packages/reown_appkit/example/base/lib/pages/pairings_page.dart new file mode 100644 index 0000000..a3db3e0 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/pages/pairings_page.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_dapp/utils/constants.dart'; +import 'package:reown_appkit_dapp/utils/string_constants.dart'; +import 'package:reown_appkit_dapp/widgets/pairing_item.dart'; + +class PairingsPage extends StatefulWidget { + const PairingsPage({ + super.key, + required this.appKit, + }); + + final ReownAppKit appKit; + + @override + PairingsPageState createState() => PairingsPageState(); +} + +class PairingsPageState extends State { + List _pairings = []; + + @override + void initState() { + _pairings = widget.appKit.pairings.getAll(); + // widget.appKit.onSessionDelete.subscribe(_onSessionDelete); + widget.appKit.core.pairing.onPairingDelete.subscribe(_onPairingDelete); + widget.appKit.core.pairing.onPairingExpire.subscribe(_onPairingDelete); + super.initState(); + } + + @override + void dispose() { + // widget.appKit.onSessionDelete.unsubscribe(_onSessionDelete); + widget.appKit.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); + widget.appKit.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final List pairingItems = _pairings + .map( + (PairingInfo pairing) => PairingItem( + key: ValueKey(pairing.topic), + pairing: pairing, + onTap: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text( + StringConstants.deletePairing, + style: StyleConstants.titleText, + ), + content: Text( + pairing.topic, + ), + actions: [ + TextButton( + child: const Text( + StringConstants.cancel, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text( + StringConstants.delete, + ), + onPressed: () async { + try { + widget.appKit.core.pairing.disconnect( + topic: pairing.topic, + ); + Navigator.of(context).pop(); + } catch (e) { + debugPrint('[SampleDapp] ${e.toString()}'); + } + }, + ), + ], + ); + }, + ); + }, + ), + ) + .toList(); + + return Center( + child: Container( + constraints: const BoxConstraints( + maxWidth: StyleConstants.maxWidth, + ), + child: ListView( + padding: const EdgeInsets.all(0.0), + children: pairingItems, + ), + ), + ); + } + + void _onPairingDelete(PairingEvent? event) { + setState(() { + _pairings = widget.appKit.pairings.getAll(); + }); + } +} diff --git a/packages/reown_appkit/example/base/lib/pages/sessions_page.dart b/packages/reown_appkit/example/base/lib/pages/sessions_page.dart new file mode 100644 index 0000000..3b23b9d --- /dev/null +++ b/packages/reown_appkit/example/base/lib/pages/sessions_page.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_dapp/utils/constants.dart'; +import 'package:reown_appkit_dapp/utils/string_constants.dart'; +import 'package:reown_appkit_dapp/widgets/session_item.dart'; +import 'package:reown_appkit_dapp/widgets/session_widget.dart'; + +class SessionsPage extends StatefulWidget { + const SessionsPage({ + super.key, + required this.appKit, + }); + + final ReownAppKit appKit; + + @override + SessionsPageState createState() => SessionsPageState(); +} + +class SessionsPageState extends State { + Map _activeSessions = {}; + String _selectedSession = ''; + + @override + void initState() { + _activeSessions = widget.appKit.getActiveSessions(); + widget.appKit.onSessionDelete.subscribe(_onSessionDelete); + widget.appKit.onSessionExpire.subscribe(_onSessionExpire); + super.initState(); + } + + @override + void dispose() { + widget.appKit.onSessionDelete.unsubscribe(_onSessionDelete); + widget.appKit.onSessionExpire.unsubscribe(_onSessionExpire); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final List sessions = _activeSessions.values.toList(); + return Center( + child: Container( + constraints: const BoxConstraints( + maxWidth: StyleConstants.maxWidth, + ), + child: Align( + alignment: Alignment.topCenter, + child: SingleChildScrollView( + child: ExpansionPanelList( + elevation: 0.0, + materialGapSize: 0.0, + expansionCallback: (int index, bool isExpanded) { + setState(() { + _selectedSession = !isExpanded ? '' : sessions[index].topic; + }); + }, + children: sessions + .map( + (session) => ExpansionPanel( + canTapOnHeader: true, + isExpanded: _selectedSession == session.topic, + backgroundColor: Colors.blue.withOpacity(0.2), + headerBuilder: (context, isExpanded) { + return SessionItem( + key: ValueKey(session.topic), + session: session, + ); + }, + body: Container( + height: MediaQuery.of(context).size.height - 300.0, + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: _buildSessionView(), + ), + ), + ) + .toList(), + ), + ), + ), + ), + ); + } + + Widget _buildSessionView() { + if (_selectedSession == '') { + return const Center( + child: Text( + StringConstants.noSessionSelected, + style: StyleConstants.titleText, + ), + ); + } + + final SessionData session = _activeSessions[_selectedSession]!; + + return SessionWidget( + appKit: widget.appKit, + session: session, + ); + } + + void _onSessionDelete(SessionDelete? event) { + setState(() { + if (event!.topic == _selectedSession) { + _selectedSession = ''; + } + _activeSessions = widget.appKit.getActiveSessions(); + }); + } + + void _onSessionExpire(SessionExpire? event) { + setState(() { + if (event!.topic == _selectedSession) { + _selectedSession = ''; + } + _activeSessions = widget.appKit.getActiveSessions(); + }); + } +} diff --git a/packages/reown_appkit/example/base/lib/utils/constants.dart b/packages/reown_appkit/example/base/lib/utils/constants.dart new file mode 100644 index 0000000..a17612d --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/constants.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class Constants { + static const smallScreen = 640; +} + +class StyleConstants { + static const Color primaryColor = Color.fromARGB(255, 16, 165, 206); + static const Color secondaryColor = Color(0xFF1A1A1A); + static const Color grayColor = Color.fromARGB(255, 180, 180, 180); + static const Color titleTextColor = Color(0xFFFFFFFF); + + // Linear + static const double linear8 = 8; + static const double linear16 = 16; + static const double linear24 = 24; + static const double linear32 = 32; + static const double linear48 = 48; + static const double linear56 = 56; + static const double linear72 = 72; + static const double linear80 = 80; + + // Magic Number + static const double magic10 = 10; + static const double magic14 = 14; + static const double magic20 = 20; + static const double magic40 = 28; + static const double magic64 = 64; + + // Width + static const double maxWidth = 500; + + // Text styles + static const TextStyle titleText = TextStyle( + color: Colors.black, + fontSize: magic40, + fontWeight: FontWeight.w600, + ); + static const TextStyle subtitleText = TextStyle( + color: Colors.black, + fontSize: linear24, + fontWeight: FontWeight.w600, + ); + static const TextStyle paragraph = TextStyle( + color: Colors.black, + fontSize: linear16, + fontWeight: FontWeight.w600, + ); + static const TextStyle buttonText = TextStyle( + color: Colors.black, + fontSize: magic14, + fontWeight: FontWeight.w600, + ); +} diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/chain_data.dart b/packages/reown_appkit/example/base/lib/utils/crypto/chain_data.dart new file mode 100644 index 0000000..b6b4396 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/crypto/chain_data.dart @@ -0,0 +1,199 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; + +class ChainData { + static final List eip155Chains = [ + ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:1', + name: 'Ethereum', + logo: '/chain-logos/eip155-1.png', + color: Colors.blue.shade300, + rpc: ['https://eth.drpc.org'], + ), + ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:137', + name: 'Polygon', + logo: '/chain-logos/eip155-137.png', + color: Colors.purple.shade300, + rpc: ['https://polygon-rpc.com/'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:42161', + name: 'Arbitrum', + logo: '/chain-logos/eip155-42161.png', + color: Colors.blue, + rpc: ['https://arbitrum.blockpi.network/v1/rpc/public'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:10', + name: 'OP Mainnet', + logo: '/chain-logos/eip155-10.png', + color: Colors.red, + rpc: ['https://mainnet.optimism.io/'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:43114', + name: 'Avalanche', + logo: '/chain-logos/eip155-43114.png', + color: Colors.orange, + rpc: ['https://api.avax.network/ext/bc/C/rpc'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:56', + name: 'BNB Smart Chain Mainnet', + logo: '/chain-logos/eip155-56.png', + color: Colors.orange, + rpc: ['https://bsc-dataseed1.bnbchain.org'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:42220', + name: 'Celo', + logo: '/chain-logos/eip155-42220.png', + color: Colors.green, + rpc: ['https://forno.celo.org/'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:100', + name: 'Gnosis', + logo: '/chain-logos/eip155-100.png', + color: Colors.greenAccent, + rpc: ['https://rpc.gnosischain.com/'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:324', + name: 'zkSync', + logo: '/chain-logos/eip155-324.png', + color: Colors.black, + rpc: ['https://mainnet.era.zksync.io'], + ), + ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:11155111', + name: 'Sepolia', + logo: '/chain-logos/eip155-1.png', + color: Colors.blue.shade300, + isTestnet: true, + rpc: ['https://ethereum-sepolia.publicnode.com'], + ), + ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:80001', + name: 'Polygon Mumbai', + logo: '/chain-logos/eip155-137.png', + color: Colors.purple.shade300, + isTestnet: true, + rpc: ['https://matic-mumbai.chainstacklabs.com'], + ), + ]; + + static final List solanaChains = [ + const ChainMetadata( + type: ChainType.solana, + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana Mainnet', + logo: '/chain-logos/solana.png', + color: Color.fromARGB(255, 247, 0, 255), + rpc: ['https://api.mainnet-beta.solana.com'], + ), + const ChainMetadata( + type: ChainType.solana, + chainId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + name: 'Solana Devnet', + logo: '/chain-logos/solana.png', + color: Color.fromARGB(255, 247, 0, 255), + rpc: ['https://api.devnet.solana.com'], + ), + const ChainMetadata( + type: ChainType.solana, + chainId: 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', + name: 'Solana Testnet', + logo: '/chain-logos/solana.png', + color: Colors.black, + isTestnet: true, + rpc: ['https://api.testnet.solana.com'], + ), + ]; + + static final List cosmosChains = [ + // TODO TO BE SUPPORTED + const ChainMetadata( + type: ChainType.cosmos, + chainId: 'cosmos:cosmoshub-4', + name: 'Cosmos Mainnet', + logo: '/chain-logos/cosmos.png', + color: Colors.purple, + rpc: [ + 'https://cosmos-rpc.polkachu.com:443', + 'https://rpc-cosmoshub-ia.cosmosia.notional.ventures:443', + 'https://rpc.cosmos.network:443', + ], + ), + ]; + + static final List kadenaChains = [ + // TODO TO BE SUPPORTED + const ChainMetadata( + type: ChainType.kadena, + chainId: 'kadena:mainnet01', + name: 'Kadena Mainnet', + logo: '/chain-logos/kadena.png', + color: Colors.green, + rpc: [ + 'https://api.chainweb.com', + ], + ), + const ChainMetadata( + type: ChainType.kadena, + chainId: 'kadena:testnet04', + name: 'Kadena Testnet', + logo: '/chain-logos/kadena.png', + color: Colors.green, + isTestnet: true, + rpc: [ + 'https://api.chainweb.com', + ], + ), + ]; + + static final List polkadotChains = [ + const ChainMetadata( + type: ChainType.polkadot, + chainId: 'polkadot:91b171bb158e2d3848fa23a9f1c25182', + name: 'Polkadot Mainnet', + logo: '/chain-logos/polkadot.png', + color: Color.fromARGB(255, 174, 57, 220), + rpc: [ + 'wss://rpc.polkadot.io', + // 'wss://rpc.matrix.canary.enjin.io' + ], + ), + const ChainMetadata( + type: ChainType.polkadot, + chainId: 'polkadot:e143f23803ac50e8f6f8e62695d1ce9e', + name: 'Polkadot Testnet (Westend)', + logo: '/chain-logos/polkadot.png', + color: Color.fromARGB(255, 174, 57, 220), + isTestnet: true, + rpc: [ + 'wss://westend-rpc.polkadot.io', + ], + ), + ]; + + static final List allChains = [ + ...eip155Chains, + ...solanaChains, + ...polkadotChains, + // ...kadenaChains, + // ...cosmosChains, + ]; +} diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart b/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart new file mode 100644 index 0000000..0556792 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart @@ -0,0 +1,277 @@ +import 'dart:convert'; + +import 'package:eth_sig_util/util/utils.dart'; +import 'package:intl/intl.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; +import 'package:reown_appkit_dapp/utils/crypto/chain_data.dart'; +import 'package:reown_appkit_dapp/utils/smart_contracts.dart'; +import 'package:reown_appkit_dapp/utils/test_data.dart'; + +enum EIP155Methods { + personalSign, + ethSign, + ethSignTransaction, + ethSignTypedData, + ethSendTransaction, +} + +enum EIP155Events { + chainChanged, + accountsChanged, +} + +class EIP155 { + static final Map methods = { + EIP155Methods.personalSign: 'personal_sign', + EIP155Methods.ethSign: 'eth_sign', + EIP155Methods.ethSignTransaction: 'eth_signTransaction', + EIP155Methods.ethSignTypedData: 'eth_signTypedData', + EIP155Methods.ethSendTransaction: 'eth_sendTransaction', + }; + + static final Map events = { + EIP155Events.chainChanged: 'chainChanged', + EIP155Events.accountsChanged: 'accountsChanged', + }; + + static Future callMethod({ + required ReownAppKit appKit, + required String topic, + required String method, + required ChainMetadata chainData, + required String address, + }) { + switch (method) { + case 'personal_sign': + return personalSign( + appKit: appKit, + topic: topic, + chainId: chainData.chainId, + address: address, + message: testSignData, + ); + case 'eth_sign': + return ethSign( + appKit: appKit, + topic: topic, + chainId: chainData.chainId, + address: address, + message: testSignData, + ); + case 'eth_signTypedData': + return ethSignTypedData( + appKit: appKit, + topic: topic, + chainId: chainData.chainId, + address: address, + data: typedData, + ); + case 'eth_signTransaction': + return ethSignTransaction( + appKit: appKit, + topic: topic, + chainId: chainData.chainId, + transaction: Transaction( + from: EthereumAddress.fromHex(address), + to: EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + value: EtherAmount.fromInt(EtherUnit.finney, 12), // == 0.012 + ), + ); + case 'eth_sendTransaction': + return ethSendTransaction( + appKit: appKit, + topic: topic, + chainId: chainData.chainId, + transaction: Transaction( + from: EthereumAddress.fromHex(address), + to: EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + value: EtherAmount.fromInt(EtherUnit.finney, 11), // == 0.011 + ), + ); + default: + throw 'Method unimplemented'; + } + } + + static Future callSmartContract({ + required ReownAppKit appKit, + required String topic, + required String address, + required String action, + }) { + // Create DeployedContract object using contract's ABI and address + final deployedContract = DeployedContract( + ContractAbi.fromJson( + jsonEncode(SepoliaTestContract.readContractAbi), + 'Alfreedoms', + ), + EthereumAddress.fromHex(SepoliaTestContract.contractAddress), + ); + + final sepolia = + ChainData.allChains.firstWhere((e) => e.chainId == 'eip155:11155111'); + + switch (action) { + case 'read': + return readSmartContract( + appKit: appKit, + rpcUrl: sepolia.rpc.first, + contract: deployedContract, + address: address, + ); + case 'write': + return appKit.requestWriteContract( + topic: topic, + chainId: sepolia.chainId, + deployedContract: deployedContract, + functionName: 'transfer', + transaction: Transaction( + from: EthereumAddress.fromHex(address), + ), + parameters: [ + // Recipient + EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + // Amount to Transfer + EtherAmount.fromInt(EtherUnit.finney, 10).getInWei, // == 0.010 + ], + ); + default: + return Future.value(); + } + } + + static Future personalSign({ + required ReownAppKit appKit, + required String topic, + required String chainId, + required String address, + required String message, + }) async { + final bytes = utf8.encode(message); + final encoded = bytesToHex(bytes); + + return await appKit.request( + topic: topic, + chainId: chainId, + request: SessionRequestParams( + method: methods[EIP155Methods.personalSign]!, + params: [encoded, address], + ), + ); + } + + static Future ethSign({ + required ReownAppKit appKit, + required String topic, + required String chainId, + required String address, + required String message, + }) async { + return await appKit.request( + topic: topic, + chainId: chainId, + request: SessionRequestParams( + method: methods[EIP155Methods.ethSign]!, + params: [address, message], + ), + ); + } + + static Future ethSignTypedData({ + required ReownAppKit appKit, + required String topic, + required String chainId, + required String address, + required String data, + }) async { + return await appKit.request( + topic: topic, + chainId: chainId, + request: SessionRequestParams( + method: methods[EIP155Methods.ethSignTypedData]!, + params: [address, data], + ), + ); + } + + static Future ethSignTransaction({ + required ReownAppKit appKit, + required String topic, + required String chainId, + required Transaction transaction, + }) async { + return await appKit.request( + topic: topic, + chainId: chainId, + request: SessionRequestParams( + method: methods[EIP155Methods.ethSignTransaction]!, + params: [transaction.toJson()], + ), + ); + } + + static Future ethSendTransaction({ + required ReownAppKit appKit, + required String topic, + required String chainId, + required Transaction transaction, + }) async { + return await appKit.request( + topic: topic, + chainId: chainId, + request: SessionRequestParams( + method: methods[EIP155Methods.ethSendTransaction]!, + params: [transaction.toJson()], + ), + ); + } + + static Future readSmartContract({ + required ReownAppKit appKit, + required String rpcUrl, + required String address, + required DeployedContract contract, + }) async { + final results = await Future.wait([ + // results[0] + appKit.requestReadContract( + deployedContract: contract, + functionName: 'name', + rpcUrl: rpcUrl, + ), + // results[1] + appKit.requestReadContract( + deployedContract: contract, + functionName: 'totalSupply', + rpcUrl: rpcUrl, + ), + // results[2] + appKit.requestReadContract( + deployedContract: contract, + functionName: 'balanceOf', + rpcUrl: rpcUrl, + parameters: [ + EthereumAddress.fromHex(address), + ], + ), + ]); + + final oCcy = NumberFormat('#,##0.00', 'en_US'); + final name = results[0].first.toString(); + final total = results[1].first / BigInt.from(1000000000000000000); + final balance = results[2].first / BigInt.from(1000000000000000000); + + return { + 'name': name, + 'totalSupply': oCcy.format(total), + 'balance': oCcy.format(balance), + }; + } +} diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/helpers.dart b/packages/reown_appkit/example/base/lib/utils/crypto/helpers.dart new file mode 100644 index 0000000..6ead377 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/crypto/helpers.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; +import 'package:reown_appkit_dapp/utils/crypto/chain_data.dart'; +import 'package:reown_appkit_dapp/utils/crypto/eip155.dart'; +import 'package:reown_appkit_dapp/utils/crypto/polkadot.dart'; +import 'package:reown_appkit_dapp/utils/crypto/solana.dart'; + +String getChainName(String chain) { + try { + return ChainData.allChains + .where((element) => element.chainId == chain) + .first + .name; + } catch (e) { + debugPrint('[SampleDapp] Invalid chain'); + } + return 'Unknown'; +} + +ChainMetadata getChainMetadataFromChain(String chain) { + try { + return ChainData.allChains + .where((element) => element.chainId == chain) + .first; + } catch (e) { + debugPrint('[SampleDapp] Invalid chain'); + } + return ChainData.eip155Chains[0]; +} + +List getChainMethods(ChainType value) { + switch (value) { + case ChainType.eip155: + return EIP155.methods.values.toList(); + case ChainType.solana: + return Solana.methods.values.toList(); + case ChainType.polkadot: + return Polkadot.methods.values.toList(); + default: + return []; + } +} + +List getChainEvents(ChainType value) { + switch (value) { + case ChainType.eip155: + return EIP155.events.values.toList(); + case ChainType.solana: + return Solana.events.values.toList(); + case ChainType.polkadot: + return Polkadot.events.values.toList(); + default: + return []; + } +} diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/kadena.dart b/packages/reown_appkit/example/base/lib/utils/crypto/kadena.dart new file mode 100644 index 0000000..4c6f01c --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/crypto/kadena.dart @@ -0,0 +1,225 @@ +// import 'dart:convert'; + +// import 'package:kadena_dart_sdk/kadena_dart_sdk.dart'; +// import 'package:reown_appkit/reown_appkit.dart'; +// import 'package:reown_appkit_dapp/utils/test_data.dart'; + +// enum KadenaMethods { +// sign, +// quicksign, +// kadenaSignV1, +// kadenaQuicksignV1, +// kadenaGetAccountsV1, +// } + +// enum KadenaEvents { +// none, +// } + +// extension KadenaMethodsX on KadenaMethods { +// String? get value => Kadena.methods[this]; +// } + +// extension KadenaMethodsStringX on String { +// KadenaMethods? toKadenaMethod() { +// final entries = Kadena.methods.entries.where( +// (element) => element.value == this, +// ); +// return (entries.isNotEmpty) ? entries.first.key : null; +// } +// } + +// extension KadenaEventsX on KadenaEvents { +// String? get value => Kadena.events[this]; +// } + +// extension KadenaEventsStringX on String { +// KadenaEvents? toKadenaEvent() { +// final entries = Kadena.events.entries.where( +// (element) => element.value == this, +// ); +// return (entries.isNotEmpty) ? entries.first.key : null; +// } +// } + +// class Kadena { +// static final Map methods = { +// KadenaMethods.sign: 'kadena_sign', +// KadenaMethods.quicksign: 'kadena_quicksign', +// KadenaMethods.kadenaSignV1: 'kadena_sign_v1', +// KadenaMethods.kadenaQuicksignV1: 'kadena_quicksign_v1', +// KadenaMethods.kadenaGetAccountsV1: 'kadena_getAccounts_v1' +// }; + +// static final Map events = {}; + +// static Future callMethod({ +// required ReownAppKit appKit, +// required String topic, +// required KadenaMethods method, +// required String chainId, +// required String address, +// }) { +// final String addressActual = +// address.startsWith('k**') ? address.substring(3) : address; + +// switch (method) { +// case KadenaMethods.sign: +// case KadenaMethods.kadenaSignV1: +// return kadenaSignV1( +// appKit: appKit, +// method: method, +// topic: topic, +// chainId: chainId, +// data: createSignRequest( +// networkId: chainId.split(':')[1], +// signingPubKey: addressActual, +// sender: 'k:$addressActual', +// caps: [ +// DappCapp( +// role: 'Test', +// description: 'description', +// cap: Capability( +// name: 'coin.GAS', +// ), +// ), +// DappCapp( +// role: 'Test', +// description: 'description', +// cap: Capability( +// name: 'coin.TRANSFER', +// args: ['sender', 'receiver', 1.0], +// ), +// ), +// ], +// ), +// ); +// case KadenaMethods.quicksign: +// case KadenaMethods.kadenaQuicksignV1: +// return kadenaQuicksignV1( +// appKit: appKit, +// topic: topic, +// chainId: chainId, +// data: QuicksignRequest( +// commandSigDatas: [ +// CommandSigData( +// cmd: jsonEncode( +// createPactCommandPayload( +// networkId: chainId.split(':')[1], +// sender: 'k:$addressActual', +// signerCaps: [ +// SignerCapabilities( +// pubKey: addressActual, +// clist: [ +// Capability( +// name: 'coin.GAS', +// ), +// Capability( +// name: 'coin.TRANSFER', +// args: ['sender', 'receiver', 1.0], +// ), +// ], +// ), +// ], +// ).toJson(), +// ), +// sigs: [ +// QuicksignSigner( +// pubKey: addressActual, +// ), +// ], +// ), +// CommandSigData( +// cmd: jsonEncode( +// createPactCommandPayload( +// networkId: chainId.split(':')[1], +// sender: 'k:$addressActual', +// signerCaps: [ +// SignerCapabilities( +// pubKey: addressActual, +// clist: [ +// Capability( +// name: 'coin.GAS', +// ), +// Capability( +// name: 'coin.TRANSFER', +// args: ['sender2', 'receiver2', 2.0], +// ), +// ], +// ), +// ], +// ).toJson(), +// ), +// sigs: [ +// QuicksignSigner( +// pubKey: addressActual, +// ), +// ], +// ), +// ], +// ), +// ); + +// case KadenaMethods.kadenaGetAccountsV1: +// return kadenaGetAccountsV1( +// appKit: appKit, +// topic: topic, +// chainId: chainId, +// data: createGetAccountsRequest(account: '$chainId:$addressActual'), +// ); +// } +// } + +// static Future kadenaSignV1({ +// required ReownAppKit appKit, +// required KadenaMethods method, +// required String topic, +// required String chainId, +// required SignRequest data, +// }) async { +// // print(jsonEncode(data)); +// final ret = await appKit.request( +// topic: topic, +// chainId: chainId, +// request: SessionRequestParams( +// method: methods[method]!, +// params: data, +// ), +// ); +// // print('ret: $ret'); +// // print(ret.runtimeType); +// return ret; +// } + +// static Future kadenaQuicksignV1({ +// required ReownAppKit appKit, +// required String topic, +// required String chainId, +// required QuicksignRequest data, +// }) async { +// return await appKit.request( +// topic: topic, +// chainId: chainId, +// request: SessionRequestParams( +// method: methods[KadenaMethods.kadenaQuicksignV1]!, +// params: data.toJson(), +// ), +// ); +// } + +// static Future kadenaGetAccountsV1({ +// required ReownAppKit appKit, +// required String topic, +// required String chainId, +// required GetAccountsRequest data, +// }) async { +// return await appKit.request( +// topic: topic, +// chainId: chainId, +// request: SessionRequestParams( +// method: methods[KadenaMethods.kadenaGetAccountsV1]!, +// params: data.toJson(), +// ), +// ); +// } +// } diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart b/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart new file mode 100644 index 0000000..27eec4f --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart @@ -0,0 +1,82 @@ +import 'package:reown_appkit/reown_appkit.dart'; + +enum PolkadotMethods { + polkadotSignTransaction, + polkadotSignMessage, +} + +enum PolkadotEvents { + none, +} + +class Polkadot { + static final Map methods = { + PolkadotMethods.polkadotSignTransaction: 'polkadot_signTransaction', + PolkadotMethods.polkadotSignMessage: 'polkadot_signMessage' + }; + + static final Map events = {}; + + static Future callMethod({ + required ReownAppKit appKit, + required String topic, + required String method, + required String chainId, + required String address, + }) { + switch (method) { + case 'polkadot_signMessage': + return appKit.request( + topic: topic, + chainId: chainId, + request: SessionRequestParams( + method: method, + params: { + 'address': address, + 'message': + 'This is an example message to be signed - ${DateTime.now()}', + }, + ), + ); + case 'polkadot_signTransaction': + return appKit.request( + topic: topic, + chainId: chainId, + request: SessionRequestParams( + method: method, + params: { + 'address': address, + 'transactionPayload': { + 'specVersion': '0x00002468', + 'transactionVersion': '0x0000000e', + 'address': address, + 'blockHash': + '0x554d682a74099d05e8b7852d19c93b527b5fae1e9e1969f6e1b82a2f09a14cc9', + 'blockNumber': '0x00cb539c', + 'era': '0xc501', + 'genesisHash': + '0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e', + 'method': + '0x0001784920616d207369676e696e672074686973207472616e73616374696f6e21', + 'nonce': '0x00000000', + 'signedExtensions': [ + 'CheckNonZeroSender', + 'CheckSpecVersion', + 'CheckTxVersion', + 'CheckGenesis', + 'CheckMortality', + 'CheckNonce', + 'CheckWeight', + 'ChargeTransactionPayment', + ], + 'tip': '0x00000000000000000000000000000000', + 'version': 4, + }, + }, + ), + ); + default: + throw 'Method unimplemented'; + } + } +} diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart b/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart new file mode 100644 index 0000000..329d83c --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart @@ -0,0 +1,116 @@ +import 'dart:convert'; +import 'package:bs58/bs58.dart'; +import 'package:solana_web3/solana_web3.dart' as solana; + +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; + +enum SolanaMethods { + solanaSignTransaction, + solanaSignMessage, +} + +enum SolanaEvents { + none, +} + +class Solana { + static final Map methods = { + SolanaMethods.solanaSignTransaction: 'solana_signTransaction', + SolanaMethods.solanaSignMessage: 'solana_signMessage' + }; + + static final Map events = {}; + + static Future callMethod({ + required ReownAppKit appKit, + required String topic, + required String method, + required ChainMetadata chainData, + required String address, + bool isV0 = false, + }) async { + switch (method) { + case 'solana_signMessage': + final bytes = utf8.encode( + 'This is an example message to be signed - ${DateTime.now()}', + ); + final message = base58.encode(bytes); + return appKit.request( + topic: topic, + chainId: chainData.chainId, + request: SessionRequestParams( + method: method, + params: { + 'pubkey': address, + 'message': message, + }, + ), + ); + case 'solana_signTransaction': + // Create a connection to the devnet cluster. + final cluster = solana.Cluster.https( + Uri.parse(chainData.rpc.first).authority, + ); + // final cluster = solana.Cluster.devnet; + final connection = solana.Connection(cluster); + + // Fetch the latest blockhash. + final blockhash = await connection.getLatestBlockhash(); + + // Create a System Program instruction to transfer 0.5 SOL from [address1] to [address2]. + final transactionv0 = solana.Transaction.v0( + payer: solana.Pubkey.fromBase58(address), + recentBlockhash: blockhash.blockhash, + instructions: [ + solana.TransactionInstruction.fromJson({ + 'programId': '11111111111111111111111111111111', + 'data': [2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + 'keys': [ + { + 'isSigner': true, + 'isWritable': true, + 'pubkey': address, + }, + { + 'isSigner': false, + 'isWritable': true, + 'pubkey': '8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR' + } + ] + }), + // SystemProgram.transfer( + // fromPubkey: solana.Pubkey.fromBase58(address), + // toPubkey: solana.Pubkey.fromBase58( + // '8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR', + // ), + // lamports: solana.solToLamports(0.5), + // ), + ], + ); + + const config = solana.TransactionSerializableConfig( + verifySignatures: false, + ); + final bytes = transactionv0.serialize(config).asUint8List(); + final encodedV0Trx = base64.encode(bytes); + + return appKit.request( + topic: topic, + chainId: chainData.chainId, + request: SessionRequestParams( + method: method, + params: { + 'transaction': encodedV0Trx, + 'pubkey': address, + 'feePayer': address, + ...transactionv0.message.toJson(), + }, + ), + ); + default: + throw 'Method unimplemented'; + } + } +} diff --git a/packages/reown_appkit/example/base/lib/utils/dart_defines.dart b/packages/reown_appkit/example/base/lib/utils/dart_defines.dart new file mode 100644 index 0000000..d70ca8f --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/dart_defines.dart @@ -0,0 +1,5 @@ +class DartDefines { + static const String projectId = String.fromEnvironment( + 'PROJECT_ID', + ); +} diff --git a/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart b/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart new file mode 100644 index 0000000..7e22515 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart @@ -0,0 +1,60 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class DeepLinkHandler { + static const _methodChannel = MethodChannel( + 'com.walletconnect.flutterdapp/methods', + ); + static const _eventChannel = EventChannel( + 'com.walletconnect.flutterdapp/events', + ); + static final waiting = ValueNotifier(false); + static late IReownAppKit _appKit; + + static void initListener() { + if (kIsWeb) return; + try { + _eventChannel.receiveBroadcastStream().listen( + _onLink, + onError: _onError, + ); + } catch (e) { + debugPrint('[SampleWallet] [DeepLinkHandler] checkInitialLink $e'); + } + } + + static void init(IReownAppKit appKit) { + if (kIsWeb) return; + _appKit = appKit; + } + + static void checkInitialLink() async { + if (kIsWeb) return; + try { + _methodChannel.invokeMethod('initialLink'); + } catch (e) { + debugPrint('[SampleWallet] [DeepLinkHandler] checkInitialLink $e'); + } + } + + static Uri get nativeUri => + Uri.parse(_appKit.metadata.redirect?.native ?? ''); + static Uri get universalUri => + Uri.parse(_appKit.metadata.redirect?.universal ?? ''); + static String get host => universalUri.host; + + static void _onLink(dynamic link) async { + if (link == null) return; + final envelope = ReownCoreUtils.getSearchParamFromURL(link, 'wc_ev'); + if (envelope.isNotEmpty) { + debugPrint('[SampleDapp] is linkMode $link'); + await _appKit.dispatchEnvelope(link); + } + } + + static void _onError(dynamic error) { + debugPrint('[SampleDapp] _onError $error'); + waiting.value = false; + } +} diff --git a/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart b/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart new file mode 100644 index 0000000..e7730c6 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart @@ -0,0 +1,90 @@ +import 'dart:io'; + +class WCSampleWallets { + static List> sampleWalletsInternal() => [ + { + 'name': 'Swift Wallet', + 'platform': ['ios'], + 'id': '123456789012345678901234567890', + 'schema': 'walletapp://', + 'bundleId': 'com.walletconnect.sample.wallet', + 'universal': 'https://lab.web3modal.com/wallet', + }, + { + 'name': 'Flutter Wallet (internal)', + 'platform': ['ios', 'android'], + 'id': '123456789012345678901234567895', + 'schema': 'wcflutterwallet-internal://', + 'bundleId': 'com.walletconnect.flutterwallet.internal', + 'universal': + 'https://dev.lab.web3modal.com/flutter_walletkit_internal', + }, + { + 'name': 'RN Wallet (internal)', + 'platform': ['ios', 'android'], + 'id': '1234567890123456789012345678922', + 'schema': 'rn-web3wallet://wc', + 'bundleId': 'com.walletconnect.web3wallet.rnsample.internal', + 'universal': 'https://lab.web3modal.com/rn_walletkit', + }, + { + 'name': 'Kotlin Wallet (Internal)', + 'platform': ['android'], + 'id': '123456789012345678901234567894', + 'schema': 'kotlin-web3wallet://wc', + 'bundleId': 'com.walletconnect.sample.wallet.internal', + 'universal': + 'https://web3modal-laboratory-git-chore-kotlin-assetlinks-walletconnect1.vercel.app/wallet_internal', + }, + ]; + + static List> sampleWalletsProduction() => [ + { + 'name': 'Swift Wallet', + 'platform': ['ios'], + 'id': '123456789012345678901234567890', + 'schema': 'walletapp://', + 'bundleId': 'com.walletconnect.sample.wallet', + 'universal': 'https://lab.web3modal.com/wallet', + }, + { + 'name': 'Flutter Wallet', + 'platform': ['ios', 'android'], + 'id': '123456789012345678901234567891', + 'schema': 'wcflutterwallet://', + 'bundleId': 'com.walletconnect.flutterwallet', + 'universal': 'https://lab.web3modal.com/flutter_walletkit', + }, + { + 'name': 'RN Wallet', + 'platform': ['ios', 'android'], + 'id': '123456789012345678901234567892', + 'schema': 'rn-web3wallet://wc', + 'bundleId': 'com.walletconnect.web3wallet.rnsample', + 'universal': 'https://lab.web3modal.com/rn_walletkit', + }, + { + 'name': 'Kotlin Wallet', + 'platform': ['android'], + 'id': '123456789012345678901234567893', + 'schema': 'kotlin-web3wallet://wc', + 'bundleId': 'com.walletconnect.sample.wallet', + 'universal': + 'https://web3modal-laboratory-git-chore-kotlin-assetlinks-walletconnect1.vercel.app/wallet_release', + }, + ]; + + static List> getSampleWallets() { + String flavor = '-${const String.fromEnvironment('FLUTTER_APP_FLAVOR')}'; + flavor = flavor.replaceAll('-production', ''); + if (flavor.isNotEmpty) { + return sampleWalletsInternal().where((e) { + return (e['platform'] as List) + .contains(Platform.operatingSystem); + }).toList(); + } + return sampleWalletsProduction().where((e) { + return (e['platform'] as List).contains(Platform.operatingSystem); + }).toList(); + } +} diff --git a/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart b/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart new file mode 100644 index 0000000..9b20505 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart @@ -0,0 +1,279 @@ +class SepoliaTestContract { + // Alfreedoms2 ALF2 in Sepolia + // DEPLOY https://sepolia.etherscan.io/tx/0xebf287281abbc976b7cf6956a7f5f66338935d324c6453a350e3bb42ff7bd4e2 + // MINT https://sepolia.etherscan.io/tx/0x04a015504be7420a40a59936bfcca9302e55700fd00129059444539770fed5e7 + // CONTRACT https://sepolia.etherscan.io/address/0xBe60D05C11BD1C365849C824E0C2D880d2466eAF + // TRANSFERS https://sepolia.etherscan.io/token/0xbe60d05c11bd1c365849c824e0c2d880d2466eaf?a=0x59e2f66C0E96803206B6486cDb39029abAE834c0 + // SOURCIFY https://repo.sourcify.dev/contracts/full_match/11155111/0xBe60D05C11BD1C365849C824E0C2D880d2466eAF/ + static const contractAddress = '0xBe60D05C11BD1C365849C824E0C2D880d2466eAF'; + + static const readContractAbi = [ + { + 'inputs': [ + {'internalType': 'address', 'name': 'initialOwner', 'type': 'address'} + ], + 'stateMutability': 'nonpayable', + 'type': 'constructor' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'allowance', 'type': 'uint256'}, + {'internalType': 'uint256', 'name': 'needed', 'type': 'uint256'} + ], + 'name': 'ERC20InsufficientAllowance', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'sender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'balance', 'type': 'uint256'}, + {'internalType': 'uint256', 'name': 'needed', 'type': 'uint256'} + ], + 'name': 'ERC20InsufficientBalance', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'approver', 'type': 'address'} + ], + 'name': 'ERC20InvalidApprover', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'receiver', 'type': 'address'} + ], + 'name': 'ERC20InvalidReceiver', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'sender', 'type': 'address'} + ], + 'name': 'ERC20InvalidSender', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'} + ], + 'name': 'ERC20InvalidSpender', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'} + ], + 'name': 'OwnableInvalidOwner', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'account', 'type': 'address'} + ], + 'name': 'OwnableUnauthorizedAccount', + 'type': 'error' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'owner', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'spender', + 'type': 'address' + }, + { + 'indexed': false, + 'internalType': 'uint256', + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Approval', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'previousOwner', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'newOwner', + 'type': 'address' + } + ], + 'name': 'OwnershipTransferred', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'from', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'to', + 'type': 'address' + }, + { + 'indexed': false, + 'internalType': 'uint256', + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Transfer', + 'type': 'event' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'}, + {'internalType': 'address', 'name': 'spender', 'type': 'address'} + ], + 'name': 'allowance', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'approve', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'account', 'type': 'address'} + ], + 'name': 'balanceOf', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'decimals', + 'outputs': [ + {'internalType': 'uint8', 'name': '', 'type': 'uint8'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'to', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'mint', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'name', + 'outputs': [ + {'internalType': 'string', 'name': '', 'type': 'string'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'owner', + 'outputs': [ + {'internalType': 'address', 'name': '', 'type': 'address'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'renounceOwnership', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'symbol', + 'outputs': [ + {'internalType': 'string', 'name': '', 'type': 'string'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'totalSupply', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'to', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'transfer', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'from', 'type': 'address'}, + {'internalType': 'address', 'name': 'to', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'transferFrom', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'newOwner', 'type': 'address'} + ], + 'name': 'transferOwnership', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + } + ]; +} diff --git a/packages/reown_appkit/example/base/lib/utils/string_constants.dart b/packages/reown_appkit/example/base/lib/utils/string_constants.dart new file mode 100644 index 0000000..aa2727f --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/string_constants.dart @@ -0,0 +1,40 @@ +class StringConstants { + // General + static const String cancel = 'Cancel'; + static const String close = 'Close'; + static const String ok = 'OK'; + static const String delete = 'Delete'; + + // Main Page + static const String appTitle = 'Reown\'s Flutter Dapp Demo'; + static const String connectPageTitle = 'Connect'; + static const String pairingsPageTitle = 'Pairings'; + static const String sessionsPageTitle = 'Sessions'; + static const String authPageTitle = 'Auth'; + static const String settingsPageTitle = 'Settings'; + static const String receivedPing = 'Received Ping'; + static const String receivedEvent = 'Received Event'; + + // Connect Page + static const String selectChains = 'Select chains:'; + static const String testnetsOnly = 'Testnets only?'; + static const String scanQrCode = 'Scan QR Code'; + static const String copiedToClipboard = 'Copied to clipboard'; + static const String connect = 'Connect'; + static const String connectionEstablished = 'Session established'; + static const String connectionFailed = 'Session setup failed'; + static const String connectionRejected = 'Connection rejected by user'; + static const String authSucceeded = 'Authentication Successful'; + static const String authFailed = 'Authentication Failed'; + + // Pairings Page + static const String pairings = 'Pairings'; + static const String deletePairing = 'Delete Pairing?'; + + // Sessions Page + static const String sessions = 'Sessions'; + static const String noSessionSelected = 'No session selected'; + static const String sessionTopic = 'Session Topic: '; + static const String methods = 'Methods'; + static const String events = 'Events'; +} diff --git a/packages/reown_appkit/example/base/lib/utils/test_data.dart b/packages/reown_appkit/example/base/lib/utils/test_data.dart new file mode 100644 index 0000000..cb78e1d --- /dev/null +++ b/packages/reown_appkit/example/base/lib/utils/test_data.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; + +const String testSignData = 'Test sign data'; +String testSignTypedData(String address) => jsonEncode( + { + 'types': { + 'EIP712Domain': [ + {'name': 'name', 'type': 'string'}, + {'name': 'version', 'type': 'string'}, + {'name': 'chainId', 'type': 'uint256'}, + {'name': 'verifyingContract', 'type': 'address'}, + ], + 'Person': [ + {'name': 'name', 'type': 'string'}, + {'name': 'wallet', 'type': 'address'}, + ], + }, + 'primaryType': 'Mail', + 'domain': { + 'name': 'Ether Mail', + 'version': '1', + 'chainId': 1, + 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + 'message': { + 'from': {'name': 'Cow', 'wallet': address}, + 'to': { + 'name': 'Bob', + 'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' + }, + 'contents': 'Hello, Bob!', + }, + }, + ); + +const typedData = + r'''{"types":{"EIP712Domain":[{"type":"string","name":"name"},{"type":"string","name":"version"},{"type":"uint256","name":"chainId"},{"type":"address","name":"verifyingContract"}],"Part":[{"name":"account","type":"address"},{"name":"value","type":"uint96"}],"Mint721":[{"name":"tokenId","type":"uint256"},{"name":"tokenURI","type":"string"},{"name":"creators","type":"Part[]"},{"name":"royalties","type":"Part[]"}]},"domain":{"name":"Mint721","version":"1","chainId":4,"verifyingContract":"0x2547760120aed692eb19d22a5d9ccfe0f7872fce"},"primaryType":"Mint721","message":{"@type":"ERC721","contract":"0x2547760120aed692eb19d22a5d9ccfe0f7872fce","tokenId":"1","uri":"ipfs://ipfs/hash","creators":[{"account":"0xc5eac3488524d577a1495492599e8013b1f91efa","value":10000}],"royalties":[],"tokenURI":"ipfs://ipfs/hash"}}'''; + +/// KADENA /// + +// SignRequest createSignRequest({ +// required String networkId, +// required String signingPubKey, +// required String sender, +// String code = '"hello"', +// Map? data, +// List caps = const [], +// String chainId = '1', +// int gasLimit = 2000, +// double gasPrice = 1e-8, +// int ttl = 600, +// }) => +// SignRequest( +// code: code, +// data: data ?? {}, +// sender: sender, +// networkId: networkId, +// chainId: chainId, +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// signingPubKey: signingPubKey, +// ttl: ttl, +// caps: caps, +// ); + +// PactCommandPayload createPactCommandPayload({ +// required String networkId, +// required String sender, +// String code = '"hello"', +// Map? data, +// List signerCaps = const [], +// String chainId = '1', +// int gasLimit = 2000, +// double gasPrice = 1e-8, +// int ttl = 600, +// }) => +// PactCommandPayload( +// networkId: networkId, +// payload: CommandPayload( +// exec: ExecMessage( +// code: code, +// data: data ?? {}, +// ), +// ), +// signers: signerCaps, +// meta: CommandMetadata( +// chainId: chainId, +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// ttl: ttl, +// sender: sender, +// ), +// ); + +// QuicksignRequest createQuicksignRequest({ +// required String cmd, +// List sigs = const [], +// }) => +// QuicksignRequest( +// commandSigDatas: [ +// CommandSigData( +// cmd: cmd, +// sigs: sigs, +// ), +// ], +// ); + +// GetAccountsRequest createGetAccountsRequest({ +// required String account, +// }) => +// GetAccountsRequest( +// accounts: [ +// AccountRequest( +// account: account, +// ), +// ], +// ); diff --git a/packages/reown_appkit/example/base/lib/widgets/chain_button.dart b/packages/reown_appkit/example/base/lib/widgets/chain_button.dart new file mode 100644 index 0000000..b923c1e --- /dev/null +++ b/packages/reown_appkit/example/base/lib/widgets/chain_button.dart @@ -0,0 +1,55 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; +import 'package:reown_appkit_dapp/utils/constants.dart'; + +class ChainButton extends StatelessWidget { + const ChainButton({ + super.key, + required this.chain, + required this.onPressed, + this.selected = false, + }); + + final ChainMetadata chain; + final VoidCallback onPressed; + final bool selected; + + @override + Widget build(BuildContext context) { + return Container( + width: (min(Constants.smallScreen - 78.0, + MediaQuery.of(context).size.width) / + 2) - + 14.0, + height: StyleConstants.linear48, + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + child: ElevatedButton( + onPressed: onPressed, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + selected ? Colors.grey.shade400 : Colors.white, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + side: BorderSide( + color: selected ? Colors.grey.shade400 : chain.color, + width: selected ? 4 : 2, + ), + borderRadius: BorderRadius.circular( + StyleConstants.linear8, + ), + ), + ), + ), + child: Text( + chain.name, + style: StyleConstants.buttonText, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/example/base/lib/widgets/event_widget.dart b/packages/reown_appkit/example/base/lib/widgets/event_widget.dart new file mode 100644 index 0000000..4cdf848 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/widgets/event_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit_dapp/utils/constants.dart'; +import 'package:reown_appkit_dapp/utils/string_constants.dart'; + +class EventWidget extends StatelessWidget { + const EventWidget({ + super.key, + required this.title, + required this.content, + }); + + final String title; + final String content; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text( + title, + style: StyleConstants.titleText, + ), + content: Text(content), + actions: [ + TextButton( + child: const Text( + StringConstants.ok, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + } +} diff --git a/packages/reown_appkit/example/base/lib/widgets/method_dialog.dart b/packages/reown_appkit/example/base/lib/widgets/method_dialog.dart new file mode 100644 index 0000000..3776d38 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/widgets/method_dialog.dart @@ -0,0 +1,98 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:convert'; + +import 'package:fl_toast/fl_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:reown_appkit_dapp/utils/constants.dart'; +import 'package:reown_appkit_dapp/utils/string_constants.dart'; + +class MethodDialog extends StatefulWidget { + static Future show( + BuildContext context, + String method, + Future response, + ) async { + await showDialog( + context: context, + builder: (BuildContext context) { + return MethodDialog( + method: method, + response: response, + ); + }, + ); + } + + const MethodDialog({ + super.key, + required this.method, + required this.response, + }); + + final String method; + final Future response; + + @override + MethodDialogState createState() => MethodDialogState(); +} + +class MethodDialogState extends State { + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(widget.method), + content: FutureBuilder( + future: widget.response, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + final String t = jsonEncode(snapshot.data); + return InkWell( + onTap: () { + Clipboard.setData(ClipboardData(text: t)).then( + (_) => showPlatformToast( + child: const Text(StringConstants.copiedToClipboard), + context: context, + ), + ); + }, + child: Text(t), + ); + } else if (snapshot.hasError) { + return InkWell( + onTap: () { + Clipboard.setData(ClipboardData(text: snapshot.data.toString())) + .then( + (_) => showPlatformToast( + child: const Text(StringConstants.copiedToClipboard), + context: context, + ), + ); + }, + child: Text( + snapshot.error.toString(), + ), + ); + } else { + return const SizedBox( + width: StyleConstants.linear48, + height: StyleConstants.linear48, + child: Center(child: CircularProgressIndicator()), + ); + } + }, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text( + StringConstants.close, + ), + ), + ], + ); + } +} diff --git a/packages/reown_appkit/example/base/lib/widgets/pairing_item.dart b/packages/reown_appkit/example/base/lib/widgets/pairing_item.dart new file mode 100644 index 0000000..1af0501 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/widgets/pairing_item.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_dapp/utils/constants.dart'; + +class PairingItem extends StatelessWidget { + const PairingItem({ + required Key key, + required this.pairing, + required this.onTap, + }) : super(key: key); + + final PairingInfo pairing; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + final expiryTimestamp = DateTime.fromMillisecondsSinceEpoch( + pairing.expiry * 1000, + ); + final dateFormat = DateFormat.yMd().add_jm(); + final expiryDate = dateFormat.format(expiryTimestamp); + final inDays = expiryTimestamp.difference(DateTime.now()).inDays + 1; + return InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(12.0), + color: pairing.active + ? Colors.blue.withOpacity(0.2) + : Colors.red.withOpacity(0.2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + pairing.peerMetadata?.name ?? 'Unknown', + style: StyleConstants.paragraph, + ), + Text( + pairing.peerMetadata?.url ?? 'Expiry: $expiryDate ($inDays days)', + ), + Text(pairing.topic), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/example/base/lib/widgets/session_item.dart b/packages/reown_appkit/example/base/lib/widgets/session_item.dart new file mode 100644 index 0000000..a3bc7f7 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/widgets/session_item.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_dapp/utils/constants.dart'; + +class SessionItem extends StatelessWidget { + const SessionItem({ + required Key key, + required this.session, + }) : super(key: key); + + final SessionData session; + + @override + Widget build(BuildContext context) { + final expiryTimestamp = DateTime.fromMillisecondsSinceEpoch( + session.expiry * 1000, + ); + final dateFormat = DateFormat.yMd().add_jm(); + final expiryDate = dateFormat.format(expiryTimestamp); + final inDays = expiryTimestamp.difference(DateTime.now()).inDays + 1; + return Container( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + session.peer.metadata.name, + style: StyleConstants.paragraph, + ), + Text('Expiry: $expiryDate ($inDays days)'), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/example/base/lib/widgets/session_widget.dart b/packages/reown_appkit/example/base/lib/widgets/session_widget.dart new file mode 100644 index 0000000..93e0989 --- /dev/null +++ b/packages/reown_appkit/example/base/lib/widgets/session_widget.dart @@ -0,0 +1,399 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; +import 'package:reown_appkit_dapp/utils/constants.dart'; +import 'package:reown_appkit_dapp/utils/crypto/eip155.dart'; +import 'package:reown_appkit_dapp/utils/crypto/helpers.dart'; +import 'package:reown_appkit_dapp/utils/crypto/polkadot.dart'; +import 'package:reown_appkit_dapp/utils/crypto/solana.dart'; +import 'package:reown_appkit_dapp/utils/string_constants.dart'; +import 'package:reown_appkit_dapp/widgets/method_dialog.dart'; + +class SessionWidget extends StatefulWidget { + const SessionWidget({ + super.key, + required this.session, + required this.appKit, + }); + + final SessionData session; + final ReownAppKit appKit; + + @override + SessionWidgetState createState() => SessionWidgetState(); +} + +class SessionWidgetState extends State { + @override + Widget build(BuildContext context) { + final List children = [ + Text( + '${StringConstants.sessionTopic}${widget.session.topic}', + ), + ]; + + // Get all of the accounts + final List namespaceAccounts = []; + + // Loop through the namespaces, and get the accounts + for (final Namespace namespace in widget.session.namespaces.values) { + namespaceAccounts.addAll(namespace.accounts); + } + + // Loop through the namespace accounts and build the widgets + for (final String namespaceAccount in namespaceAccounts) { + children.add( + _buildAccountWidget( + namespaceAccount, + ), + ); + } + + // Add a delete button + children.add( + Container( + width: double.infinity, + height: StyleConstants.linear48, + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + child: ElevatedButton( + onPressed: () async { + await widget.appKit.disconnectSession( + topic: widget.session.topic, + reason: Errors.getSdkError( + Errors.USER_DISCONNECTED, + ).toSignError(), + ); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Colors.red, + ), + ), + child: const Text( + StringConstants.delete, + style: StyleConstants.buttonText, + ), + ), + ), + ); + + children.add(const SizedBox(height: 20.0)); + return ListView( + children: children, + ); + } + + Widget _buildAccountWidget(String namespaceAccount) { + final chainId = NamespaceUtils.getChainFromAccount(namespaceAccount); + final account = NamespaceUtils.getAccount(namespaceAccount); + final chainMetadata = getChainMetadataFromChain(chainId); + + final List children = [ + Text( + chainMetadata.name, + style: StyleConstants.subtitleText, + ), + const SizedBox( + height: StyleConstants.linear8, + ), + Text( + account, + textAlign: TextAlign.center, + ), + const SizedBox( + height: StyleConstants.linear8, + ), + const Text( + StringConstants.methods, + style: StyleConstants.subtitleText, + ), + ]; + + children.addAll(_buildChainMethodButtons(chainMetadata, account)); + + children.add(const Divider()); + + if (chainId != 'eip155:11155111') { + children.add(const Text('Connect to Sepolia to Test')); + } + children.addAll(_buildSepoliaButtons(account, chainId)); + + children.addAll([ + const SizedBox( + height: StyleConstants.linear8, + ), + const Text( + StringConstants.events, + style: StyleConstants.subtitleText, + ), + ]); + children.addAll( + _buildChainEventsTiles( + chainMetadata, + ), + ); + + // final ChainMetadata + return Container( + width: double.infinity, + // height: StyleConstants.linear48, + padding: const EdgeInsets.all( + StyleConstants.linear8, + ), + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + decoration: BoxDecoration( + border: Border.all( + color: chainMetadata.color, + ), + borderRadius: const BorderRadius.all( + Radius.circular( + StyleConstants.linear8, + ), + ), + ), + child: Column( + children: children, + ), + ); + } + + List _buildChainMethodButtons( + ChainMetadata chainMetadata, + String address, + ) { + final List buttons = []; + // Add Methods + for (final String method in getChainMethods(chainMetadata.type)) { + final namespaces = widget.session.namespaces[chainMetadata.type.name]; + final supported = namespaces?.methods.contains(method) ?? false; + buttons.add( + Container( + width: double.infinity, + height: StyleConstants.linear48, + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + child: ElevatedButton( + onPressed: supported + ? () async { + final future = callChainMethod( + method, + chainMetadata, + address, + ); + MethodDialog.show(context, method, future); + _launchWallet(); + } + : null, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) => states.contains(MaterialState.disabled) + ? Colors.grey + : chainMetadata.color, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + StyleConstants.linear8, + ), + ), + ), + ), + child: Text( + method, + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + ), + ); + } + + return buttons; + } + + Future callChainMethod( + String method, + ChainMetadata chainMetadata, + String address, + ) { + switch (chainMetadata.type) { + case ChainType.eip155: + return EIP155.callMethod( + appKit: widget.appKit, + topic: widget.session.topic, + method: method, + chainData: chainMetadata, + address: address, + ); + case ChainType.polkadot: + return Polkadot.callMethod( + appKit: widget.appKit, + topic: widget.session.topic, + method: method, + chainId: chainMetadata.chainId, + address: address, + ); + case ChainType.solana: + return Solana.callMethod( + appKit: widget.appKit, + topic: widget.session.topic, + method: method, + chainData: chainMetadata, + address: address, + isV0: true, + ); + // case ChainType.kadena: + // return Kadena.callMethod( + // appKit: widget.appKit, + // topic: widget.session.topic, + // method: method.toKadenaMethod()!, + // chainId: chainMetadata.chainId, + // address: address.toLowerCase(), + // ); + default: + throw 'Unimplemented'; + } + } + + void _launchWallet() { + if (kIsWeb) return; + widget.appKit.redirectToWallet( + topic: widget.session.topic, + redirect: widget.session.peer.metadata.redirect, + ); + } + + List _buildSepoliaButtons(String address, String chainId) { + final List buttons = []; + final enabled = chainId == 'eip155:11155111'; + buttons.add( + Container( + width: double.infinity, + height: StyleConstants.linear48, + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + child: ElevatedButton( + onPressed: enabled + ? () async { + final future = EIP155.callSmartContract( + appKit: widget.appKit, + topic: widget.session.topic, + address: address, + action: 'read', + ); + MethodDialog.show(context, 'Test Contract (Read)', future); + } + : null, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return StyleConstants.grayColor; + } + return Colors.orange; + }), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + StyleConstants.linear8, + ), + ), + ), + ), + child: const Text( + 'Test Contract (Read)', + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + ), + ); + buttons.add( + Container( + width: double.infinity, + height: StyleConstants.linear48, + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + child: ElevatedButton( + onPressed: enabled + ? () async { + final future = EIP155.callSmartContract( + appKit: widget.appKit, + topic: widget.session.topic, + address: address, + action: 'write', + ); + MethodDialog.show(context, 'Test Contract (Write)', future); + _launchWallet(); + } + : null, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return StyleConstants.grayColor; + } + return Colors.orange; + }), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + StyleConstants.linear8, + ), + ), + ), + ), + child: const Text( + 'Test Contract (Write)', + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + ), + ); + + return buttons; + } + + List _buildChainEventsTiles(ChainMetadata chainMetadata) { + final List values = []; + + for (final String event in getChainEvents(chainMetadata.type)) { + values.add( + Container( + width: double.infinity, + height: StyleConstants.linear48, + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + decoration: BoxDecoration( + border: Border.all( + color: chainMetadata.color, + ), + borderRadius: const BorderRadius.all( + Radius.circular( + StyleConstants.linear8, + ), + ), + ), + child: Center( + child: Text( + event, + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + ), + ); + } + + return values; + } +} diff --git a/packages/reown_appkit/example/base/linux/.gitignore b/packages/reown_appkit/example/base/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/reown_appkit/example/base/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/reown_appkit/example/base/linux/CMakeLists.txt b/packages/reown_appkit/example/base/linux/CMakeLists.txt new file mode 100644 index 0000000..8e06638 --- /dev/null +++ b/packages/reown_appkit/example/base/linux/CMakeLists.txt @@ -0,0 +1,138 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "dapp") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.dapp") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/reown_appkit/example/base/linux/flutter/CMakeLists.txt b/packages/reown_appkit/example/base/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/packages/reown_appkit/example/base/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/reown_appkit/example/base/linux/flutter/generated_plugin_registrant.cc b/packages/reown_appkit/example/base/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/packages/reown_appkit/example/base/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/packages/reown_appkit/example/base/linux/flutter/generated_plugin_registrant.h b/packages/reown_appkit/example/base/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/packages/reown_appkit/example/base/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/reown_appkit/example/base/linux/flutter/generated_plugins.cmake b/packages/reown_appkit/example/base/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f16b4c3 --- /dev/null +++ b/packages/reown_appkit/example/base/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/reown_appkit/example/base/linux/main.cc b/packages/reown_appkit/example/base/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/packages/reown_appkit/example/base/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/reown_appkit/example/base/linux/my_application.cc b/packages/reown_appkit/example/base/linux/my_application.cc new file mode 100644 index 0000000..9d66210 --- /dev/null +++ b/packages/reown_appkit/example/base/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "dapp"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "dapp"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/packages/reown_appkit/example/base/linux/my_application.h b/packages/reown_appkit/example/base/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/packages/reown_appkit/example/base/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/reown_appkit/example/base/macos/.gitignore b/packages/reown_appkit/example/base/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/packages/reown_appkit/example/base/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/reown_appkit/example/base/macos/Flutter/Flutter-Debug.xcconfig b/packages/reown_appkit/example/base/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/reown_appkit/example/base/macos/Flutter/Flutter-Release.xcconfig b/packages/reown_appkit/example/base/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/reown_appkit/example/base/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/reown_appkit/example/base/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..0f56e05 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,22 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import connectivity_plus +import package_info_plus +import path_provider_foundation +import shared_preferences_foundation +import sqflite +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/packages/reown_appkit/example/base/macos/Podfile b/packages/reown_appkit/example/base/macos/Podfile new file mode 100644 index 0000000..049abe2 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Podfile @@ -0,0 +1,40 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/reown_appkit/example/base/macos/Podfile.lock b/packages/reown_appkit/example/base/macos/Podfile.lock new file mode 100644 index 0000000..1f66755 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Podfile.lock @@ -0,0 +1,35 @@ +PODS: + - FlutterMacOS (1.0.0) + - package_info_plus (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 + +COCOAPODS: 1.15.2 diff --git a/packages/reown_appkit/example/base/macos/Runner.xcodeproj/project.pbxproj b/packages/reown_appkit/example/base/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..0867ca9 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,564 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* dapp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dapp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* dapp.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* dapp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/reown_appkit/example/base/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_appkit/example/base/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_appkit/example/base/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/reown_appkit/example/base/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..6d82d95 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/reown_appkit/example/base/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/reown_appkit/example/base/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_appkit/example/base/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_appkit/example/base/macos/Runner/AppDelegate.swift b/packages/reown_appkit/example/base/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/packages/reown_appkit/example/base/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/reown_appkit/example/base/macos/Runner/Base.lproj/MainMenu.xib b/packages/reown_appkit/example/base/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/macos/Runner/Configs/AppInfo.xcconfig b/packages/reown_appkit/example/base/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..e7eb003 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = dapp + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. diff --git a/packages/reown_appkit/example/base/macos/Runner/Configs/Debug.xcconfig b/packages/reown_appkit/example/base/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/reown_appkit/example/base/macos/Runner/Configs/Release.xcconfig b/packages/reown_appkit/example/base/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/reown_appkit/example/base/macos/Runner/Configs/Warnings.xcconfig b/packages/reown_appkit/example/base/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/reown_appkit/example/base/macos/Runner/DebugProfile.entitlements b/packages/reown_appkit/example/base/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..195ca6b --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/packages/reown_appkit/example/base/macos/Runner/Info.plist b/packages/reown_appkit/example/base/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/reown_appkit/example/base/macos/Runner/MainFlutterWindow.swift b/packages/reown_appkit/example/base/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/reown_appkit/example/base/macos/Runner/Release.entitlements b/packages/reown_appkit/example/base/macos/Runner/Release.entitlements new file mode 100644 index 0000000..ab5e00b --- /dev/null +++ b/packages/reown_appkit/example/base/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/packages/reown_appkit/example/base/netlify.toml b/packages/reown_appkit/example/base/netlify.toml new file mode 100644 index 0000000..8263e63 --- /dev/null +++ b/packages/reown_appkit/example/base/netlify.toml @@ -0,0 +1,11 @@ +[[plugins]] + + package = "netlify-plugin-flutter" + + [plugins.inputs] + channel = "stable" + +[build] + +command = "flutter build web --release" +publish = "build/web" \ No newline at end of file diff --git a/packages/reown_appkit/example/base/pubspec.yaml b/packages/reown_appkit/example/base/pubspec.yaml new file mode 100644 index 0000000..14f52d6 --- /dev/null +++ b/packages/reown_appkit/example/base/pubspec.yaml @@ -0,0 +1,35 @@ +name: reown_appkit_dapp +description: An example dapp for Reown's AppKit built with Flutter + +publish_to: "none" + +version: 1.0.0+1 + +environment: + sdk: ">=2.18.6 <3.0.0" + +dependencies: + bs58: ^1.0.2 + cupertino_icons: ^1.0.2 + eth_sig_util: ^0.0.9 + fl_toast: ^3.1.0 + flutter: + sdk: flutter + intl: ^0.19.0 + json_annotation: ^4.8.1 + package_info_plus: ^7.0.0 + qr_flutter: ^4.0.0 + reown_appkit: + path: ../.. + solana_web3: ^0.1.3 + +dev_dependencies: + build_runner: ^2.0.0 + dependency_validator: ^3.2.2 + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + json_serializable: ^6.5.3 + +flutter: + uses-material-design: true diff --git a/packages/reown_appkit/example/base/test/widget_test.dart b/packages/reown_appkit/example/base/test/widget_test.dart new file mode 100644 index 0000000..2a2b819 --- /dev/null +++ b/packages/reown_appkit/example/base/test/widget_test.dart @@ -0,0 +1,8 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +void main() {} diff --git a/packages/reown_appkit/example/base/web/favicon.png b/packages/reown_appkit/example/base/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/packages/reown_appkit/example/base/web/favicon.png differ diff --git a/packages/reown_appkit/example/base/web/icons/Icon-192.png b/packages/reown_appkit/example/base/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/packages/reown_appkit/example/base/web/icons/Icon-192.png differ diff --git a/packages/reown_appkit/example/base/web/icons/Icon-512.png b/packages/reown_appkit/example/base/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/packages/reown_appkit/example/base/web/icons/Icon-512.png differ diff --git a/packages/reown_appkit/example/base/web/icons/Icon-maskable-192.png b/packages/reown_appkit/example/base/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/packages/reown_appkit/example/base/web/icons/Icon-maskable-192.png differ diff --git a/packages/reown_appkit/example/base/web/icons/Icon-maskable-512.png b/packages/reown_appkit/example/base/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/packages/reown_appkit/example/base/web/icons/Icon-maskable-512.png differ diff --git a/packages/reown_appkit/example/base/web/index.html b/packages/reown_appkit/example/base/web/index.html new file mode 100644 index 0000000..267bfd2 --- /dev/null +++ b/packages/reown_appkit/example/base/web/index.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + dapp + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/web/manifest.json b/packages/reown_appkit/example/base/web/manifest.json new file mode 100644 index 0000000..4cd3ac9 --- /dev/null +++ b/packages/reown_appkit/example/base/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "Flutter Dapp", + "short_name": "Flutter Dapp", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/reown_appkit/example/base/windows/.gitignore b/packages/reown_appkit/example/base/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/packages/reown_appkit/example/base/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/reown_appkit/example/base/windows/CMakeLists.txt b/packages/reown_appkit/example/base/windows/CMakeLists.txt new file mode 100644 index 0000000..38de73d --- /dev/null +++ b/packages/reown_appkit/example/base/windows/CMakeLists.txt @@ -0,0 +1,101 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(dapp LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "dapp") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/reown_appkit/example/base/windows/flutter/CMakeLists.txt b/packages/reown_appkit/example/base/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..930d207 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/reown_appkit/example/base/windows/flutter/generated_plugin_registrant.cc b/packages/reown_appkit/example/base/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..5777988 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/packages/reown_appkit/example/base/windows/flutter/generated_plugin_registrant.h b/packages/reown_appkit/example/base/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/reown_appkit/example/base/windows/flutter/generated_plugins.cmake b/packages/reown_appkit/example/base/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..3103206 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + connectivity_plus + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/reown_appkit/example/base/windows/runner/CMakeLists.txt b/packages/reown_appkit/example/base/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..17411a8 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/reown_appkit/example/base/windows/runner/Runner.rc b/packages/reown_appkit/example/base/windows/runner/Runner.rc new file mode 100644 index 0000000..f70b53b --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "dapp" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "dapp" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "dapp.exe" "\0" + VALUE "ProductName", "dapp" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/reown_appkit/example/base/windows/runner/flutter_window.cpp b/packages/reown_appkit/example/base/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b43b909 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/reown_appkit/example/base/windows/runner/flutter_window.h b/packages/reown_appkit/example/base/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/reown_appkit/example/base/windows/runner/main.cpp b/packages/reown_appkit/example/base/windows/runner/main.cpp new file mode 100644 index 0000000..d35f87b --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"dapp", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/reown_appkit/example/base/windows/runner/resource.h b/packages/reown_appkit/example/base/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/reown_appkit/example/base/windows/runner/resources/app_icon.ico b/packages/reown_appkit/example/base/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/packages/reown_appkit/example/base/windows/runner/resources/app_icon.ico differ diff --git a/packages/reown_appkit/example/base/windows/runner/runner.exe.manifest b/packages/reown_appkit/example/base/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/base/windows/runner/utils.cpp b/packages/reown_appkit/example/base/windows/runner/utils.cpp new file mode 100644 index 0000000..f5bf9fa --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/reown_appkit/example/base/windows/runner/utils.h b/packages/reown_appkit/example/base/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/reown_appkit/example/base/windows/runner/win32_window.cpp b/packages/reown_appkit/example/base/windows/runner/win32_window.cpp new file mode 100644 index 0000000..c10f08d --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/packages/reown_appkit/example/base/windows/runner/win32_window.h b/packages/reown_appkit/example/base/windows/runner/win32_window.h new file mode 100644 index 0000000..17ba431 --- /dev/null +++ b/packages/reown_appkit/example/base/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/reown_appkit/example/modal/.gitignore b/packages/reown_appkit/example/modal/.gitignore new file mode 100644 index 0000000..0e140c0 --- /dev/null +++ b/packages/reown_appkit/example/modal/.gitignore @@ -0,0 +1,59 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/packages/reown_appkit/example/modal/README.md b/packages/reown_appkit/example/modal/README.md new file mode 100644 index 0000000..e32052c --- /dev/null +++ b/packages/reown_appkit/example/modal/README.md @@ -0,0 +1,16 @@ +# reown_appkit_example + +Demonstrates how to use the reown_appkit plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/reown_appkit/example/modal/analysis_options.yaml b/packages/reown_appkit/example/modal/analysis_options.yaml new file mode 100644 index 0000000..b677fc6 --- /dev/null +++ b/packages/reown_appkit/example/modal/analysis_options.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:lints/recommended.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + non_constant_identifier_names: false + constant_identifier_names: false + avoid_print: true + prefer_single_quotes: true + sort_pub_dependencies: true + avoid_unnecessary_containers: true + cancel_subscriptions: true + +analyzer: + exclude: + - '**.freezed.dart' + - '**.g.dart' + - '**/*.freezed.dart' + - '**/*.g.dart' + - '**/generated_plugin_registrant.dart' + errors: + invalid_annotation_target: ignore +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reown_appkit/example/modal/android/.gitignore b/packages/reown_appkit/example/modal/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/packages/reown_appkit/example/modal/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/reown_appkit/example/modal/android/app/build.gradle b/packages/reown_appkit/example/modal/android/app/build.gradle new file mode 100644 index 0000000..069b36c --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/build.gradle @@ -0,0 +1,95 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace "com.web3modal.flutterExample" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.web3modal.flutterExample" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion 23 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + // Specifies one flavor dimension. + flavorDimensions = ["version"] + + productFlavors { + alpha { + // Assigns this product flavor to the "version" flavor dimension. + // If you are using only one dimension, this property is optional, + // and the plugin automatically assigns all the module's flavors to + // that dimension. + dimension "version" + applicationIdSuffix ".debug" + // versionNameSuffix "-alpha" + } + beta { + dimension "version" + applicationIdSuffix ".internal" + // versionNameSuffix "-beta" + } + stable { + dimension "version" + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/reown_appkit/example/modal/android/app/src/debug/AndroidManifest.xml b/packages/reown_appkit/example/modal/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml b/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6dc4758 --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/modal/android/app/src/main/ic_launcher-playstore.png b/packages/reown_appkit/example/modal/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..e98ed55 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/ic_launcher-playstore.png differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/kotlin/com/example/sign/MainActivity.kt b/packages/reown_appkit/example/modal/android/app/src/main/kotlin/com/example/sign/MainActivity.kt new file mode 100644 index 0000000..4094c0c --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/kotlin/com/example/sign/MainActivity.kt @@ -0,0 +1,6 @@ +package com.web3modal.flutterExample + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/reown_appkit/example/modal/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/drawable/launch_background.xml b/packages/reown_appkit/example/modal/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..141bb17 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..afb3a4d Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..785b2b9 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..2e99eb4 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..c4b8b8e Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..f4c8554 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..6e1cc75 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..6f30056 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..8551f76 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..0af8592 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..81bb974 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..c873004 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..a45aa95 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..0ae6576 Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..af652ad Binary files /dev/null and b/packages/reown_appkit/example/modal/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/values-night/styles.xml b/packages/reown_appkit/example/modal/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/values/ic_launcher_background.xml b/packages/reown_appkit/example/modal/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..bc5d622 --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #EDF1FF + \ No newline at end of file diff --git a/packages/reown_appkit/example/modal/android/app/src/main/res/values/styles.xml b/packages/reown_appkit/example/modal/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/reown_appkit/example/modal/android/app/src/profile/AndroidManifest.xml b/packages/reown_appkit/example/modal/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/packages/reown_appkit/example/modal/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/reown_appkit/example/modal/android/build.gradle b/packages/reown_appkit/example/modal/android/build.gradle new file mode 100644 index 0000000..f7eb7f6 --- /dev/null +++ b/packages/reown_appkit/example/modal/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/reown_appkit/example/modal/android/gradle.properties b/packages/reown_appkit/example/modal/android/gradle.properties new file mode 100644 index 0000000..c49e7ef --- /dev/null +++ b/packages/reown_appkit/example/modal/android/gradle.properties @@ -0,0 +1,5 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +versionName=3.3.3 +versionCode=74 diff --git a/packages/reown_appkit/example/modal/android/gradle/wrapper/gradle-wrapper.properties b/packages/reown_appkit/example/modal/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/packages/reown_appkit/example/modal/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/reown_appkit/example/modal/android/settings.gradle b/packages/reown_appkit/example/modal/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/packages/reown_appkit/example/modal/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/reown_appkit/example/modal/assets/AppIcon.png b/packages/reown_appkit/example/modal/assets/AppIcon.png new file mode 100644 index 0000000..3acb645 Binary files /dev/null and b/packages/reown_appkit/example/modal/assets/AppIcon.png differ diff --git a/packages/reown_appkit/example/modal/assets/abis/testContract.abi.json b/packages/reown_appkit/example/modal/assets/abis/testContract.abi.json new file mode 100644 index 0000000..b546a79 --- /dev/null +++ b/packages/reown_appkit/example/modal/assets/abis/testContract.abi.json @@ -0,0 +1,28 @@ +[ + { + "inputs": [], + "name": "retrieve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "num", + "type": "uint256" + } + ], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/reown_appkit/example/modal/integration_test/plugin_integration_test.dart b/packages/reown_appkit/example/modal/integration_test/plugin_integration_test.dart new file mode 100644 index 0000000..481e050 --- /dev/null +++ b/packages/reown_appkit/example/modal/integration_test/plugin_integration_test.dart @@ -0,0 +1,23 @@ +// This is a basic Flutter integration test. +// +// Since integration tests run in a full Flutter application, they can interact +// with the host side of a plugin implementation, unlike Dart unit tests. +// +// For more information about Flutter integration tests, please see +// https://docs.flutter.dev/cookbook/testing/integration/introduction + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +// import 'package:reown_appkit/reown_appkit.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('getPlatformVersion test', (WidgetTester tester) async { + // final AppKitModal plugin = AppKitModal(); + // final String? version = await plugin.getPlatformVersion(); + // The version string depends on the host platform running the test, so + // just assert that some non-empty string is returned. + // expect(version?.isNotEmpty, true); + }); +} diff --git a/packages/reown_appkit/example/modal/ios/.gitignore b/packages/reown_appkit/example/modal/ios/.gitignore new file mode 100644 index 0000000..167c078 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/.gitignore @@ -0,0 +1,37 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 + +*.zip +*.ipa diff --git a/packages/reown_appkit/example/modal/ios/Flutter/AppFrameworkInfo.plist b/packages/reown_appkit/example/modal/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/packages/reown_appkit/example/modal/ios/Flutter/Debug.xcconfig b/packages/reown_appkit/example/modal/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_appkit/example/modal/ios/Flutter/Release.xcconfig b/packages/reown_appkit/example/modal/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_appkit/example/modal/ios/Podfile b/packages/reown_appkit/example/modal/ios/Podfile new file mode 100644 index 0000000..3e44f9c --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/reown_appkit/example/modal/ios/Podfile.lock b/packages/reown_appkit/example/modal/ios/Podfile.lock new file mode 100644 index 0000000..35be0e9 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Podfile.lock @@ -0,0 +1,89 @@ +PODS: + - appcheck (1.0.3): + - Flutter + - coinbase_wallet_sdk (0.0.1): + - CoinbaseWalletSDK/CrossPlatform (= 1.0.4) + - Flutter + - CoinbaseWalletSDK/Client (1.0.4) + - CoinbaseWalletSDK/CrossPlatform (1.0.4): + - CoinbaseWalletSDK/Client + - connectivity_plus (0.0.1): + - Flutter + - FlutterMacOS + - Flutter (1.0.0) + - integration_test (0.0.1): + - Flutter + - package_info_plus (0.4.5): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite (0.0.3): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter + +DEPENDENCIES: + - appcheck (from `.symlinks/plugins/appcheck/ios`) + - coinbase_wallet_sdk (from `.symlinks/plugins/coinbase_wallet_sdk/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) + - Flutter (from `Flutter`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + +SPEC REPOS: + trunk: + - CoinbaseWalletSDK + +EXTERNAL SOURCES: + appcheck: + :path: ".symlinks/plugins/appcheck/ios" + coinbase_wallet_sdk: + :path: ".symlinks/plugins/coinbase_wallet_sdk/ios" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/darwin" + Flutter: + :path: Flutter + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite: + :path: ".symlinks/plugins/sqflite/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + +SPEC CHECKSUMS: + appcheck: e1ab9d4e03736f03e0401554a134d1ed502d7629 + coinbase_wallet_sdk: 7ccd4e1a7940deba6ba9bd81beece999a2268c15 + CoinbaseWalletSDK: ea1f37512bbc69ebe07416e3b29bf840f5cc3152 + connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + integration_test: 13825b8a9334a850581300559b8839134b124670 + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1 + +PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 + +COCOAPODS: 1.15.2 diff --git a/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.pbxproj b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c010e2d --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,744 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 045B3B66922963849F55398B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A45AE752066FA1327E4BFC84 /* Pods_RunnerTests.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B301E97BBEF0D0D439C8FEBD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB726FCBD7E3D4509E242BBE /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 092D151B2ABD988600C69848 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 196F86CE363C39102FDC31ED /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 5336573867299186093BD96F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 66C8FD3735872A84CC442949 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A45AE752066FA1327E4BFC84 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D57DDFCB49A18D7917B137 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + C9BF0D86647DA9B24F19C581 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + FB726FCBD7E3D4509E242BBE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FD6495BE06E43EEDD540010D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6D09732E2619FACF7894A4A1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 045B3B66922963849F55398B /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B301E97BBEF0D0D439C8FEBD /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 70EA58727DC3189C1B55723C /* Frameworks */ = { + isa = PBXGroup; + children = ( + FB726FCBD7E3D4509E242BBE /* Pods_Runner.framework */, + A45AE752066FA1327E4BFC84 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + E880F334C300E1CEFAFD6E4C /* Pods */, + 70EA58727DC3189C1B55723C /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 092D151B2ABD988600C69848 /* Runner.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + E880F334C300E1CEFAFD6E4C /* Pods */ = { + isa = PBXGroup; + children = ( + C9BF0D86647DA9B24F19C581 /* Pods-Runner.debug.xcconfig */, + 5336573867299186093BD96F /* Pods-Runner.release.xcconfig */, + 196F86CE363C39102FDC31ED /* Pods-Runner.profile.xcconfig */, + 66C8FD3735872A84CC442949 /* Pods-RunnerTests.debug.xcconfig */, + A7D57DDFCB49A18D7917B137 /* Pods-RunnerTests.release.xcconfig */, + FD6495BE06E43EEDD540010D /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 1B16F32792E453E2E6356936 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 6D09732E2619FACF7894A4A1 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + C1BF67432638FF95297A6F41 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + DE291E06FB11EC8EFC85C02A /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1B16F32792E453E2E6356936 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + C1BF67432638FF95297A6F41 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + DE291E06FB11EC8EFC85C02A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 73; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = FlutterDevelopmentProfile2; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 66C8FD3735872A84CC442949 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 73; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7D57DDFCB49A18D7917B137 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 73; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FD6495BE06E43EEDD540010D /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 73; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 73; + DEVELOPMENT_TEAM = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 73; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = FlutterAppStoreProfileWithPush; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c2a30f9 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift b/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..81aa14a --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift @@ -0,0 +1,39 @@ +import UIKit +import Flutter +import CoinbaseWalletSDK + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + if #available(iOS 13.0, *) { + if (CoinbaseWalletSDK.isConfigured == true) { + if (try? CoinbaseWalletSDK.shared.handleResponse(url)) == true { + return true + } + } + } + + return super.application(app, open: url, options: options) + } + + override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if #available(iOS 13.0, *) { + if (CoinbaseWalletSDK.isConfigured == true) { + if let url = userActivity.webpageURL, + (try? CoinbaseWalletSDK.shared.handleResponse(url)) == true { + return true + } + } + } + + return super.application(application, continue: userActivity, restorationHandler: restorationHandler) + } +} diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..9718eec Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..dc72967 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..70848f0 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..572c931 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..17b9f07 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..dbe0918 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..6127ee7 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..c92af57 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..701a5f4 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..355f4ba Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..adc4036 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..f8987a7 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..5de78c6 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..a675a9c Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..f879607 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..43cf544 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..2a56452 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..b641939 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..6982630 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..65b74d7 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} \ No newline at end of file diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Contents.json b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Contents.json b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Contents.json new file mode 100644 index 0000000..d40e37d --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Logo@2x.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Logo@2x.png new file mode 100644 index 0000000..4589bb7 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Logo@2x.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Logo@3x.png b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Logo@3x.png new file mode 100644 index 0000000..dc72967 Binary files /dev/null and b/packages/reown_appkit/example/modal/ios/Runner/Assets.xcassets/Logo.imageset/Logo@3x.png differ diff --git a/packages/reown_appkit/example/modal/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/reown_appkit/example/modal/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..85abd12 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner/Base.lproj/Main.storyboard b/packages/reown_appkit/example/modal/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsDebug.plist b/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsDebug.plist new file mode 100644 index 0000000..0b9e347 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsDebug.plist @@ -0,0 +1,13 @@ + + + + + method + app-store + provisioningProfiles + + com.web3modal.flutterExample.debug + FlutterAppStoreProfileDebug + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsInternal.plist b/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsInternal.plist new file mode 100644 index 0000000..f846f4b --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsInternal.plist @@ -0,0 +1,13 @@ + + + + + method + app-store + provisioningProfiles + + com.web3modal.flutterExample.internal + FlutterAppStoreProfileInternal + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsRelease.plist b/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsRelease.plist new file mode 100644 index 0000000..d23e826 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsRelease.plist @@ -0,0 +1,13 @@ + + + + + method + app-store + provisioningProfiles + + com.web3modal.flutterExample + FlutterAppStoreProfileWithPush + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner/Info.plist b/packages/reown_appkit/example/modal/ios/Runner/Info.plist new file mode 100644 index 0000000..41668d5 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/Info.plist @@ -0,0 +1,118 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + AppKit Flutter + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppKit Flutter + CFBundlePackageType + APPL + CFBundleShortVersionString + 3.3.3 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.web3modal.flutterExample + CFBundleURLSchemes + + web3modalflutter + + + + CFBundleVersion + 73 + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + wirexwallet + stasis + omni + strikex + bitcoincom + bnc + kryptogo + roninwallet + moonstake + ripio + frontier + qubic + dropp + safepalwallet + bee + shido + foxwallet + exodus + coolwallet + shinobi-wallet + halowallet + spotonchain + rainbow + obvious + robinhood-wallet + cbwallet + okto + bitkeep + hyperPay + bitizen + ape + uniswap + zerion + oasys-wallet + coinstats + ledgerlive + safe + okex + trust + thorwallet + krakenwallet + coinwallet + mewwallet + metamask + avacus + walletapp + wcflutterwallet + wcflutterwallet-internal + rn-web3wallet + + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/reown_appkit/example/modal/ios/Runner/Runner-Bridging-Header.h b/packages/reown_appkit/example/modal/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements b/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/packages/reown_appkit/example/modal/ios/RunnerTests/RunnerTests.swift b/packages/reown_appkit/example/modal/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/packages/reown_appkit/example/modal/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/reown_appkit/example/modal/lib/home_page.dart b/packages/reown_appkit/example/modal/lib/home_page.dart new file mode 100644 index 0000000..d2bc1a2 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/home_page.dart @@ -0,0 +1,518 @@ +import 'dart:developer'; + +import 'package:fl_toast/fl_toast.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_example/widgets/debug_drawer.dart'; +import 'package:reown_appkit_example/utils/constants.dart'; +import 'package:reown_appkit_example/services/siwe_service.dart'; +import 'package:reown_appkit_example/widgets/logger_widget.dart'; +import 'package:reown_appkit_example/widgets/session_widget.dart'; +import 'package:reown_appkit_example/utils/dart_defines.dart'; + +class MyHomePage extends StatefulWidget { + const MyHomePage({ + super.key, + required this.prefs, + required this.packageInfo, + required this.toggleBrightness, + required this.toggleTheme, + }); + + final VoidCallback toggleBrightness; + final VoidCallback toggleTheme; + final SharedPreferences prefs; + final PackageInfo packageInfo; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + final overlay = OverlayController(const Duration(milliseconds: 200)); + late AppKitModal _appKitModal; + late SIWESampleWebService _siweTestService; + bool _initialized = false; + + @override + void initState() { + super.initState(); + _siweTestService = SIWESampleWebService(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _toggleOverlay(); + _initializeService(widget.prefs); + }); + } + + void _toggleOverlay() { + overlay.show(context); + } + + String get _flavor { + // String flavor = '-${const String.fromEnvironment('FLUTTER_APP_FLAVOR')}'; + // return flavor.replaceAll('-production', ''); + final internal = widget.packageInfo.packageName.endsWith('.internal'); + final debug = widget.packageInfo.packageName.endsWith('.debug'); + if (internal || debug || kDebugMode) { + return 'internal'; + } + return ''; + } + + String _universalLink() { + // TODO change /flutter_appkit to something else + Uri link = Uri.parse('https://lab.web3modal.com/flutter_appkit'); + if (_flavor.isNotEmpty) { + return link + .replace(path: '${link.path}_internal') + .replace(host: 'dev.${link.host}') + .toString(); + } + return link.toString(); + } + + Redirect _constructRedirect() { + return Redirect( + native: 'web3modalflutter://', + universal: _universalLink(), + // enable linkMode on Wallet so Dapps can use relay-less connection + // universal: value must be set on cloud config as well + // linkMode: true, + ); + } + + PairingMetadata _pairingMetadata() { + return PairingMetadata( + name: StringConstants.pageTitle, + description: StringConstants.pageTitle, + url: _universalLink(), + icons: [ + 'https://docs.walletconnect.com/assets/images/web3modalLogo-2cee77e07851ba0a710b56d03d4d09dd.png' + ], + redirect: _constructRedirect(), + ); + } + + SIWEConfig _siweConfig(bool enabled) => SIWEConfig( + getNonce: () async { + // this has to be called at the very moment of creating the pairing uri + try { + debugPrint('[SIWEConfig] getNonce()'); + final response = await _siweTestService.getNonce(); + return response['nonce'] as String; + } catch (error) { + debugPrint('[SIWEConfig] getNonce error: $error'); + // Fallback patch for testing purposes in case SIWE backend has issues + return SIWEUtils.generateNonce(); + } + }, + getMessageParams: () async { + // Provide everything that is needed to construct the SIWE message + debugPrint('[SIWEConfig] getMessageParams()'); + final url = _pairingMetadata().url; + final uri = Uri.parse(url); + return SIWEMessageArgs( + domain: uri.authority, + uri: 'https://${uri.authority}/login', + statement: 'Welcome to AppKit $packageVersion for Flutter.', + methods: MethodsConstants.allMethods, + ); + }, + createMessage: (SIWECreateMessageArgs args) { + // Create SIWE message to be signed. + // You can use our provided formatMessage() method of implement your own + debugPrint('[SIWEConfig] createMessage()'); + return SIWEUtils.formatMessage(args); + }, + verifyMessage: (SIWEVerifyMessageArgs args) async { + // Implement your verifyMessage to authenticate the user after it. + try { + debugPrint('[SIWEConfig] verifyMessage()'); + final payload = args.toJson(); + final url = _pairingMetadata().url; + final uri = Uri.parse(url); + final result = await _siweTestService.verifyMessage( + payload, + domain: uri.authority, + ); + return result['token'] != null; + } catch (error) { + debugPrint('[SIWEConfig] verifyMessage error: $error'); + // Fallback patch for testing purposes in case SIWE backend has issues + // TODO document SIWEUtils + final chainId = SIWEUtils.getChainIdFromMessage(args.message); + final address = SIWEUtils.getAddressFromMessage(args.message); + final cacaoSignature = args.cacao != null + ? args.cacao!.s + : CacaoSignature( + t: CacaoSignature.EIP191, + s: args.signature, + ); + return await SIWEUtils.verifySignature( + address, + args.message, + cacaoSignature, + chainId, + DartDefines.projectId, + ); + } + }, + getSession: () async { + // Return proper session from your Web Service + try { + debugPrint('[SIWEConfig] getSession()'); + final session = await _siweTestService.getSession(); + final address = session['address']!.toString(); + final chainId = session['chainId']!.toString(); + return SIWESession(address: address, chains: [chainId]); + } catch (error) { + debugPrint('[SIWEConfig] getSession error: $error'); + // Fallback patch for testing purposes in case SIWE backend has issues + final address = _appKitModal.session!.address!; + final chainId = _appKitModal.session!.chainId; + return SIWESession(address: address, chains: [chainId]); + } + }, + onSignIn: (SIWESession session) { + // Called after SIWE message is signed and verified + debugPrint('[SIWEConfig] onSignIn()'); + }, + signOut: () async { + // Called when user taps on disconnect button + try { + debugPrint('[SIWEConfig] signOut()'); + final _ = await _siweTestService.signOut(); + return true; + } catch (error) { + debugPrint('[SIWEConfig] signOut error: $error'); + // Fallback patch for testing purposes in case SIWE backend has issues + return true; + } + }, + onSignOut: () { + // Called when disconnecting WalletConnect session was successfull + debugPrint('[SIWEConfig] onSignOut()'); + }, + enabled: enabled, + // signOutOnDisconnect: true, + // signOutOnAccountChange: true, + // signOutOnNetworkChange: true, + // nonceRefetchIntervalMs: 300000, + // sessionRefetchIntervalMs: 300000, + ); + + void _initializeService(SharedPreferences prefs) async { + final analyticsValue = prefs.getBool('appkit_analytics') ?? true; + final emailWalletValue = prefs.getBool('appkit_email_wallet') ?? true; + final siweAuthValue = prefs.getBool('appkit_siwe_auth') ?? true; + + // See https://docs.walletconnect.com/appkit/flutter/core/custom-chains + final eip155Chains = AppKitModalNetworks.supported['eip155']!; + final eip155Tests = AppKitModalNetworks.test['eip155']!; + AppKitModalNetworks.supported['eip155'] = [ + ...eip155Chains, + ...eip155Tests, + ]; + + try { + _appKitModal = AppKitModal( + context: context, + projectId: DartDefines.projectId, + logLevel: LogLevel.error, + metadata: _pairingMetadata(), + siweConfig: _siweConfig(siweAuthValue), + enableAnalytics: analyticsValue, // OPTIONAL - null by default + enableEmail: emailWalletValue, // OPTIONAL - false by default + // requiredNamespaces: {}, + // optionalNamespaces: {}, + // includedWalletIds: {}, + featuredWalletIds: { + 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', // Coinbase + '18450873727504ae9315a084fa7624b5297d2fe5880f0982979c17345a138277', // Kraken Wallet + 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96', // Metamask + '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369', // Rainbow + 'c03dfee351b6fcc421b4494ea33b9d4b92a984f87aa76d1663bb28705e95034a', // Uniswap + '38f5d18bd8522c244bdd70cb4a68e0e718865155811c043f052fb9f1c51de662', // Bitget + }, + // excludedWalletIds: { + // 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', // Coinbase + // }, + // MORE WALLETS https://explorer.walletconnect.com/?type=wallet&chains=eip155%3A1 + ); + setState(() => _initialized = true); + } on AppKitModalException catch (e) { + debugPrint('⛔️ ${e.message}'); + return; + } + // modal specific subscriptions + _appKitModal.onModalConnect.subscribe(_onModalConnect); + _appKitModal.onModalUpdate.subscribe(_onModalUpdate); + _appKitModal.onModalNetworkChange.subscribe(_onModalNetworkChange); + _appKitModal.onModalDisconnect.subscribe(_onModalDisconnect); + _appKitModal.onModalError.subscribe(_onModalError); + // session related subscriptions + _appKitModal.onSessionExpireEvent.subscribe(_onSessionExpired); + _appKitModal.onSessionUpdateEvent.subscribe(_onSessionUpdate); + _appKitModal.onSessionEventEvent.subscribe(_onSessionEvent); + // relayClient subscriptions + _appKitModal.appKit!.core.relayClient.onRelayClientConnect.subscribe( + _onRelayClientConnect, + ); + _appKitModal.appKit!.core.relayClient.onRelayClientError.subscribe( + _onRelayClientError, + ); + _appKitModal.appKit!.core.relayClient.onRelayClientDisconnect.subscribe( + _onRelayClientDisconnect, + ); + _appKitModal.appKit!.core.addLogListener(_logListener); + // + await _appKitModal.init(); + setState(() {}); + } + + void _logListener(LogEvent event) { + if (event.level == Level.debug) { + // TODO send to mixpanel + log('${event.message}'); + } else { + debugPrint('${event.message}'); + } + } + + @override + void dispose() { + // + _appKitModal.appKit!.core.removeLogListener(_logListener); + _appKitModal.appKit!.core.relayClient.onRelayClientConnect.unsubscribe( + _onRelayClientConnect, + ); + _appKitModal.appKit!.core.relayClient.onRelayClientError.unsubscribe( + _onRelayClientError, + ); + _appKitModal.appKit!.core.relayClient.onRelayClientDisconnect.unsubscribe( + _onRelayClientDisconnect, + ); + // + _appKitModal.onModalConnect.unsubscribe(_onModalConnect); + _appKitModal.onModalUpdate.unsubscribe(_onModalUpdate); + _appKitModal.onModalNetworkChange.unsubscribe(_onModalNetworkChange); + _appKitModal.onModalDisconnect.unsubscribe(_onModalDisconnect); + _appKitModal.onModalError.unsubscribe(_onModalError); + // + _appKitModal.onSessionExpireEvent.unsubscribe(_onSessionExpired); + _appKitModal.onSessionUpdateEvent.unsubscribe(_onSessionUpdate); + _appKitModal.onSessionEventEvent.unsubscribe(_onSessionEvent); + // + super.dispose(); + } + + void _onModalConnect(ModalConnect? event) async { + setState(() {}); + debugPrint('[ExampleApp] _onModalConnect ${event?.session.toJson()}'); + } + + void _onModalUpdate(ModalConnect? event) { + setState(() {}); + } + + void _onModalNetworkChange(ModalNetworkChange? event) { + debugPrint('[ExampleApp] _onModalNetworkChange ${event?.toString()}'); + setState(() {}); + } + + void _onModalDisconnect(ModalDisconnect? event) { + debugPrint('[ExampleApp] _onModalDisconnect ${event?.toString()}'); + setState(() {}); + } + + void _onModalError(ModalError? event) { + debugPrint('[ExampleApp] _onModalError ${event?.toString()}'); + // When user connected to Coinbase Wallet but Coinbase Wallet does not have a session anymore + // (for instance if user disconnected the dapp directly within Coinbase Wallet) + // Then Coinbase Wallet won't emit any event + if ((event?.message ?? '').contains('Coinbase Wallet Error')) { + _appKitModal.disconnect(); + } + setState(() {}); + } + + void _onSessionExpired(SessionExpire? event) { + debugPrint('[ExampleApp] _onSessionExpired ${event?.toString()}'); + setState(() {}); + } + + void _onSessionUpdate(SessionUpdate? event) { + debugPrint('[ExampleApp] _onSessionUpdate ${event?.toString()}'); + setState(() {}); + } + + void _onSessionEvent(SessionEvent? event) { + debugPrint('[ExampleApp] _onSessionEvent ${event?.toString()}'); + setState(() {}); + } + + void _onRelayClientConnect(EventArgs? event) { + setState(() {}); + showTextToast(text: 'Relay connected', context: context); + } + + void _onRelayClientError(EventArgs? event) { + setState(() {}); + showTextToast(text: 'Relay disconnected', context: context); + } + + void _onRelayClientDisconnect(EventArgs? event) { + setState(() {}); + showTextToast( + text: 'Relay disconnected: ${event?.toString()}', + context: context, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppKitModalTheme.colorsOf(context).background125, + appBar: AppBar( + elevation: 0.0, + title: const Text(StringConstants.pageTitle), + backgroundColor: AppKitModalTheme.colorsOf(context).background175, + foregroundColor: AppKitModalTheme.colorsOf(context).foreground100, + ), + body: !_initialized + ? const SizedBox.shrink() + : RefreshIndicator( + onRefresh: () => _refreshData(), + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox.square(dimension: 6.0), + _ButtonsView(appKit: _appKitModal), + _ConnectedView(appKit: _appKitModal), + ], + ), + ), + ), + endDrawer: Drawer( + backgroundColor: AppKitModalTheme.colorsOf(context).background125, + child: DebugDrawer( + toggleOverlay: _toggleOverlay, + toggleBrightness: widget.toggleBrightness, + toggleTheme: widget.toggleTheme, + ), + ), + onEndDrawerChanged: (isOpen) { + // write your callback implementation here + if (isOpen) return; + showDialog( + context: context, + builder: (BuildContext context) { + return const AlertDialog( + content: Text( + 'If you made changes you\'ll need to restart the app', + ), + ); + }, + ); + }, + // floatingActionButton: CircleAvatar( + // radius: 6.0, + // backgroundColor: _initialized && + // _appKitModal.appKit?.core.relayClient.isConnected == true + // ? Colors.green + // : Colors.red, + // ), + ); + } + + Future _refreshData() async { + await _appKitModal.reconnectRelay(); + await _appKitModal.loadAccountData(); + setState(() {}); + } +} + +class _ButtonsView extends StatelessWidget { + const _ButtonsView({required this.appKit}); + final AppKitModal appKit; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppKitModalNetworkSelectButton( + service: appKit, + context: context, + // UNCOMMENT TO USE A CUSTOM BUTTON + // custom: ElevatedButton( + // onPressed: () { + // appKit.openNetworksView(); + // }, + // child: Text(appKit.selectedChain?.name ?? 'OPEN CHAINS'), + // ), + ), + const SizedBox.square(dimension: 6.0), + AppKitModalConnectButton( + service: appKit, + context: context, + // UNCOMMENT TO USE A CUSTOM BUTTON + // TO HIDE AppKitModalConnectButton BUT STILL RENDER IT (NEEDED) JUST USE SizedBox.shrink() + // TODO document custom button + // custom: ElevatedButton( + // onPressed: () { + // // TODO document openModalView + // // appKit.openModalView(AppKitModalQRCodePage()); + // // appKit.openModalView(AppKitModalSelectNetworkPage()); + // // appKit.openModalView(AppKitModalAllWalletsPage()); + // // appKit.openModalView(AppKitModalMainWalletsPage()); + // }, + // child: appKit.isConnected + // ? Text('${appKit.session!.address!.substring(0, 7)}...') + // : const Text('CONNECT WALLET'), + // ), + ), + ], + ); + } +} + +class _ConnectedView extends StatelessWidget { + const _ConnectedView({required this.appKit}); + final AppKitModal appKit; + + @override + Widget build(BuildContext context) { + if (!appKit.isConnected) { + return const SizedBox.shrink(); + } + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppKitModalAccountButton( + service: appKit, + context: context, + // custom: ValueListenableBuilder( + // valueListenable: appKit.balanceNotifier, // TODO document balanceNotifier + // builder: (_, balance, __) { + // return ElevatedButton( + // onPressed: () { + // appKit.openModalView(); + // }, + // child: Text(balance), + // ); + // }, + // ), + ), + SessionWidget(appKit: appKit), + const SizedBox.square(dimension: 12.0), + ], + ); + } +} diff --git a/packages/reown_appkit/example/modal/lib/main.dart b/packages/reown_appkit/example/modal/lib/main.dart new file mode 100644 index 0000000..243e9c8 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/main.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:reown_appkit_example/home_page.dart'; +import 'package:reown_appkit_example/utils/constants.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State with WidgetsBindingObserver { + bool _isDarkMode = false; + AppKitModalThemeData? _themeData; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + final platformDispatcher = View.of(context).platformDispatcher; + final platformBrightness = platformDispatcher.platformBrightness; + _isDarkMode = platformBrightness == Brightness.dark; + }); + }); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangePlatformBrightness() { + if (mounted) { + setState(() { + final platformDispatcher = View.of(context).platformDispatcher; + final platformBrightness = platformDispatcher.platformBrightness; + _isDarkMode = platformBrightness == Brightness.dark; + }); + } + super.didChangePlatformBrightness(); + } + + Future> _initDeps() async { + final deps = await Future.wait([ + SharedPreferences.getInstance(), + PackageInfo.fromPlatform(), + ]); + return deps; + } + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return AppKitModalTheme( + isDarkMode: _isDarkMode, + themeData: _themeData, + child: MaterialApp( + debugShowCheckedModeBanner: false, + title: StringConstants.pageTitle, + home: FutureBuilder>( + future: _initDeps(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return MyHomePage( + prefs: snapshot.data!.first as SharedPreferences, + packageInfo: snapshot.data!.last as PackageInfo, + toggleTheme: () => _toggleTheme(), + toggleBrightness: () => _toggleBrightness(), + ); + } + return const Center( + child: CircularProgressIndicator(), + ); + }, + ), + ), + ); + } + + void _toggleTheme() => setState(() { + _themeData = (_themeData == null) ? _customTheme : null; + }); + + void _toggleBrightness() => setState(() { + _isDarkMode = !_isDarkMode; + }); + + AppKitModalThemeData get _customTheme => AppKitModalThemeData( + lightColors: AppKitModalColors.lightMode.copyWith( + accent100: const Color.fromARGB(255, 30, 59, 236), + background100: const Color.fromARGB(255, 161, 183, 231), + // Main Modal's background color + background125: const Color.fromARGB(255, 206, 221, 255), + background175: const Color.fromARGB(255, 237, 241, 255), + inverse100: const Color.fromARGB(255, 233, 237, 236), + inverse000: const Color.fromARGB(255, 22, 18, 19), + // Main Modal's text + foreground100: const Color.fromARGB(255, 22, 18, 19), + // Secondary Modal's text + foreground150: const Color.fromARGB(255, 22, 18, 19), + ), + darkColors: AppKitModalColors.darkMode.copyWith( + accent100: const Color.fromARGB(255, 161, 183, 231), + background100: const Color.fromARGB(255, 30, 59, 236), + // Main Modal's background color + background125: const Color.fromARGB(255, 12, 23, 99), + background175: const Color.fromARGB(255, 78, 103, 230), + inverse100: const Color.fromARGB(255, 22, 18, 19), + inverse000: const Color.fromARGB(255, 233, 237, 236), + // Main Modal's text + foreground100: const Color.fromARGB(255, 233, 237, 236), + // Secondary Modal's text + foreground150: const Color.fromARGB(255, 233, 237, 236), + ), + radiuses: AppKitModalRadiuses.square, + ); +} diff --git a/packages/reown_appkit/example/modal/lib/models/accounts.dart b/packages/reown_appkit/example/modal/lib/models/accounts.dart new file mode 100644 index 0000000..309725e --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/models/accounts.dart @@ -0,0 +1,76 @@ +import 'package:flutter/foundation.dart'; + +class AccountDetails { + final String address; + final String chain; + + const AccountDetails({ + required this.address, + required this.chain, + }); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AccountDetails && + other.address == address && + other.chain == chain; + } + + @override + int get hashCode => address.hashCode ^ chain.hashCode; +} + +class Account { + final int id; + final String name; + final String mnemonic; + final String privateKey; + final List details; + + const Account({ + required this.id, + required this.name, + required this.mnemonic, + required this.privateKey, + required this.details, + }); + + Account copyWith({ + int? id, + String? name, + String? mnemonic, + String? privateKey, + List? details, + }) { + return Account( + id: id ?? this.id, + name: name ?? this.name, + mnemonic: mnemonic ?? this.mnemonic, + privateKey: privateKey ?? this.privateKey, + details: details ?? this.details, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Account && + other.id == id && + other.name == name && + other.mnemonic == mnemonic && + other.privateKey == privateKey && + listEquals(other.details, details); + } + + @override + int get hashCode { + return id.hashCode ^ + name.hashCode ^ + mnemonic.hashCode ^ + privateKey.hashCode ^ + details.hashCode; + } +} diff --git a/packages/reown_appkit/example/modal/lib/models/chain_metadata.dart b/packages/reown_appkit/example/modal/lib/models/chain_metadata.dart new file mode 100644 index 0000000..40f7c57 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/models/chain_metadata.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +enum ChainType { + eip155, + solana, + kadena, +} + +class ChainMetadata { + final Color color; + final ChainType type; + final AppKitModalNetworkInfo appKitNetworkInfo; + + const ChainMetadata({ + required this.color, + required this.type, + required this.appKitNetworkInfo, + }); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ChainMetadata && + other.color == color && + other.type == type && + other.appKitNetworkInfo == appKitNetworkInfo; + } + + @override + int get hashCode { + return color.hashCode ^ type.hashCode ^ appKitNetworkInfo.hashCode; + } +} diff --git a/packages/reown_appkit/example/modal/lib/models/page_data.dart b/packages/reown_appkit/example/modal/lib/models/page_data.dart new file mode 100644 index 0000000..1a9f4e4 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/models/page_data.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class PageData { + Widget page; + String title; + IconData icon; + + PageData({ + required this.page, + required this.title, + required this.icon, + }); +} diff --git a/packages/reown_appkit/example/modal/lib/services/contracts/aave_contract.dart b/packages/reown_appkit/example/modal/lib/services/contracts/aave_contract.dart new file mode 100644 index 0000000..f47c9d7 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/services/contracts/aave_contract.dart @@ -0,0 +1,312 @@ +class AAVESepoliaContract { + // AAVE on Sepolia + // https://sepolia.etherscan.io/token/0x88541670E55cC00bEEFD87eB59EDd1b7C511AC9a + static const contractAddress = '0x88541670E55cC00bEEFD87eB59EDd1b7C511AC9a'; + + static const contractABI = [ + { + 'inputs': [ + {'internalType': 'string', 'name': 'name', 'type': 'string'}, + {'internalType': 'string', 'name': 'symbol', 'type': 'string'}, + {'internalType': 'uint8', 'name': 'decimals', 'type': 'uint8'}, + {'internalType': 'address', 'name': 'owner', 'type': 'address'} + ], + 'stateMutability': 'nonpayable', + 'type': 'constructor' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'owner', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'spender', + 'type': 'address' + }, + { + 'indexed': false, + 'internalType': 'uint256', + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Approval', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'previousOwner', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'newOwner', + 'type': 'address' + } + ], + 'name': 'OwnershipTransferred', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'from', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'to', + 'type': 'address' + }, + { + 'indexed': false, + 'internalType': 'uint256', + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Transfer', + 'type': 'event' + }, + { + 'inputs': [], + 'name': 'DOMAIN_SEPARATOR', + 'outputs': [ + {'internalType': 'bytes32', 'name': '', 'type': 'bytes32'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'EIP712_REVISION', + 'outputs': [ + {'internalType': 'bytes', 'name': '', 'type': 'bytes'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'PERMIT_TYPEHASH', + 'outputs': [ + {'internalType': 'bytes32', 'name': '', 'type': 'bytes32'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'}, + {'internalType': 'address', 'name': 'spender', 'type': 'address'} + ], + 'name': 'allowance', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'approve', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'account', 'type': 'address'} + ], + 'name': 'balanceOf', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'decimals', + 'outputs': [ + {'internalType': 'uint8', 'name': '', 'type': 'uint8'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + { + 'internalType': 'uint256', + 'name': 'subtractedValue', + 'type': 'uint256' + } + ], + 'name': 'decreaseAllowance', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'addedValue', 'type': 'uint256'} + ], + 'name': 'increaseAllowance', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'account', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'mint', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'mint', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'name', + 'outputs': [ + {'internalType': 'string', 'name': '', 'type': 'string'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'} + ], + 'name': 'nonces', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'owner', + 'outputs': [ + {'internalType': 'address', 'name': '', 'type': 'address'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'}, + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'}, + {'internalType': 'uint256', 'name': 'deadline', 'type': 'uint256'}, + {'internalType': 'uint8', 'name': 'v', 'type': 'uint8'}, + {'internalType': 'bytes32', 'name': 'r', 'type': 'bytes32'}, + {'internalType': 'bytes32', 'name': 's', 'type': 'bytes32'} + ], + 'name': 'permit', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'renounceOwnership', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'symbol', + 'outputs': [ + {'internalType': 'string', 'name': '', 'type': 'string'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'totalSupply', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'recipient', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'transfer', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'sender', 'type': 'address'}, + {'internalType': 'address', 'name': 'recipient', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'transferFrom', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'newOwner', 'type': 'address'} + ], + 'name': 'transferOwnership', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + } + ]; +} diff --git a/packages/reown_appkit/example/modal/lib/services/contracts/test_contract.dart b/packages/reown_appkit/example/modal/lib/services/contracts/test_contract.dart new file mode 100644 index 0000000..4257d3d --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/services/contracts/test_contract.dart @@ -0,0 +1,279 @@ +class TESTContract { + // Alfreedoms2 ALF2 in Sepolia + // DEPLOY https://sepolia.etherscan.io/tx/0xebf287281abbc976b7cf6956a7f5f66338935d324c6453a350e3bb42ff7bd4e2 + // MINT https://sepolia.etherscan.io/tx/0x04a015504be7420a40a59936bfcca9302e55700fd00129059444539770fed5e7 + // CONTRACT https://sepolia.etherscan.io/address/0xBe60D05C11BD1C365849C824E0C2D880d2466eAF + // TRANSFERS https://sepolia.etherscan.io/token/0xbe60d05c11bd1c365849c824e0c2d880d2466eaf?a=0x59e2f66C0E96803206B6486cDb39029abAE834c0 + // SOURCIFY https://repo.sourcify.dev/contracts/full_match/11155111/0xBe60D05C11BD1C365849C824E0C2D880d2466eAF/ + static const contractAddress = '0xBe60D05C11BD1C365849C824E0C2D880d2466eAF'; + + static const contractABI = [ + { + 'inputs': [ + {'internalType': 'address', 'name': 'initialOwner', 'type': 'address'} + ], + 'stateMutability': 'nonpayable', + 'type': 'constructor' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'allowance', 'type': 'uint256'}, + {'internalType': 'uint256', 'name': 'needed', 'type': 'uint256'} + ], + 'name': 'ERC20InsufficientAllowance', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'sender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'balance', 'type': 'uint256'}, + {'internalType': 'uint256', 'name': 'needed', 'type': 'uint256'} + ], + 'name': 'ERC20InsufficientBalance', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'approver', 'type': 'address'} + ], + 'name': 'ERC20InvalidApprover', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'receiver', 'type': 'address'} + ], + 'name': 'ERC20InvalidReceiver', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'sender', 'type': 'address'} + ], + 'name': 'ERC20InvalidSender', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'} + ], + 'name': 'ERC20InvalidSpender', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'} + ], + 'name': 'OwnableInvalidOwner', + 'type': 'error' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'account', 'type': 'address'} + ], + 'name': 'OwnableUnauthorizedAccount', + 'type': 'error' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'owner', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'spender', + 'type': 'address' + }, + { + 'indexed': false, + 'internalType': 'uint256', + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Approval', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'previousOwner', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'newOwner', + 'type': 'address' + } + ], + 'name': 'OwnershipTransferred', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'from', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'to', + 'type': 'address' + }, + { + 'indexed': false, + 'internalType': 'uint256', + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Transfer', + 'type': 'event' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'}, + {'internalType': 'address', 'name': 'spender', 'type': 'address'} + ], + 'name': 'allowance', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'approve', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'account', 'type': 'address'} + ], + 'name': 'balanceOf', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'decimals', + 'outputs': [ + {'internalType': 'uint8', 'name': '', 'type': 'uint8'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'to', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'mint', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'name', + 'outputs': [ + {'internalType': 'string', 'name': '', 'type': 'string'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'owner', + 'outputs': [ + {'internalType': 'address', 'name': '', 'type': 'address'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'renounceOwnership', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'symbol', + 'outputs': [ + {'internalType': 'string', 'name': '', 'type': 'string'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'totalSupply', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'to', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'transfer', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'from', 'type': 'address'}, + {'internalType': 'address', 'name': 'to', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'transferFrom', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'newOwner', 'type': 'address'} + ], + 'name': 'transferOwnership', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + } + ]; +} diff --git a/packages/reown_appkit/example/modal/lib/services/contracts/test_data.dart b/packages/reown_appkit/example/modal/lib/services/contracts/test_data.dart new file mode 100644 index 0000000..6c40038 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/services/contracts/test_data.dart @@ -0,0 +1,166 @@ +// import 'dart:convert'; + +const String testSignData = 'Test AppKit data'; + +Map typedData() => + {'type': 'string', 'name': 'Key', 'value': 'Value'}; + +Map typeDataV3(int chainId) => { + 'types': { + 'EIP712Domain': [ + {'name': 'name', 'type': 'string'}, + {'name': 'version', 'type': 'string'}, + {'name': 'chainId', 'type': 'uint256'}, + {'name': 'verifyingContract', 'type': 'address'} + ], + 'Person': [ + {'name': 'name', 'type': 'string'}, + {'name': 'wallet', 'type': 'address'} + ], + 'Mail': [ + {'name': 'from', 'type': 'Person'}, + {'name': 'to', 'type': 'Person'}, + {'name': 'contents', 'type': 'string'} + ] + }, + 'primaryType': 'Mail', + 'domain': { + 'name': 'Ether Mail', + 'version': '1', + 'chainId': chainId, + 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + 'message': { + 'from': { + 'name': 'Cow', + 'wallet': '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' + }, + 'to': { + 'name': 'Bob', + 'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' + }, + 'contents': 'Hello, Bob!' + } + }; + +Map typeDataV4(int chainId) => { + 'types': { + 'EIP712Domain': [ + {'type': 'string', 'name': 'name'}, + {'type': 'string', 'name': 'version'}, + {'type': 'uint256', 'name': 'chainId'}, + {'type': 'address', 'name': 'verifyingContract'} + ], + 'Part': [ + {'name': 'account', 'type': 'address'}, + {'name': 'value', 'type': 'uint96'} + ], + 'Mint721': [ + {'name': 'tokenId', 'type': 'uint256'}, + {'name': 'tokenURI', 'type': 'string'}, + {'name': 'creators', 'type': 'Part[]'}, + {'name': 'royalties', 'type': 'Part[]'} + ] + }, + 'domain': { + 'name': 'Mint721', + 'version': '1', + 'chainId': chainId, + 'verifyingContract': '0x2547760120aed692eb19d22a5d9ccfe0f7872fce' + }, + 'primaryType': 'Mint721', + 'message': { + '@type': 'ERC721', + 'contract': '0x2547760120aed692eb19d22a5d9ccfe0f7872fce', + 'tokenId': '1', + 'uri': 'ipfs://ipfs/hash', + 'creators': [ + { + 'account': '0xc5eac3488524d577a1495492599e8013b1f91efa', + 'value': 10000 + } + ], + 'royalties': [], + 'tokenURI': 'ipfs://ipfs/hash' + } + }; + +/// KADENA /// + +// SignRequest createSignRequest({ +// required String networkId, +// required String signingPubKey, +// required String sender, +// String code = '"hello"', +// Map? data, +// List caps = const [], +// String chainId = '1', +// int gasLimit = 2000, +// double gasPrice = 1e-8, +// int ttl = 600, +// }) => +// SignRequest( +// code: code, +// data: data ?? {}, +// sender: sender, +// networkId: networkId, +// chainId: chainId, +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// signingPubKey: signingPubKey, +// ttl: ttl, +// caps: caps, +// ); + +// PactCommandPayload createPactCommandPayload({ +// required String networkId, +// required String sender, +// String code = '"hello"', +// Map? data, +// List signerCaps = const [], +// String chainId = '1', +// int gasLimit = 2000, +// double gasPrice = 1e-8, +// int ttl = 600, +// }) => +// PactCommandPayload( +// networkId: networkId, +// payload: CommandPayload( +// exec: ExecMessage( +// code: code, +// data: data ?? {}, +// ), +// ), +// signers: signerCaps, +// meta: CommandMetadata( +// chainId: chainId, +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// ttl: ttl, +// sender: sender, +// ), +// ); + +// QuicksignRequest createQuicksignRequest({ +// required String cmd, +// List sigs = const [], +// }) => +// QuicksignRequest( +// commandSigDatas: [ +// CommandSigData( +// cmd: cmd, +// sigs: sigs, +// ), +// ], +// ); + +// GetAccountsRequest createGetAccountsRequest({ +// required String account, +// }) => +// GetAccountsRequest( +// accounts: [ +// AccountRequest( +// account: account, +// ), +// ], +// ); diff --git a/packages/reown_appkit/example/modal/lib/services/contracts/usdt_contract.dart b/packages/reown_appkit/example/modal/lib/services/contracts/usdt_contract.dart new file mode 100644 index 0000000..f9f8bb9 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/services/contracts/usdt_contract.dart @@ -0,0 +1,466 @@ +class USDTContract { + // USDT-ERC20 + // https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7 + static const contractAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; + + static const contractABI = [ + { + 'constant': true, + 'inputs': [], + 'name': 'name', + 'outputs': [ + {'name': '', 'type': 'string'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_upgradedAddress', 'type': 'address'} + ], + 'name': 'deprecate', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_spender', 'type': 'address'}, + {'name': '_value', 'type': 'uint256'} + ], + 'name': 'approve', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'deprecated', + 'outputs': [ + {'name': '', 'type': 'bool'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_evilUser', 'type': 'address'} + ], + 'name': 'addBlackList', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'totalSupply', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_from', 'type': 'address'}, + {'name': '_to', 'type': 'address'}, + {'name': '_value', 'type': 'uint256'} + ], + 'name': 'transferFrom', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'upgradedAddress', + 'outputs': [ + {'name': '', 'type': 'address'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '', 'type': 'address'} + ], + 'name': 'balances', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'decimals', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'maximumFee', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': '_totalSupply', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [], + 'name': 'unpause', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '_maker', 'type': 'address'} + ], + 'name': 'getBlackListStatus', + 'outputs': [ + {'name': '', 'type': 'bool'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '', 'type': 'address'}, + {'name': '', 'type': 'address'} + ], + 'name': 'allowed', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'paused', + 'outputs': [ + {'name': '', 'type': 'bool'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': 'who', 'type': 'address'} + ], + 'name': 'balanceOf', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [], + 'name': 'pause', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'getOwner', + 'outputs': [ + {'name': '', 'type': 'address'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'owner', + 'outputs': [ + {'name': '', 'type': 'address'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'symbol', + 'outputs': [ + {'name': '', 'type': 'string'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_to', 'type': 'address'}, + {'name': '_value', 'type': 'uint256'} + ], + 'name': 'transfer', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': 'newBasisPoints', 'type': 'uint256'}, + {'name': 'newMaxFee', 'type': 'uint256'} + ], + 'name': 'setParams', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': 'amount', 'type': 'uint256'} + ], + 'name': 'issue', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': 'amount', 'type': 'uint256'} + ], + 'name': 'redeem', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '_owner', 'type': 'address'}, + {'name': '_spender', 'type': 'address'} + ], + 'name': 'allowance', + 'outputs': [ + {'name': 'remaining', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'basisPointsRate', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '', 'type': 'address'} + ], + 'name': 'isBlackListed', + 'outputs': [ + {'name': '', 'type': 'bool'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_clearedUser', 'type': 'address'} + ], + 'name': 'removeBlackList', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'MAX_UINT', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': 'newOwner', 'type': 'address'} + ], + 'name': 'transferOwnership', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_blackListedUser', 'type': 'address'} + ], + 'name': 'destroyBlackFunds', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'name': '_initialSupply', 'type': 'uint256'}, + {'name': '_name', 'type': 'string'}, + {'name': '_symbol', 'type': 'string'}, + {'name': '_decimals', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'constructor' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'Issue', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'Redeem', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': 'newAddress', 'type': 'address'} + ], + 'name': 'Deprecate', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': 'feeBasisPoints', 'type': 'uint256'}, + {'indexed': false, 'name': 'maxFee', 'type': 'uint256'} + ], + 'name': 'Params', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': '_blackListedUser', 'type': 'address'}, + {'indexed': false, 'name': '_balance', 'type': 'uint256'} + ], + 'name': 'DestroyedBlackFunds', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': '_user', 'type': 'address'} + ], + 'name': 'AddedBlackList', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': '_user', 'type': 'address'} + ], + 'name': 'RemovedBlackList', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': true, 'name': 'owner', 'type': 'address'}, + {'indexed': true, 'name': 'spender', 'type': 'address'}, + {'indexed': false, 'name': 'value', 'type': 'uint256'} + ], + 'name': 'Approval', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': true, 'name': 'from', 'type': 'address'}, + {'indexed': true, 'name': 'to', 'type': 'address'}, + {'indexed': false, 'name': 'value', 'type': 'uint256'} + ], + 'name': 'Transfer', + 'type': 'event' + }, + {'anonymous': false, 'inputs': [], 'name': 'Pause', 'type': 'event'}, + {'anonymous': false, 'inputs': [], 'name': 'Unpause', 'type': 'event'} + ]; +} diff --git a/packages/reown_appkit/example/modal/lib/services/eip155_service.dart b/packages/reown_appkit/example/modal/lib/services/eip155_service.dart new file mode 100644 index 0000000..cb7800a --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/services/eip155_service.dart @@ -0,0 +1,446 @@ +import 'dart:convert'; +import 'package:intl/intl.dart'; +import 'package:reown_appkit_example/services/contracts/usdt_contract.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +// ignore: depend_on_referenced_packages +import 'package:convert/convert.dart'; + +import 'package:reown_appkit_example/services/contracts/aave_contract.dart'; +import 'package:reown_appkit_example/services/contracts/test_data.dart'; + +enum EIP155UIMethods { + personalSign, + ethSendTransaction, + requestAccounts, + ethSignTypedData, + ethSignTypedDataV3, + ethSignTypedDataV4, + ethSignTransaction, + walletWatchAsset; + + String get name { + switch (this) { + case personalSign: + return 'personal_sign'; + case ethSignTypedDataV4: + return 'eth_signTypedData_v4'; + case ethSendTransaction: + return 'eth_sendTransaction'; + case requestAccounts: + return 'eth_requestAccounts'; + case ethSignTypedDataV3: + return 'eth_signTypedData_v3'; + case ethSignTypedData: + return 'eth_signTypedData'; + case ethSignTransaction: + return 'eth_signTransaction'; + case walletWatchAsset: + return 'wallet_watchAsset'; + } + } +} + +class EIP155 { + static EIP155UIMethods methodFromName(String name) { + switch (name) { + case 'personal_sign': + return EIP155UIMethods.personalSign; + case 'eth_signTypedData_v4': + return EIP155UIMethods.ethSignTypedDataV4; + case 'eth_sendTransaction': + return EIP155UIMethods.ethSendTransaction; + case 'eth_requestAccounts': + return EIP155UIMethods.requestAccounts; + case 'eth_signTypedData_v3': + return EIP155UIMethods.ethSignTypedDataV3; + case 'eth_signTypedData': + return EIP155UIMethods.ethSignTypedData; + case 'eth_signTransaction': + return EIP155UIMethods.ethSignTransaction; + case 'wallet_watchAsset': + return EIP155UIMethods.walletWatchAsset; + default: + throw Exception('Unrecognized method'); + } + } + + static Future callMethod({ + required AppKitModal appKit, + required String topic, + required EIP155UIMethods method, + required String chainId, + required String address, + }) { + final cid = int.parse(chainId); + switch (method) { + case EIP155UIMethods.requestAccounts: + return requestAccounts( + appKit: appKit, + ); + case EIP155UIMethods.personalSign: + return personalSign( + appKit: appKit, + message: testSignData, + ); + case EIP155UIMethods.ethSignTypedDataV3: + return ethSignTypedDataV3( + appKit: appKit, + data: jsonEncode(typeDataV3(cid)), + ); + case EIP155UIMethods.ethSignTypedData: + return ethSignTypedData( + appKit: appKit, + data: jsonEncode(typedData()), + ); + case EIP155UIMethods.ethSignTypedDataV4: + return ethSignTypedDataV4( + appKit: appKit, + data: jsonEncode(typeDataV4(cid)), + ); + case EIP155UIMethods.ethSignTransaction: + case EIP155UIMethods.ethSendTransaction: + return ethSendOrSignTransaction( + appKit: appKit, + method: method, + transaction: Transaction( + from: EthereumAddress.fromHex(address), + to: EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + value: EtherAmount.fromInt(EtherUnit.finney, 11), // == 0.011 + data: utf8.encode('0x'), // to make it work with some wallets + ), + ); + case EIP155UIMethods.walletWatchAsset: + return walletWatchAsset( + appKit: appKit, + ); + } + } + + static Future requestAccounts({ + required AppKitModal appKit, + }) async { + return await appKit.request( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + request: SessionRequestParams( + method: EIP155UIMethods.requestAccounts.name, + params: [], + ), + ); + } + + static Future personalSign({ + required AppKitModal appKit, + required String message, + }) async { + final bytes = utf8.encode(message); + final encoded = hex.encode(bytes); + + return await appKit.request( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + request: SessionRequestParams( + method: EIP155UIMethods.personalSign.name, + params: [ + '0x$encoded', + appKit.session!.address!, + ], + ), + ); + } + + static Future ethSignTypedData({ + required AppKitModal appKit, + required String data, + }) async { + return await appKit.request( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + request: SessionRequestParams( + method: EIP155UIMethods.ethSignTypedData.name, + params: [ + data, + appKit.session!.address!, + ], + ), + ); + } + + static Future ethSignTypedDataV3({ + required AppKitModal appKit, + required String data, + }) async { + return await appKit.request( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + request: SessionRequestParams( + method: EIP155UIMethods.ethSignTypedDataV3.name, + params: [ + data, + appKit.session!.address!, + ], + ), + ); + } + + static Future ethSignTypedDataV4({ + required AppKitModal appKit, + required String data, + }) async { + return await appKit.request( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + request: SessionRequestParams( + method: EIP155UIMethods.ethSignTypedDataV4.name, + params: [ + data, + appKit.session!.address!, + ], + ), + ); + } + + static Future ethSendOrSignTransaction({ + required AppKitModal appKit, + required Transaction transaction, + required EIP155UIMethods method, + }) async { + return await appKit.request( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + request: SessionRequestParams( + method: method.name, + params: [ + transaction.toJson(), + ], + ), + ); + } + + static Future walletWatchAsset({ + required AppKitModal appKit, + }) async { + return await appKit.request( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + request: SessionRequestParams( + method: EIP155UIMethods.walletWatchAsset.name, + params: { + 'type': 'ERC20', + 'options': { + 'address': '0xcf664087a5bb0237a0bad6742852ec6c8d69a27a', + 'symbol': 'WONE', + 'decimals': 18, + 'image': + 'https://s2.coinmarketcap.com/static/img/coins/64x64/11696.png' + } + }, + ), + ); + } + + // Example of calling `transfer` function from AAVE token Smart Contract + static Future callTestSmartContract({ + required AppKitModal appKit, + required String action, + }) async { + // Create DeployedContract object using contract's ABI and address + final deployedContract = DeployedContract( + ContractAbi.fromJson( + jsonEncode(AAVESepoliaContract.contractABI), + 'AAVE Token (Sepolia)', + ), + EthereumAddress.fromHex(AAVESepoliaContract.contractAddress), + ); + + switch (action) { + case 'read': + return _readSmartContract( + appKit: appKit, + contract: deployedContract, + ); + case 'write': + // return await appKit.requestWriteContract( + // topic: appKit.session?.topic ?? '', + // chainId: 'eip155:11155111', + // deployedContract: deployedContract, + // functionName: 'subscribe', + // parameters: [], + // transaction: Transaction( + // from: EthereumAddress.fromHex(appKit.session!.address!), + // value: EtherAmount.fromInt(EtherUnit.finney, 1), + // ), + // ); + // we first call `decimals` function, which is a read function, + // to check how much decimal we need to use to parse the amount value + final decimals = await appKit.requestReadContract( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + deployedContract: deployedContract, + functionName: 'decimals', + ); + final d = (decimals.first as BigInt); + final requestValue = _formatValue(0.01, decimals: d); + // now we call `transfer` write function with the parsed value. + return appKit.requestWriteContract( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + deployedContract: deployedContract, + functionName: 'transfer', + transaction: Transaction( + from: EthereumAddress.fromHex(appKit.session!.address!), + ), + parameters: [ + EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + requestValue, // == 0.12 + ], + ); + // payable function with no parameters such as: + // { + // "inputs": [], + // "name": "functionName", + // "outputs": [], + // "stateMutability": "payable", + // "type": "function" + // }, + // return appKit.requestWriteContract( + // topic: appKit.session?.topic ?? '', + // chainId: 'eip155:11155111', + // rpcUrl: 'https://ethereum-sepolia.publicnode.com', + // deployedContract: deployedContract, + // functionName: 'functionName', + // transaction: Transaction( + // from: EthereumAddress.fromHex(appKit.session!.address!), + // value: EtherAmount.fromInt(EtherUnit.finney, 1), + // ), + // parameters: [], + // ); + default: + return Future.value(); + } + } + + // Example of calling `transfer` function from USDT token Smart Contract + static Future callUSDTSmartContract({ + required AppKitModal appKit, + required String action, + }) async { + // Create DeployedContract object using contract's ABI and address + final deployedContract = DeployedContract( + ContractAbi.fromJson( + jsonEncode(USDTContract.contractABI), + 'Tether USD', + ), + EthereumAddress.fromHex(USDTContract.contractAddress), + ); + + switch (action) { + case 'read': + return _readSmartContract( + appKit: appKit, + contract: deployedContract, + ); + case 'write': + // we first call `decimals` function, which is a read function, + // to check how much decimal we need to use to parse the amount value + final decimals = await appKit.requestReadContract( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + deployedContract: deployedContract, + functionName: 'decimals', + ); + final d = (decimals.first as BigInt); + final requestValue = _formatValue(0.23, decimals: d); + // now we call `transfer` write function with the parsed value. + return appKit.requestWriteContract( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + deployedContract: deployedContract, + functionName: 'transfer', + transaction: Transaction( + from: EthereumAddress.fromHex(appKit.session!.address!), + ), + parameters: [ + EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + requestValue, // == 0.23 + ], + ); + default: + return Future.value(); + } + } + + static Future _readSmartContract({ + required AppKitModal appKit, + required DeployedContract contract, + }) async { + final results = await Future.wait([ + // results[0] + appKit.requestReadContract( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + deployedContract: contract, + functionName: 'name', + ), + // results[1] + appKit.requestReadContract( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + deployedContract: contract, + functionName: 'totalSupply', + ), + // results[2] + appKit.requestReadContract( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + deployedContract: contract, + functionName: 'balanceOf', + parameters: [ + EthereumAddress.fromHex(appKit.session!.address!), + ], + ), + // results[4] + appKit.requestReadContract( + topic: appKit.session!.topic, + chainId: appKit.selectedChain!.chainId, + deployedContract: contract, + functionName: 'decimals', + ), + ]); + + // + final name = (results[0].first as String); + final multiplier = _multiplier(results[3].first); + final total = (results[1].first as BigInt) / BigInt.from(multiplier); + final balance = (results[2].first as BigInt) / BigInt.from(multiplier); + final formatter = NumberFormat('#,##0.00000', 'en_US'); + + return { + 'name': name, + 'totalSupply': formatter.format(total), + 'balance': formatter.format(balance), + }; + } + + static BigInt _formatValue(num value, {required BigInt decimals}) { + final multiplier = _multiplier(decimals); + final result = EtherAmount.fromInt( + EtherUnit.ether, + (value * multiplier).toInt(), + ); + return result.getInEther; + } + + static int _multiplier(BigInt decimals) { + final d = decimals.toInt(); + final pad = '1'.padRight(d + 1, '0'); + return int.parse(pad); + } +} diff --git a/packages/reown_appkit/example/modal/lib/services/siwe_service.dart b/packages/reown_appkit/example/modal/lib/services/siwe_service.dart new file mode 100644 index 0000000..45dbaad --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/services/siwe_service.dart @@ -0,0 +1,132 @@ +import 'package:flutter/foundation.dart'; +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:reown_appkit/modal/utils/core_utils.dart'; + +import 'package:reown_appkit_example/utils/dart_defines.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SIWESampleWebService { + Map? _headers; + + SIWESampleWebService() { + _headers = { + ...CoreUtils.getAPIHeaders( + DartDefines.appKitProjectId, + ), + 'Content-Type': 'application/json', + }; + } + + Future _checkHeaders() async { + final instance = await SharedPreferences.getInstance(); + final headers = instance.getString('appkit_siwe_headers'); + if (headers != null) { + _headers = { + ...(jsonDecode(headers) as Map), + 'Content-Type': 'application/json', + }; + } + } + + Future _persistHeaders() async { + final instance = await SharedPreferences.getInstance(); + await instance.setString('appkit_siwe_headers', jsonEncode(_headers)); + } + + Future> getNonce() async { + try { + final response = await http.get( + Uri.parse('${DartDefines.authApiUrl}/auth/v1/nonce'), + headers: _headers, + ); + debugPrint('[SIWESERVICE] getNonce() => ${response.body}'); + final nonceRes = jsonDecode(response.body) as Map; + final newToken = nonceRes['token'] as String; + _headers!['Authorization'] = 'Bearer $newToken'; + await _persistHeaders(); + // Persist the newToken so it can be used again with getSession() even if the user terminated the app + return nonceRes; + } catch (error) { + debugPrint('[SIWESERVICE] ⛔️ getNonce() => ${error.toString()}'); + rethrow; + } + } + + Future> getSession() async { + try { + await _checkHeaders(); + final response = await http.get( + Uri.parse('${DartDefines.authApiUrl}/auth/v1/me'), + headers: _headers, + ); + debugPrint('[SIWESERVICE] getSession() => ${response.body}'); + return jsonDecode(response.body) as Map; + } catch (error) { + debugPrint('[SIWESERVICE] ⛔️ getSession() => ${error.toString()}'); + rethrow; + } + } + + Future> verifyMessage( + Map payload, { + required String domain, + }) async { + try { + final uri = Uri.parse('${DartDefines.authApiUrl}/auth/v1/authenticate'); + final response = await http.post( + uri.replace(queryParameters: {'domain': domain}), + headers: _headers, + body: jsonEncode(payload), + ); + debugPrint('[SIWESERVICE] verifyMessage() => ${response.body}'); + final authenticateRes = jsonDecode(response.body) as Map; + final newToken = authenticateRes['token'] as String; + _headers!['Authorization'] = 'Bearer $newToken'; + await _persistHeaders(); + // Persist the newToken so it can be used again with getSession() even if the user terminated the app + return authenticateRes; + } catch (error) { + debugPrint('[SIWESERVICE] ⛔️ verifyMessage() => ${error.toString()}'); + rethrow; + } + } + + Future> updateUser(Map data) async { + try { + final response = await http.post( + Uri.parse('${DartDefines.authApiUrl}/auth/v1/update-user'), + headers: _headers, + body: json.encode({'metadata': data}), + ); + debugPrint('[SIWESERVICE] updateUser() => ${response.body}'); + return jsonDecode(response.body) as Map; + } catch (error) { + debugPrint('[SIWESERVICE] ⛔️ updateUser() => ${error.toString()}'); + rethrow; + } + } + + Future> signOut() async { + try { + final response = await http.post( + Uri.parse('${DartDefines.authApiUrl}/auth/v1/sign-out'), + headers: _headers, + ); + debugPrint('[SIWESERVICE] signOut() => ${response.body}'); + final result = jsonDecode(response.body) as Map; + _headers = { + ...CoreUtils.getAPIHeaders( + DartDefines.appKitProjectId, + ), + 'Content-Type': 'application/json', + }; + await _persistHeaders(); + return result; + } catch (error) { + debugPrint('[SIWESERVICE] ⛔️ signOut() => ${error.toString()}'); + rethrow; + } + } +} diff --git a/packages/reown_appkit/example/modal/lib/utils/constants.dart b/packages/reown_appkit/example/modal/lib/utils/constants.dart new file mode 100644 index 0000000..8d0c8f0 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/utils/constants.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +class StyleConstants { + static const Color primaryColor = Color.fromARGB(255, 16, 165, 206); + static const Color secondaryColor = Color(0xFF1A1A1A); + static const Color grayColor = Color.fromARGB(255, 180, 180, 180); + static const Color titleTextColor = Color(0xFFFFFFFF); + + // Linear + static const double linear8 = 8; + static const double linear16 = 16; + static const double linear24 = 24; + static const double linear32 = 32; + static const double linear48 = 48; + static const double linear40 = 40; + static const double linear56 = 56; + static const double linear72 = 72; + static const double linear80 = 80; + + // Magic Number + static const double magic10 = 10; + static const double magic14 = 14; + static const double magic20 = 20; + static const double magic40 = 40; + static const double magic64 = 64; + + // Width + static const double maxWidth = 400; + + // Text styles + static const TextStyle titleText = TextStyle( + color: Colors.black, + fontSize: linear32, + fontWeight: FontWeight.w600, + ); + static const TextStyle subtitleText = TextStyle( + color: Colors.black, + fontSize: linear24, + fontWeight: FontWeight.w600, + ); + static const TextStyle buttonText = TextStyle( + color: Colors.black, + fontSize: magic14, + fontWeight: FontWeight.w600, + ); +} + +class StringConstants { + static const String close = 'Close'; + static const String pageTitle = 'AppKit Flutter'; + static const String copiedToClipboard = 'Copied to clipboard'; + static const String sessionTopic = 'Session Topic: '; + static const String methods = 'Methods'; + static const String events = 'Events'; +} diff --git a/packages/reown_appkit/example/modal/lib/utils/dart_defines.dart b/packages/reown_appkit/example/modal/lib/utils/dart_defines.dart new file mode 100644 index 0000000..04d687c --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/utils/dart_defines.dart @@ -0,0 +1,14 @@ +class DartDefines { + static const String projectId = String.fromEnvironment( + 'PROJECT_ID', + ); + static const String appKitAuth = String.fromEnvironment( + 'APPKIT_AUTH', + ); + static const String appKitProjectId = String.fromEnvironment( + 'APPKIT_PROJECT_ID', + ); + static const String authApiUrl = String.fromEnvironment( + 'AUTH_SERVICE_URL', + ); +} diff --git a/packages/reown_appkit/example/modal/lib/utils/styles.dart b/packages/reown_appkit/example/modal/lib/utils/styles.dart new file mode 100644 index 0000000..7e4719f --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/utils/styles.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +ButtonStyle buttonStyle(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + return ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return AppKitModalTheme.colorsOf(context).background225; + } + return AppKitModalTheme.colorsOf(context).accent100; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: states.contains(MaterialState.disabled) + ? BorderSide(color: themeColors.grayGlass005, width: 1.0) + : BorderSide(color: themeColors.grayGlass010, width: 1.0), + borderRadius: borderRadius(context), + ); + }, + ), + textStyle: MaterialStateProperty.resolveWith( + (states) { + return AppKitModalTheme.getDataOf(context).textStyles.small600.copyWith( + color: (states.contains(MaterialState.disabled)) + ? AppKitModalTheme.colorsOf(context).foreground300 + : AppKitModalTheme.colorsOf(context).inverse100, + ); + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + return (states.contains(MaterialState.disabled)) + ? AppKitModalTheme.colorsOf(context).foreground300 + : AppKitModalTheme.colorsOf(context).inverse100; + }, + ), + ); +} + +BorderRadiusGeometry borderRadius(BuildContext context) { + final radiuses = AppKitModalTheme.radiusesOf(context); + return radiuses.isSquare() + ? const BorderRadius.all(Radius.zero) + : radiuses.isCircular() + ? BorderRadius.circular(1000.0) + : BorderRadius.circular(8.0); +} diff --git a/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart b/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart new file mode 100644 index 0000000..aa9595b --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart @@ -0,0 +1,292 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +// ignore: depend_on_referenced_packages +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:reown_appkit/version.dart' as apkt; + +import 'package:reown_appkit/reown_appkit.dart'; + +class DebugDrawer extends StatefulWidget { + const DebugDrawer({ + super.key, + required this.toggleOverlay, + required this.toggleBrightness, + required this.toggleTheme, + }); + final VoidCallback toggleOverlay; + final VoidCallback toggleBrightness; + final VoidCallback toggleTheme; + + @override + State createState() => _DebugDrawerState(); +} + +class _DebugDrawerState extends State with WidgetsBindingObserver { + late SharedPreferences prefs; + bool _analyticsValue = false; + bool _emailWalletValue = false; + bool _siweAuthValue = false; + bool _analyticsValueBkp = false; + bool _emailWalletValueBkp = false; + bool _siweAuthValueBkp = false; + bool _hasUpdates = false; + + @override + void didChangePlatformBrightness() { + if (mounted) { + setState(() {}); + } + super.didChangePlatformBrightness(); + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + SharedPreferences.getInstance().then((instance) { + setState(() { + prefs = instance; + _analyticsValue = prefs.getBool('appkit_analytics') ?? true; + _analyticsValueBkp = _analyticsValue; + _emailWalletValue = prefs.getBool('appkit_email_wallet') ?? true; + _emailWalletValueBkp = _emailWalletValue; + _siweAuthValue = prefs.getBool('appkit_siwe_auth') ?? true; + _siweAuthValueBkp = _siweAuthValue; + }); + }); + }); + } + + void _updateValue(String key, bool value) async { + await prefs.setBool(key, value); + _hasUpdates = true; + setState(() {}); + } + + Future _restore() async { + await prefs.setBool('appkit_analytics', _analyticsValueBkp); + await prefs.setBool('appkit_email_wallet', _emailWalletValueBkp); + await prefs.setBool('appkit_siwe_auth', _siweAuthValueBkp); + } + + @override + Widget build(BuildContext context) { + final isCustom = AppKitModalTheme.isCustomTheme(context); + return SafeArea( + child: Column( + children: [ + Expanded( + child: ListView( + children: [ + ListTile( + leading: Icon( + Icons.logo_dev_rounded, + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + title: const Text('Analytics view'), + titleTextStyle: TextStyle( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + onTap: () { + widget.toggleOverlay(); + }, + ), + ListTile( + leading: isCustom + ? Icon( + Icons.yard, + color: + AppKitModalTheme.colorsOf(context).foreground100, + ) + : Icon( + Icons.yard_outlined, + color: + AppKitModalTheme.colorsOf(context).foreground100, + ), + title: isCustom + ? const Text('Custom theme') + : const Text('Default theme'), + titleTextStyle: TextStyle( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + trailing: Switch( + value: isCustom, + activeColor: AppKitModalTheme.colorsOf(context).accent100, + onChanged: (value) { + widget.toggleTheme(); + }, + ), + ), + ListTile( + leading: AppKitModalTheme.maybeOf(context)?.isDarkMode ?? + false + ? Icon( + Icons.dark_mode_outlined, + color: + AppKitModalTheme.colorsOf(context).foreground100, + ) + : Icon( + Icons.light_mode_outlined, + color: + AppKitModalTheme.colorsOf(context).foreground100, + ), + title: AppKitModalTheme.maybeOf(context)?.isDarkMode ?? false + ? const Text('Dark theme') + : const Text('Light theme'), + titleTextStyle: TextStyle( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + trailing: Switch( + value: + AppKitModalTheme.maybeOf(context)?.isDarkMode ?? false, + activeColor: AppKitModalTheme.colorsOf(context).accent100, + onChanged: (value) { + widget.toggleBrightness(); + }, + ), + ), + const SizedBox.square(dimension: 10.0), + const Divider(height: 1.0, indent: 12.0, endIndent: 12.0), + const SizedBox.square(dimension: 10.0), + Center( + child: Text( + 'Will require app to restart', + style: TextStyle( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ), + const SizedBox.square(dimension: 10.0), + ListTile( + leading: Icon( + Icons.speaker_notes_rounded, + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + title: const Text('Analytics On'), + titleTextStyle: TextStyle( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + trailing: Switch( + value: _analyticsValue, + activeColor: AppKitModalTheme.colorsOf(context).accent100, + onChanged: (value) { + _analyticsValue = value; + _updateValue('appkit_analytics', value); + }, + ), + ), + ListTile( + leading: Icon( + Icons.email_rounded, + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + title: const Text('Email Wallet On'), + titleTextStyle: TextStyle( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + trailing: Switch( + value: _emailWalletValue, + activeColor: AppKitModalTheme.colorsOf(context).accent100, + onChanged: (value) { + _emailWalletValue = value; + _updateValue('appkit_email_wallet', value); + }, + ), + ), + ListTile( + leading: Icon( + Icons.account_balance_wallet, + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + title: const Text('1-CA + SIWE On'), + titleTextStyle: TextStyle( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + trailing: Switch( + value: _siweAuthValue, + activeColor: AppKitModalTheme.colorsOf(context).accent100, + onChanged: (value) { + _siweAuthValue = value; + _updateValue('appkit_siwe_auth', value); + }, + ), + ), + ], + ), + ), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + return InkWell( + onTap: () { + Clipboard.setData( + ClipboardData( + text: + '${snapshot.data?.packageName} v${snapshot.data?.version ?? ''} (${snapshot.data?.buildNumber})\n' + 'AppKit v${apkt.packageVersion}\n' + 'Core v$packageVersion', + ), + ); + }, + child: Text( + '${snapshot.data?.packageName} v${snapshot.data?.version ?? ''} (${snapshot.data?.buildNumber})\n' + 'AppKit v${apkt.packageVersion}\n' + 'Core v$packageVersion', + style: TextStyle( + fontSize: 12.0, + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ); + }, + ), + const SizedBox.square(dimension: 10.0), + const Divider(height: 1.0, indent: 12.0, endIndent: 12.0), + ListTile( + leading: Icon( + Icons.close, + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + title: const Text('Close'), + titleTextStyle: TextStyle( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + onTap: () { + if (_hasUpdates) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: const Text( + 'Application will be closed to make changes'), + actions: [ + TextButton( + onPressed: () { + _restore().then((value) => Navigator.pop(context)); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + exit(0); + }, + child: const Text('Ok'), + ) + ], + ); + }, + ); + } else { + // restore and pop + _restore().then((value) => Navigator.pop(context)); + } + }, + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/example/modal/lib/widgets/logger_widget.dart b/packages/reown_appkit/example/modal/lib/widgets/logger_widget.dart new file mode 100644 index 0000000..8bb0855 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/widgets/logger_widget.dart @@ -0,0 +1,225 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; + +class DraggableCard extends StatefulWidget { + final OverlayController overlayController; + const DraggableCard({ + super.key, + required this.overlayController, + }); + + @override + State createState() => _DraggableCardState(); +} + +class _DraggableCardState extends State { + final List _logs = []; + // + @override + void initState() { + super.initState(); + analyticsService.instance.events.listen(_eventsListener); + } + + void _eventsListener(event) { + if (!mounted) return; + _logs.add( + Text( + '=> $event', + style: const TextStyle( + color: Colors.white, + fontSize: 12.0, + ), + ), + ); + setState(() {}); + } + + @override + void dispose() { + widget.overlayController.remove(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return Card( + elevation: 6.0, + color: Colors.black87, + clipBehavior: Clip.antiAlias, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width, + height: 200.0, + child: Row( + children: [ + Expanded( + child: SizedBox( + height: 200.0, + child: ListView( + reverse: true, + padding: const EdgeInsets.all(6.0), + children: _logs.reversed.toList(), + ), + ), + ), + GestureDetector( + onPanUpdate: (details) { + widget.overlayController.alignChildTo( + details.globalPosition, + size * 0.5, + ); + }, + onPanEnd: (_) { + // widget.overlayController.alignToScreenEdge(); + }, + child: Container( + width: 25.0, + color: Colors.black, + child: const Center( + child: Icon( + Icons.drag_indicator_rounded, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +class OverlayController extends AnimatedOverlay { + OverlayController(super.duration); + OverlayEntry? _entry; + final _defaultAlign = const Alignment(0.0, -30.0); + Alignment align = const Alignment(0.0, -30.0); + Animation? alignAnimation; + + OverlayEntry createAlignOverlay(Widget child) { + return OverlayEntry( + maintainState: true, + builder: (_) => CustomAlign( + animation: alignAnimation ?? AlwaysStoppedAnimation(align), + child: child, + ), + ); + } + + void insert(BuildContext context) { + _entry = createAlignOverlay(DraggableCard(overlayController: this)); + Overlay.of(context).insert(_entry!); + } + + void show(BuildContext context) { + if (_entry != null) { + toggle(); + } else { + insert(context); + } + } + + void remove() { + _entry?.remove(); + _entry?.dispose(); + _entry = null; + } + + void alignChildTo(Offset globalPosition, Size size) { + double dx = (globalPosition.dx - size.width) / size.width; + double dy = (globalPosition.dy - size.height) / size.height; + + dx = dx.abs() < 1 ? dx : dx / dx.abs(); + dy = dy.abs() < 1 ? dy : dy / dy.abs(); + + final newAlign = Alignment(dx, dy); + + if (align == newAlign) return; + + alignAnimation = createAnimation(begin: align, end: newAlign); + + align = newAlign; + + controller.forward(); + _entry?.markNeedsBuild(); + } + + void toggle() { + if (align == Alignment.center) { + alignAnimation = createAnimation( + begin: align, + end: _defaultAlign, + ); + align = _defaultAlign; + } else { + alignAnimation = createAnimation( + begin: align, + end: Alignment.center, + ); + align = Alignment.center; + } + + controller.forward(); + _entry?.markNeedsBuild(); + } +} + +abstract class AnimatedOverlay extends TickerProvider { + @override + Ticker createTicker(onTick) => Ticker(onTick); + + late final AnimationController controller; + + AnimatedOverlay(Duration duration) : super() { + controller = AnimationController( + vsync: this, + duration: duration, + ); + } + + Animation createAnimation({ + required T begin, + required T end, + Curve curve = Curves.easeInOutCubic, + }) { + controller.reset(); + if (begin == end) { + return AlwaysStoppedAnimation(end); + } else { + return Tween(begin: begin, end: end).animate( + CurvedAnimation( + parent: controller, + curve: curve, + ), + ); + } + } +} + +class CustomAlign extends AnimatedWidget { + final Widget child; + final Animation animation; + + const CustomAlign({ + super.key, + required this.child, + required this.animation, + }) : super(listenable: animation); + + @override + Widget build(BuildContext context) { + return Align( + alignment: animation.value, + child: child, + ); + } +} diff --git a/packages/reown_appkit/example/modal/lib/widgets/method_dialog.dart b/packages/reown_appkit/example/modal/lib/widgets/method_dialog.dart new file mode 100644 index 0000000..54cc01f --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/widgets/method_dialog.dart @@ -0,0 +1,92 @@ +import 'dart:convert'; + +import 'package:fl_toast/fl_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:reown_appkit_example/utils/constants.dart'; + +class MethodDialog extends StatefulWidget { + static Future show( + BuildContext context, + String method, + Future response, + ) async { + await showDialog( + context: context, + builder: (BuildContext context) { + return MethodDialog( + method: method, + response: response, + ); + }, + ); + } + + const MethodDialog({ + super.key, + required this.method, + required this.response, + }); + + final String method; + final Future response; + + @override + MethodDialogState createState() => MethodDialogState(); +} + +class MethodDialogState extends State { + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(widget.method), + content: FutureBuilder( + future: widget.response, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + final String t = jsonEncode(snapshot.data); + debugPrint('[ExampleApp] result $t'); + return InkWell( + onTap: () => _copyToClipboard(t), + child: Text(t), + ); + } else if (snapshot.hasError) { + return InkWell( + onTap: () => _copyToClipboard(snapshot.data.toString()), + child: Text(snapshot.error.toString()), + ); + } else { + return const SizedBox( + width: StyleConstants.linear48, + height: StyleConstants.linear48, + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + }, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text( + StringConstants.close, + ), + ), + ], + ); + } + + void _copyToClipboard(String text) { + Clipboard.setData(ClipboardData(text: text)).then( + (_) => showPlatformToast( + child: const Text( + StringConstants.copiedToClipboard, + ), + context: context, + ), + ); + } +} diff --git a/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart b/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart new file mode 100644 index 0000000..c1cda0a --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart @@ -0,0 +1,455 @@ +import 'dart:convert'; + +import 'package:fl_toast/fl_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:reown_appkit_example/utils/styles.dart'; + +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit_example/utils/constants.dart'; +import 'package:reown_appkit_example/services/eip155_service.dart'; +import 'package:reown_appkit_example/widgets/method_dialog.dart'; + +class SessionWidget extends StatefulWidget { + const SessionWidget({super.key, required this.appKit}); + + final AppKitModal appKit; + + @override + SessionWidgetState createState() => SessionWidgetState(); +} + +class SessionWidgetState extends State { + @override + Widget build(BuildContext context) { + final session = widget.appKit.session!; + String iconImage = ''; + if ((session.peer?.metadata.icons ?? []).isNotEmpty) { + iconImage = session.peer?.metadata.icons.first ?? ''; + } + final List children = [ + const SizedBox(height: 8.0), + Row( + children: [ + if (iconImage.isNotEmpty) + Row( + children: [ + CircleAvatar( + radius: 18.0, + backgroundImage: NetworkImage(iconImage), + ), + const SizedBox(width: 8.0), + ], + ), + Expanded( + child: Row( + children: [ + Expanded( + child: Text( + session.connectedWalletName ?? '', + style: AppKitModalTheme.getDataOf(context) + .textStyles + .paragraph600 + .copyWith( + color: + AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ), + Visibility( + visible: !session.sessionService.isMagic, + child: IconButton( + onPressed: () { + widget.appKit.launchConnectedWallet(); + }, + icon: const Icon(Icons.open_in_new), + ), + ) + ], + ), + ), + ], + ), + const SizedBox(height: 8.0), + // TOPIC LABEL + Visibility( + visible: session.topic != null, + child: Column( + children: [ + Text( + StringConstants.sessionTopic, + style: AppKitModalTheme.getDataOf(context) + .textStyles + .small600 + .copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + Text( + '${session.topic}', + style: AppKitModalTheme.getDataOf(context) + .textStyles + .small400 + .copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ], + ), + ), + Column( + children: _buildSupportedChainsWidget(), + ), + const SizedBox(height: StyleConstants.linear8), + ]; + + // Get current active account + final accounts = session.getAccounts() ?? []; + final chainId = widget.appKit.selectedChain?.chainId ?? ''; + final namespace = AppKitModalNetworks.getNamespaceForChainId(chainId); + final chainsNamespaces = NamespaceUtils.getChainsFromAccounts(accounts); + if (chainsNamespaces.contains('$namespace:$chainId')) { + final account = accounts.firstWhere( + (account) => account.contains('$namespace:$chainId'), + ); + children.add(_buildAccountWidget(account)); + } + children.add( + Column( + children: [ + const SizedBox.square(dimension: 8.0), + Text( + 'Stored session:', + style: AppKitModalTheme.getDataOf(context) + .textStyles + .small600 + .copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + InkWell( + onTap: () => Clipboard.setData( + ClipboardData( + text: jsonEncode(widget.appKit.session?.toMap()), + ), + ).then( + (_) => showPlatformToast( + child: const Text(StringConstants.copiedToClipboard), + context: context, + ), + ), + child: Text( + const JsonEncoder.withIndent(' ') + .convert(widget.appKit.session?.toMap()), + style: AppKitModalTheme.getDataOf(context) + .textStyles + .small400 + .copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ), + ], + ), + ); + + return Padding( + padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0), + child: Column(children: children), + ); + } + + Widget _buildAccountWidget(String account) { + // final chainId = NamespaceUtils.getChainFromAccount(account); + // final chainMetadata = ChainDataWrapper.getChainMetadataFromChain( + // chainId.split(':').first, + // chainId.split(':').last, + // ); + + final List children = [ + Text( + // chainMetadata.appKitNetworkInfo.name, + widget.appKit.selectedChain?.name ?? 'Unsupported chain', + style: AppKitModalTheme.getDataOf(context).textStyles.title600.copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + const SizedBox(height: StyleConstants.linear8), + Text( + NamespaceUtils.getAccount(account), + style: AppKitModalTheme.getDataOf(context).textStyles.small400.copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ]; + + children.addAll([ + const SizedBox(height: StyleConstants.linear8), + Text( + StringConstants.methods, + style: AppKitModalTheme.getDataOf(context) + .textStyles + .paragraph600 + .copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ]); + children.addAll(_buildChainMethodButtons(account)); + + children.addAll([ + const SizedBox(height: StyleConstants.linear8), + Text( + StringConstants.events, + style: AppKitModalTheme.getDataOf(context) + .textStyles + .paragraph600 + .copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ]); + children.add(_buildChainEventsTiles()); + + return Container( + padding: const EdgeInsets.all(StyleConstants.linear8), + margin: const EdgeInsets.symmetric(vertical: StyleConstants.linear8), + decoration: BoxDecoration( + border: Border.all(color: AppKitModalTheme.colorsOf(context).accent100), + borderRadius: const BorderRadius.all( + Radius.circular(StyleConstants.linear8), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: children, + ), + ); + } + + List _buildChainMethodButtons( + String address, + ) { + // Add Methods + final approvedMethods = widget.appKit.getApprovedMethods() ?? []; + if (approvedMethods.isEmpty) { + return [ + Text( + 'No methods approved', + style: + AppKitModalTheme.getDataOf(context).textStyles.small400.copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ) + ]; + } + final usableMethods = EIP155UIMethods.values.map((e) => e.name).toList(); + // + final List children = []; + for (final method in approvedMethods) { + final implemented = usableMethods.contains(method); + children.add( + Container( + height: StyleConstants.linear40, + width: double.infinity, + margin: const EdgeInsets.symmetric(vertical: StyleConstants.linear8), + child: ElevatedButton( + onPressed: implemented + ? () async { + widget.appKit.launchConnectedWallet(); + final future = callChainMethod( + // chainMetadata.type, + EIP155.methodFromName(method) + // chainMetadata, + // address, + ); + MethodDialog.show(context, method, future); + } + : null, + style: buttonStyle(context), + child: Text(method), + ), + ), + ); + } + + children.add(const Divider()); + final onSepolia = widget.appKit.selectedChain?.chainId == '11155111'; + if (!onSepolia) { + children.add( + const Text( + 'Test USDT Contract on Ethereum \nor switch to Sepolia to try a test Contract', + textAlign: TextAlign.center, + ), + ); + } else { + children.add( + const Text( + 'Test AAVE Token Contract on Sepolia \nor switch to Ethereum to try USDT', + textAlign: TextAlign.center, + ), + ); + } + final onMainnet = widget.appKit.selectedChain?.chainId == '1'; + + children.addAll([ + Container( + height: StyleConstants.linear40, + width: double.infinity, + margin: const EdgeInsets.symmetric(vertical: StyleConstants.linear8), + child: ElevatedButton( + onPressed: onSepolia + ? () async { + final future = EIP155.callTestSmartContract( + appKit: widget.appKit, + action: 'read', + ); + MethodDialog.show( + context, + 'Test Contract (Read)', + future, + ); + } + : onMainnet + ? () async { + final future = EIP155.callUSDTSmartContract( + appKit: widget.appKit, + action: 'read', + ); + MethodDialog.show( + context, + 'Test Contract (Read)', + future, + ); + } + : null, + style: buttonStyle(context), + child: onMainnet + ? const Text('USDT Contract (Read)') + : const Text('AAVE Contract (Read)'), + ), + ), + Container( + height: StyleConstants.linear40, + width: double.infinity, + margin: const EdgeInsets.symmetric(vertical: StyleConstants.linear8), + child: ElevatedButton( + onPressed: onSepolia + ? () async { + widget.appKit.launchConnectedWallet(); + final future = EIP155.callTestSmartContract( + appKit: widget.appKit, + action: 'write', + ); + MethodDialog.show(context, 'Test Contract (Write)', future); + } + : onMainnet + ? () async { + widget.appKit.launchConnectedWallet(); + final future = EIP155.callUSDTSmartContract( + appKit: widget.appKit, + action: 'write', + ); + MethodDialog.show( + context, 'Test Contract (Write)', future); + } + : null, + style: buttonStyle(context), + child: onMainnet + ? const Text('USDT Contract (Write)') + : const Text('AAVE Contract (Write)'), + ), + ), + ]); + + return children; + } + + List _buildSupportedChainsWidget() { + List children = []; + children.addAll( + [ + const SizedBox(height: StyleConstants.linear8), + Text( + 'Session chains:', + style: + AppKitModalTheme.getDataOf(context).textStyles.small600.copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ], + ); + final approvedChains = widget.appKit.getApprovedChains() ?? []; + children.add( + Text( + approvedChains.join(', '), + style: AppKitModalTheme.getDataOf(context).textStyles.small400.copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ); + return children; + } + + Widget _buildChainEventsTiles() { + // Add Events + final approvedEvents = widget.appKit.getApprovedEvents() ?? []; + if (approvedEvents.isEmpty) { + return Text( + 'No events approved', + style: AppKitModalTheme.getDataOf(context).textStyles.small400.copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ); + } + final List children = []; + for (final event in approvedEvents) { + children.add( + Container( + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + horizontal: StyleConstants.linear8, + ), + padding: const EdgeInsets.all(StyleConstants.linear8), + decoration: BoxDecoration( + border: Border.all( + color: AppKitModalTheme.colorsOf(context).accent100, + ), + borderRadius: borderRadius(context), + ), + child: Text( + event, + style: AppKitModalTheme.getDataOf(context) + .textStyles + .small400 + .copyWith( + color: AppKitModalTheme.colorsOf(context).foreground100, + ), + ), + ), + ); + } + + return Wrap( + children: children, + ); + } + + Future callChainMethod( + // ChainType type, + EIP155UIMethods method, + // ChainMetadata chainMetadata, + // String address, + ) { + final session = widget.appKit.session!; + return EIP155.callMethod( + appKit: widget.appKit, + topic: session.topic ?? '', + method: method, + chainId: widget.appKit.selectedChain!.chainId, + address: session.address!, + ); + } +} diff --git a/packages/reown_appkit/example/modal/pubspec.yaml b/packages/reown_appkit/example/modal/pubspec.yaml new file mode 100644 index 0000000..652bdf3 --- /dev/null +++ b/packages/reown_appkit/example/modal/pubspec.yaml @@ -0,0 +1,40 @@ +name: reown_appkit_example +description: "Reown's AppKit modal example" + +publish_to: 'none' + +environment: + sdk: ">=3.0.1 <4.0.0" + +dependencies: + cupertino_icons: ^1.0.2 + fl_toast: ^3.1.0 + flutter: + sdk: flutter + http: ^1.2.2 + intl: ^0.19.0 + package_info_plus: ^7.0.0 + reown_appkit: + path: ../.. + shared_preferences: ^2.2.0 + +dev_dependencies: + build_runner: ^2.4.7 + flutter_launcher_icons: "^0.13.1" + flutter_lints: ^3.0.1 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + json_serializable: ^6.7.0 + +flutter: + uses-material-design: true + + assets: + - assets/abis/ + +flutter_launcher_icons: + android: true + ios: true + image_path: "assets/AppIcon.png" diff --git a/packages/reown_appkit/example/modal/test/widget_test.dart b/packages/reown_appkit/example/modal/test/widget_test.dart new file mode 100644 index 0000000..e4adbe5 --- /dev/null +++ b/packages/reown_appkit/example/modal/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:reown_appkit_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/packages/reown_appkit/lib/appkit_base.dart b/packages/reown_appkit/lib/appkit_base.dart new file mode 100644 index 0000000..2d811ac --- /dev/null +++ b/packages/reown_appkit/lib/appkit_base.dart @@ -0,0 +1,3 @@ +// files +export 'base/i_appkit_base_impl.dart'; +export 'base/appkit_base_impl.dart'; diff --git a/packages/reown_appkit/lib/appkit_modal.dart b/packages/reown_appkit/lib/appkit_modal.dart new file mode 100644 index 0000000..04bc671 --- /dev/null +++ b/packages/reown_appkit/lib/appkit_modal.dart @@ -0,0 +1,24 @@ +// You have generated a new plugin project without specifying the `--platforms` +// flag. A plugin project with no platform support was generated. To add a +// platform, run `flutter create -t plugin --platforms .` under the +// same directory. You can also find a detailed instruction on how to add +// platforms in the `pubspec.yaml` at +// https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms. + +/// Models +export 'modal/models/public/appkit_modal_models.dart'; + +/// Theme +export 'modal/theme/public/appkit_modal_theme.dart'; + +/// Utils +export 'modal/utils/public/appkit_modal_utils.dart'; + +/// Widgets +export 'modal/widgets/public/appkit_modal_widgets.dart'; + +/// Pages +export 'modal/pages/public/appkit_modal_pages.dart'; + +/// Services +export 'modal/appkit_modal_impl.dart'; diff --git a/packages/reown_appkit/lib/base/appkit_base_impl.dart b/packages/reown_appkit/lib/base/appkit_base_impl.dart new file mode 100644 index 0000000..bc82c2e --- /dev/null +++ b/packages/reown_appkit/lib/base/appkit_base_impl.dart @@ -0,0 +1,410 @@ +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_core/relay_client/websocket/http_client.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +class ReownAppKit implements IReownAppKit { + static const List> DEFAULT_METHODS = [ + [ + MethodConstants.WC_SESSION_PROPOSE, + MethodConstants.WC_SESSION_REQUEST, + ], + // [ + // MethodConstants.WC_AUTH_REQUEST, + // ] + ]; + + bool _initialized = false; + + static Future createInstance({ + required String projectId, + String relayUrl = ReownConstants.DEFAULT_RELAY_URL, + required PairingMetadata metadata, + bool memoryStore = false, + LogLevel logLevel = LogLevel.nothing, + HttpWrapper httpClient = const HttpWrapper(), + }) async { + final client = ReownAppKit( + core: ReownCore( + projectId: projectId, + relayUrl: relayUrl, + memoryStore: memoryStore, + logLevel: logLevel, + httpClient: httpClient, + ), + metadata: metadata, + ); + await client.init(); + + return client; + } + + ///---------- GENERIC ----------/// + + @override + final String protocol = 'wc'; + + @override + final int version = 2; + + @override + final IReownCore core; + + @override + final PairingMetadata metadata; + + ReownAppKit({ + required this.core, + required this.metadata, + }) { + reOwnSign = ReownSign( + core: core, + metadata: metadata, + proposals: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PROPOSALS, + version: StoreVersions.VERSION_PROPOSALS, + fromJson: (dynamic value) { + return ProposalData.fromJson(value); + }, + ), + sessions: Sessions( + storage: core.storage, + context: StoreVersions.CONTEXT_SESSIONS, + version: StoreVersions.VERSION_SESSIONS, + fromJson: (dynamic value) { + return SessionData.fromJson(value); + }, + ), + pendingRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PENDING_REQUESTS, + version: StoreVersions.VERSION_PENDING_REQUESTS, + fromJson: (dynamic value) { + return SessionRequest.fromJson(value); + }, + ), + authKeys: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_KEYS, + version: StoreVersions.VERSION_AUTH_KEYS, + fromJson: (dynamic value) { + return AuthPublicKey.fromJson(value); + }, + ), + pairingTopics: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PAIRING_TOPICS, + version: StoreVersions.VERSION_PAIRING_TOPICS, + fromJson: (dynamic value) { + return value; + }, + ), + sessionAuthRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_REQUESTS, + version: StoreVersions.VERSION_AUTH_REQUESTS, + fromJson: (dynamic value) { + return PendingSessionAuthRequest.fromJson(value); + }, + ), + ); + } + + @override + Future init() async { + if (_initialized) { + return; + } + + await core.start(); + await reOwnSign.init(); + + _initialized = true; + } + + ///---------- SIGN ENGINE ----------/// + + @override + late IReownSign reOwnSign; + + @override + Event get onSessionConnect => reOwnSign.onSessionConnect; + + @override + Event get onSessionEvent => reOwnSign.onSessionEvent; + + @override + Event get onSessionExpire => reOwnSign.onSessionExpire; + + @override + Event get onProposalExpire => + reOwnSign.onProposalExpire; + + @override + Event get onSessionExtend => reOwnSign.onSessionExtend; + + @override + Event get onSessionPing => reOwnSign.onSessionPing; + + @override + Event get onSessionUpdate => reOwnSign.onSessionUpdate; + + @override + Event get onSessionDelete => reOwnSign.onSessionDelete; + + @override + IGenericStore get proposals => reOwnSign.proposals; + + @override + ISessions get sessions => reOwnSign.sessions; + + @override + IGenericStore get pendingRequests => + reOwnSign.pendingRequests; + + @override + Future connect({ + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + String? pairingTopic, + List? relays, + List>? methods = DEFAULT_METHODS, + }) async { + try { + return await reOwnSign.connect( + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: sessionProperties, + pairingTopic: pairingTopic, + relays: relays, + methods: methods, + ); + } catch (e) { + // print(e); + rethrow; + } + } + + @override + Future request({ + required String topic, + required String chainId, + required SessionRequestParams request, + }) async { + try { + return await reOwnSign.request( + topic: topic, + chainId: chainId, + request: request, + ); + } catch (e) { + rethrow; + } + } + + @override + Future> requestReadContract({ + required DeployedContract deployedContract, + required String functionName, + required String rpcUrl, + EthereumAddress? sender, + List parameters = const [], + }) async { + try { + return await reOwnSign.requestReadContract( + sender: sender, + deployedContract: deployedContract, + functionName: functionName, + rpcUrl: rpcUrl, + parameters: parameters, + ); + } catch (e) { + rethrow; + } + } + + @override + Future requestWriteContract({ + required String topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + required Transaction transaction, + String? method, + List parameters = const [], + }) async { + try { + return await reOwnSign.requestWriteContract( + topic: topic, + chainId: chainId, + deployedContract: deployedContract, + functionName: functionName, + transaction: transaction, + method: method, + parameters: parameters, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerEventHandler({ + required String chainId, + required String event, + void Function(String, dynamic)? handler, + }) { + try { + return reOwnSign.registerEventHandler( + chainId: chainId, + event: event, + handler: handler, + ); + } catch (e) { + rethrow; + } + } + + @override + Future ping({ + required String topic, + }) async { + try { + return await reOwnSign.ping(topic: topic); + } catch (e) { + rethrow; + } + } + + @override + Future disconnectSession({ + required String topic, + required ReownSignError reason, + }) async { + try { + return await reOwnSign.disconnectSession( + topic: topic, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + Map getActiveSessions() { + try { + return reOwnSign.getActiveSessions(); + } catch (e) { + rethrow; + } + } + + @override + Map getSessionsForPairing({ + required String pairingTopic, + }) { + try { + return reOwnSign.getSessionsForPairing( + pairingTopic: pairingTopic, + ); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionProposals() { + try { + return reOwnSign.getPendingSessionProposals(); + } catch (e) { + rethrow; + } + } + + @override + Future dispatchEnvelope(String url) async { + try { + return await reOwnSign.dispatchEnvelope(url); + } catch (e) { + rethrow; + } + } + + @override + Future redirectToWallet({ + required String topic, + required Redirect? redirect, + }) { + return reOwnSign.redirectToWallet( + topic: topic, + redirect: redirect, + ); + } + + @override + IPairingStore get pairings => core.pairing.getStore(); + + @override + Event get onSessionAuthResponse => + reOwnSign.onSessionAuthResponse; + + @override + Future authenticate({ + required SessionAuthRequestParams params, + String? pairingTopic, + String? walletUniversalLink, + List>? methods = const [ + [MethodConstants.WC_SESSION_AUTHENTICATE] + ], + }) async { + try { + return reOwnSign.authenticate( + params: params, + walletUniversalLink: walletUniversalLink, + pairingTopic: pairingTopic, + methods: methods, + ); + } catch (e) { + rethrow; + } + } + + @override + Future validateSignedCacao({ + required Cacao cacao, + required String projectId, + }) { + try { + return reOwnSign.validateSignedCacao( + cacao: cacao, + projectId: projectId, + ); + } catch (e) { + rethrow; + } + } + + @override + String formatAuthMessage({ + required String iss, + required CacaoRequestPayload cacaoPayload, + }) { + try { + return reOwnSign.formatAuthMessage( + iss: iss, + cacaoPayload: cacaoPayload, + ); + } catch (e) { + rethrow; + } + } + + @override + IGenericStore get authKeys => reOwnSign.authKeys; + + @override + IGenericStore get pairingTopics => reOwnSign.pairingTopics; +} diff --git a/packages/reown_appkit/lib/base/i_appkit_base_impl.dart b/packages/reown_appkit/lib/base/i_appkit_base_impl.dart new file mode 100644 index 0000000..929e00a --- /dev/null +++ b/packages/reown_appkit/lib/base/i_appkit_base_impl.dart @@ -0,0 +1,9 @@ +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/reown_sign.dart'; + +abstract class IReownAppKit implements IReownSignDapp { + final String protocol = 'wc'; + final int version = 2; + + abstract final IReownSign reOwnSign; +} diff --git a/packages/reown_appkit/lib/modal/appkit_modal_impl.dart b/packages/reown_appkit/lib/modal/appkit_modal_impl.dart new file mode 100644 index 0000000..85dc745 --- /dev/null +++ b/packages/reown_appkit/lib/modal/appkit_modal_impl.dart @@ -0,0 +1,1923 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; +import 'dart:developer' as dev; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:reown_appkit/modal/services/uri_service/launch_url_exception.dart'; +import 'package:reown_appkit/modal/services/uri_service/url_utils.dart'; +import 'package:reown_appkit/modal/services/uri_service/url_utils_singleton.dart'; +import 'package:reown_appkit/modal/utils/core_utils.dart'; +import 'package:reown_appkit/modal/utils/platform_utils.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_core/store/i_store.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; + +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/pages/account_page.dart'; +import 'package:reown_appkit/modal/pages/approve_magic_request_page.dart'; +import 'package:reown_appkit/modal/pages/approve_siwe.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/coinbase_service.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/coinbase_service_singleton.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/i_coinbase_service.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/models/coinbase_data.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/models/coinbase_events.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/redirect.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_data.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/modal/services/siwe_service/siwe_service.dart'; +import 'package:reown_appkit/modal/services/siwe_service/siwe_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/services/blockchain_service/blockchain_service.dart'; +import 'package:reown_appkit/modal/services/blockchain_service/blockchain_service_singleton.dart'; +import 'package:reown_appkit/modal/services/network_service/network_service_singleton.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/widgets/modal_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; + +/// Either a [projectId] and [metadata] must be provided or an already created [appKit]. +/// optionalNamespaces is mostly not needed, if you use it, the values set here will override every optionalNamespaces set in evey chain +class AppKitModal with ChangeNotifier implements IAppKitModal { + String _projectId = ''; + + Map _requiredNamespaces = {}; + Map _optionalNamespaces = {}; + String? _lastChainEmitted; + bool _supportsOneClickAuth = false; + + AppKitModalStatus _status = AppKitModalStatus.idle; + @override + AppKitModalStatus get status => _status; + + String? _currentSelectedChainId; + @override + AppKitModalNetworkInfo? get selectedChain { + if (_currentSelectedChainId != null) { + return AppKitModalNetworks.getNetworkById( + CoreConstants.namespace, + _currentSelectedChainId!, + ); + } + return null; + } + + AppKitModalWalletInfo? _selectedWallet; + @override + AppKitModalWalletInfo? get selectedWallet => _selectedWallet; + + @override + bool get hasNamespaces => + _requiredNamespaces.isNotEmpty || _optionalNamespaces.isNotEmpty; + + String _wcUri = ''; + @override + String? get wcUri => _wcUri; + + late IReownAppKit _appKit; + @override + IReownAppKit? get appKit => _appKit; + + String? _avatarUrl; + @override + String? get avatarUrl => _avatarUrl; + + double? _chainBalance; + @override + String get chainBalance { + return CoreUtils.formatChainBalance(_chainBalance); + } + + @override + final balanceNotifier = ValueNotifier('-.--'); + + bool _isOpen = false; + @override + bool get isOpen => _isOpen; + + bool _isConnected = false; + @override + bool get isConnected => _isConnected; + + AppKitModalSession? _currentSession; + @override + AppKitModalSession? get session => _currentSession; + + Logger get _logger => _appKit.core.logger; + + IStore> get _storage => _appKit.core.storage; + + bool _disconnectOnClose = false; + + BuildContext? _context; + @override + BuildContext? get modalContext { + if (_context?.mounted == true) { + return _context; + } + return null; + } + + /// `context` is required if SIWEConfig is passed. + AppKitModal({ + required BuildContext context, + IReownAppKit? appKit, + String? projectId, + PairingMetadata? metadata, + SIWEConfig? siweConfig, + Map? requiredNamespaces, + Map? optionalNamespaces, + Set? featuredWalletIds, + Set? includedWalletIds, + Set? excludedWalletIds, + bool? enableAnalytics, + bool enableEmail = false, + List blockchains = const [], + LogLevel logLevel = LogLevel.nothing, + }) { + if (appKit == null) { + if (projectId == null) { + throw AppKitModalException( + 'Either a `projectId` and `metadata` must be provided or an already created `appKit`', + ); + } + if (metadata == null) { + throw AppKitModalException( + '`metadata:` parameter is required when using `projectId:`', + ); + } + } + // if (siweConfig?.enabled == true && context == null) { + // throw AppKitModalException( + // '`context:` parameter is required if using `siweConfig:`. Also, `context:` parameter will be enforced in future versions.', + // ); + // } + + _context = context; + + _appKit = appKit ?? + ReownAppKit( + core: ReownCore(projectId: projectId!), + metadata: metadata!, + ); + _projectId = _appKit.core.projectId; + + _setRequiredNamespaces(requiredNamespaces); + _setOptionalNamespaces(optionalNamespaces); + + uriService.instance = UriService( + core: _appKit.core, + ); + + analyticsService.instance = AnalyticsService( + core: _appKit.core, + enableAnalytics: enableAnalytics, + )..init().then((_) { + analyticsService.instance.sendEvent(ModalLoadedEvent()); + }); + + explorerService.instance = ExplorerService( + core: _appKit.core, + referer: _appKit.metadata.name.replaceAll(' ', ''), + featuredWalletIds: featuredWalletIds, + includedWalletIds: includedWalletIds, + excludedWalletIds: excludedWalletIds, + ); + + blockchainService.instance = BlockChainService( + core: _appKit.core, + ); + + magicService.instance = MagicService( + core: _appKit.core, + metadata: _appKit.metadata, + enabled: enableEmail, + ); + + coinbaseService.instance = CoinbaseService( + core: _appKit.core, + metadata: _appKit.metadata, + enabled: _initializeCoinbaseSDK, + ); + + siweService.instance = SiweService( + appKit: _appKit, + siweConfig: siweConfig, + ); + } + + ////////* PUBLIC METHODS *///////// + + bool _serviceInitialized = false; + + @override + Future init() async { + _serviceInitialized = false; + if (!CoreUtils.isValidProjectID(_projectId)) { + _logger.e( + '[$runtimeType] projectId $_projectId is invalid. ' + 'Please provide a valid projectId. ' + 'See ${UrlConstants.docsUrl}/appkit/flutter/core/options for details.', + ); + return; + } + if (_status == AppKitModalStatus.initializing || + _status == AppKitModalStatus.initialized) { + return; + } + _status = AppKitModalStatus.initializing; + _notify(); + + _registerListeners(); + + await _appKit.init(); + await networkService.instance.init(); + await explorerService.instance.init(); + await coinbaseService.instance.init(); + await blockchainService.instance.init(); + + _currentSession = await _getStoredSession(); + _currentSelectedChainId = _getStoredChainId(null); + final isMagic = _currentSession?.sessionService.isMagic == true; + final isCoinbase = _currentSession?.sessionService.isCoinbase == true; + if (isMagic || isCoinbase) { + _currentSelectedChainId ??= _currentSession!.chainId; + await _setSesionAndChainData(_currentSession!); + if (isMagic) { + await magicService.instance.init(); + } + } else { + magicService.instance.init(); + } + + await expirePreviousInactivePairings(); + + final wcPairings = _appKit.pairings.getAll(); + final wcSessions = _appKit.sessions.getAll(); + + // Loop through all the chain data + final allNetworks = AppKitModalNetworks.getNetworks( + CoreConstants.namespace, + ); + for (final chain in allNetworks) { + for (final event in EventsConstants.allEvents) { + _appKit.registerEventHandler( + chainId: '${CoreConstants.namespace}:${chain.chainId}', + event: event, + ); + } + } + + // There's a session stored + if (wcSessions.isNotEmpty) { + await _storeSession(AppKitModalSession(sessionData: wcSessions.first)); + // session should not outlive the pairing + if (wcPairings.isEmpty) { + await disconnect(); + } else { + await _checkSIWEStatus(); + } + } else { + // Check for other type of sessions stored + if (_currentSession != null) { + if (_currentSession!.sessionService.isCoinbase) { + final isCbConnected = await coinbaseService.instance.isConnected(); + if (!isCbConnected) { + await _cleanSession(); + } + } else if (_currentSession!.sessionService.isMagic) { + // Every time the app gets killed Magic service will treat the user as disconnected + // So we will need to treat magic session differently + final email = _currentSession!.email; + magicService.instance.setEmail(email); + } else { + await _cleanSession(); + } + } + } + + // Get the chainId of the chain we are connected to. + await _selectChainFromStoredId(); + + onModalNetworkChange.subscribe(_onNetworkChainRequireSIWE); + + final connected = _appKit.core.relayClient.isConnected; + _serviceInitialized = connected; + _status = + connected ? AppKitModalStatus.initialized : AppKitModalStatus.error; + _logger.i('[$runtimeType] initialized'); + _notify(); + } + + Future _checkSIWEStatus() async { + // check if SIWE is enabled and should still sign the message + if (siweService.instance!.enabled) { + try { + // If getSession() throws it means message has not been signed and + // we should present modal with ApproveSIWEPage + final siweSession = await siweService.instance!.getSession(); + final session = _currentSession!.copyWith(siweSession: siweSession); + await _setSesionAndChainData(session); + _notify(); + } catch (_) { + _disconnectOnClose = true; + _showModalView( + startWidget: ApproveSIWEPage(onSiweFinish: _oneSIWEFinish), + ); + } + } + } + + Future _setSesionAndChainData(AppKitModalSession modalSession) async { + try { + await _storeSession(modalSession); + _currentSelectedChainId = _currentSelectedChainId ?? modalSession.chainId; + await _setLocalEthChain(_currentSelectedChainId!, logEvent: false); + } catch (e, s) { + _logger.e( + '[$runtimeType] _setSesionAndChainData error $e', + stackTrace: s, + ); + } + } + + Future _getStoredSession() async { + try { + if (_storage.has(StorageConstants.modalSession)) { + final storedSession = _storage.get(StorageConstants.modalSession); + if (storedSession != null) { + return AppKitModalSession.fromMap(storedSession); + } + } + } catch (e) { + _logger.e('[$runtimeType] _getStoredSession error: $e'); + } + return null; + } + + Future _storeSession(AppKitModalSession modalSession) async { + _currentSession = modalSession; + try { + await _storage.set( + StorageConstants.modalSession, + _currentSession!.toMap(), + ); + } catch (e) { + _logger.e('[$runtimeType] _storeSession error: $e'); + } + // _isConnected shoudl probably go at the very end of the connection + _isConnected = true; + } + + Future _selectChainFromStoredId() async { + if (_currentSession != null) { + final chainId = _getStoredChainId(null); + if (chainId != null) { + final chain = AppKitModalNetworks.getNetworkById( + CoreConstants.namespace, + chainId, + ); + if (chain != null) { + await _setLocalEthChain(chainId, logEvent: false); + } + } else { + _currentSelectedChainId = chainId; + } + } + } + + @override + Future selectChain( + AppKitModalNetworkInfo? chainInfo, { + bool switchChain = false, + bool logEvent = true, + }) async { + _checkInitialized(); + + if (chainInfo?.chainId == _currentSelectedChainId) { + return; + } + + // If the chain is null, disconnect and stop. + if (chainInfo == null) { + await disconnect(); + return; + } + + _chainBalance = null; + + if (_currentSession?.sessionService.isMagic == true) { + await magicService.instance.switchNetwork(chainId: chainInfo.chainId); + // onModalNetworkChange.broadcast(ModalNetworkChange( + // chainId: chainInfo.namespace, + // )); + } else { + final hasValidSession = _isConnected && _currentSession != null; + if (switchChain && hasValidSession && _currentSelectedChainId != null) { + final approvedChains = _currentSession!.getApprovedChains() ?? []; + final hasChainAlready = approvedChains.contains( + '${CoreConstants.namespace}:${chainInfo.chainId}', + ); + if (!hasChainAlready) { + requestSwitchToChain(chainInfo); + final hasSwitchMethod = _currentSession!.hasSwitchMethod(); + if (hasSwitchMethod) { + launchConnectedWallet(); + } + } else { + await _setLocalEthChain(chainInfo.chainId, logEvent: logEvent); + } + } else { + await _setLocalEthChain(chainInfo.chainId, logEvent: logEvent); + } + } + } + + /// Will get the list of available chains to add + @override + List? getAvailableChains() { + // if there's no session or if supportsAddChain method then every chain can be used + if (_currentSession == null || _currentSession!.hasSwitchMethod()) { + return null; + } + return getApprovedChains(); + } + + /// Will get the list of already approved chains by the wallet (to switch to) + @override + List? getApprovedChains() { + if (_currentSession == null) { + return null; + } + return _currentSession!.getApprovedChains(); + } + + @override + List? getApprovedMethods() { + if (_currentSession == null) { + return null; + } + return _currentSession!.getApprovedMethods(); + } + + @override + List? getApprovedEvents() { + if (_currentSession == null) { + return null; + } + return _currentSession!.getApprovedEvents(); + } + + Future _setLocalEthChain(String chainId, {bool? logEvent}) async { + _currentSelectedChainId = chainId; + final caip2Chain = '${CoreConstants.namespace}:$_currentSelectedChainId'; + _logger.i('[$runtimeType] set local chain $caip2Chain'); + _currentSelectedChainId = chainId; + _notify(); + try { + await _storage.set( + StorageConstants.selectedChainId, + {'chainId': _currentSelectedChainId!}, + ); + } catch (e) { + _logger.e('[$runtimeType] _setLocalEthChain error: $e'); + } + if (_isConnected && logEvent == true) { + analyticsService.instance.sendEvent(SwitchNetworkEvent( + network: _currentSelectedChainId!, + )); + } + if (_lastChainEmitted != caip2Chain && _isConnected) { + if (_lastChainEmitted != null) { + onModalNetworkChange.broadcast(ModalNetworkChange( + chainId: caip2Chain, + )); + } + _lastChainEmitted = caip2Chain; + } + loadAccountData(); + } + + @override + Future openNetworksView() { + return _showModalView( + startWidget: AppKitModalSelectNetworkPage( + onTapNetwork: (info) { + selectChain(info); + widgetStack.instance.addDefault(); + }, + ), + ); + } + + @override + Future openModalView([Widget? startWidget]) { + final keyString = startWidget?.key?.toString() ?? ''; + if (_isConnected) { + final connectedKeys = + _allowedScreensWhenConnected.map((e) => e.toString()).toList(); + if (startWidget == null) { + startWidget = const AccountPage(); + } else { + if (!connectedKeys.contains(keyString)) { + startWidget = const AccountPage(); + } + } + } else { + final disconnectedKeys = + _allowedScreensWhenDisconnected.map((e) => e.toString()).toList(); + if (startWidget != null && !disconnectedKeys.contains(keyString)) { + startWidget = null; + } + } + return _showModalView(startWidget: startWidget); + } + + final List _allowedScreensWhenConnected = [ + KeyConstants.approveTransactionPage, + KeyConstants.confirmEmailPage, + KeyConstants.selectNetworkPage, + KeyConstants.accountPage, + ]; + + final List _allowedScreensWhenDisconnected = [ + KeyConstants.qrCodePageKey, + KeyConstants.walletListLongPageKey, + KeyConstants.walletListShortPageKey, + KeyConstants.selectNetworkPage, + ]; + + Future _showModalView({ + BuildContext? context, + Widget? startWidget, + }) async { + _checkInitialized(); + ApproveTransactionPage(); + + if (_isOpen) { + closeModal(); + return; + } + _isOpen = true; + _context = context ?? modalContext; + + if (_context == null) { + _logger.e( + 'No context was found. ' + 'Try adding `context:` parameter in AppKitModal class', + ); + return; + } + + analyticsService.instance.sendEvent(ModalOpenEvent( + connected: _isConnected, + )); + + // Reset the explorer + explorerService.instance.search(query: null); + widgetStack.instance.clear(); + + final isBottomSheet = PlatformUtils.isBottomSheet(); + final theme = AppKitModalTheme.maybeOf(_context!); + await magicService.instance.syncTheme(theme); + final themeData = theme?.themeData ?? const AppKitModalThemeData(); + + Widget? showWidget = startWidget; + if (_isConnected && showWidget == null) { + showWidget = const AccountPage(); + } + + final childWidget = theme == null + ? AppKitModalTheme( + themeData: themeData, + child: ModalContainer(startWidget: showWidget), + ) + : ModalContainer(startWidget: showWidget); + + final rootWidget = ModalProvider( + service: this, + child: childWidget, + ); + + final isApprovePage = startWidget is ApproveTransactionPage; + final isTabletSize = PlatformUtils.isTablet(_context!); + + if (isBottomSheet && !isTabletSize) { + final mqData = MediaQueryData.fromView(View.of(_context!)); + final safeGap = mqData.viewPadding.bottom; + final maxHeight = mqData.size.height - safeGap - 20.0; + final modalHeight = isApprovePage ? 600.0 : maxHeight; + await showModalBottomSheet( + backgroundColor: Colors.transparent, + isDismissible: !isApprovePage, + isScrollControlled: true, + enableDrag: false, + elevation: 0.0, + useRootNavigator: true, + constraints: BoxConstraints(maxHeight: modalHeight), + context: _context!, + builder: (_) => rootWidget, + ); + _close(); + } else { + await showDialog( + barrierDismissible: false, + useSafeArea: true, + useRootNavigator: true, + anchorPoint: Offset(0, 0), + context: _context!, + builder: (_) { + final radiuses = AppKitModalTheme.radiusesOf(_context!); + final maxRadius = min(radiuses.radiusM, 36.0); + final borderRadius = BorderRadius.all(Radius.circular(maxRadius)); + final constraints = BoxConstraints(maxWidth: 360, maxHeight: 600); + return Dialog( + backgroundColor: AppKitModalTheme.colorsOf(_context!).background125, + shape: RoundedRectangleBorder(borderRadius: borderRadius), + clipBehavior: Clip.hardEdge, + child: ConstrainedBox( + constraints: constraints, + child: rootWidget, + ), + ); + }, + ); + _close(); + } + + _isOpen = false; + _notify(); + } + + @override + Future expirePreviousInactivePairings() async { + for (var pairing in _appKit.pairings.getAll()) { + if (!pairing.active) { + await _appKit.core.expirer.expire(pairing.topic); + } + } + } + + void _trackSelectedWallet( + WalletRedirect? walletRedirect, { + bool inBrowser = false, + }) { + final walletName = _selectedWallet!.listing.name; + final event = SelectWalletEvent( + name: walletName, + platform: inBrowser ? AnalyticsPlatform.web : AnalyticsPlatform.mobile, + ); + analyticsService.instance.sendEvent(event); + } + + @override + Future connectSelectedWallet({bool inBrowser = false}) async { + _checkInitialized(); + + final walletRedirect = explorerService.instance.getWalletRedirect( + selectedWallet, + ); + + if (walletRedirect == null) { + throw AppKitModalException( + 'You didn\'t select a wallet or walletInfo argument is null', + ); + } + _trackSelectedWallet(walletRedirect, inBrowser: inBrowser); + + var pType = PlatformUtils.getPlatformType(); + if (inBrowser) { + pType = PlatformType.web; + } + try { + if (_selectedWallet!.isCoinbase) { + await coinbaseService.instance.getAccount(); + await explorerService.instance.storeConnectedWallet(_selectedWallet); + } else { + await buildConnectionUri(); + await uriService.instance.openRedirect( + walletRedirect, + wcURI: wcUri!, + pType: pType, + ); + } + } on LaunchUrlException catch (e) { + if (e is CanNotLaunchUrl) { + onModalError.broadcast(WalletNotInstalled()); + } else { + onModalError.broadcast(ErrorOpeningWallet()); + } + } catch (e, s) { + if (e is PlatformException) { + final installed = _selectedWallet?.installed ?? false; + if (!installed) { + onModalError.broadcast(WalletNotInstalled()); + } else { + onModalError.broadcast(ErrorOpeningWallet()); + } + } else if (e is CoinbaseServiceException) { + if (e is CoinbaseWalletNotInstalledException) { + onModalError.broadcast(WalletNotInstalled()); + } else { + if (_isUserRejectedError(e)) { + _logger.i('[$runtimeType] User declined connection'); + onModalError.broadcast(UserRejectedConnection()); + analyticsService.instance.sendEvent(ConnectErrorEvent( + message: 'User declined connection', + )); + } else { + onModalError.broadcast(ErrorOpeningWallet()); + analyticsService.instance.sendEvent(ConnectErrorEvent( + message: e.message, + )); + } + } + } else if (_isUserRejectedError(e)) { + _logger.i('[$runtimeType] User declined connection'); + onModalError.broadcast(UserRejectedConnection()); + analyticsService.instance.sendEvent(ConnectErrorEvent( + message: 'User declined connection', + )); + } else { + _logger.e( + '[$runtimeType] Error connecting wallet', + error: e, + stackTrace: s, + ); + onModalError.broadcast(ErrorOpeningWallet()); + } + } + } + + @override + Future buildConnectionUri() async { + if (!_isConnected) { + try { + if (siweService.instance!.enabled) { + final nonce = await siweService.instance!.getNonce(); + final p1 = await siweService.instance!.config!.getMessageParams(); + final chains = + AppKitModalNetworks.getNetworks(CoreConstants.namespace) + .map((e) => '${CoreConstants.namespace}:${e.chainId}') + .toList(); + final methods = p1.methods ?? MethodsConstants.allMethods; + final p2 = {'nonce': nonce, 'chains': chains, 'methods': methods}; + final authParams = SessionAuthRequestParams.fromJson({ + ...p1.toJson(), + ...p2, + }); + // One-Click Auth + final authResponse = await _appKit.authenticate( + params: authParams, + ); + _wcUri = authResponse.uri?.toString() ?? ''; + _notify(); + _awaitOCAuthCallback(authResponse); + } else { + // Regular Session Proposal + final connectResponse = await _appKit.connect( + requiredNamespaces: _requiredNamespaces, + optionalNamespaces: _optionalNamespaces, + ); + _wcUri = connectResponse.uri?.toString() ?? ''; + _notify(); + _awaitConnectionCallback(connectResponse); + } + } catch (e) { + _logger.e('[$runtimeType] buildConnectionUri $e'); + } + } + } + + void _awaitConnectionCallback(ConnectResponse connectResponse) async { + try { + final _ = await connectResponse.session.future; + } on TimeoutException { + _logger.i('[$runtimeType] Rebuilding session, ending future'); + return; + } catch (e) { + await _connectionErrorHandler(e); + } + } + + SessionAuthResponse? _sessionAuthResponse; + void _awaitOCAuthCallback(SessionAuthRequestResponse authResponse) async { + try { + _sessionAuthResponse = await authResponse.completer.future; + _supportsOneClickAuth = true; + if (_sessionAuthResponse?.session != null) { + _appKit.onSessionConnect.broadcast(SessionConnect( + _sessionAuthResponse!.session!, + )); + } else { + if (_sessionAuthResponse?.jsonRpcError != null) { + throw _sessionAuthResponse!.jsonRpcError!; + } + if (_sessionAuthResponse?.error != null) { + throw _sessionAuthResponse!.error!; + } + } + } on TimeoutException { + _logger.i('[$runtimeType] Rebuilding session, ending future'); + return; + } catch (e) { + await disconnect(); + await _connectionErrorHandler(e); + } + } + + Future _connectionErrorHandler(dynamic e) async { + if (_isUserRejectedError(e)) { + _logger.i('[$runtimeType] User declined connection'); + onModalError.broadcast(UserRejectedConnection()); + analyticsService.instance.sendEvent(ConnectErrorEvent( + message: 'User declined connection', + )); + } else { + if (e is JsonRpcError) { + final message = e.message ?? ''; + final method = MethodConstants.WC_SESSION_AUTHENTICATE; + if (e.code == 10001 && message.contains(method)) { + _supportsOneClickAuth = false; + // WILL HANDLE SESSION REQUEST + return; + } + onModalError.broadcast(ModalError('Error connecting to wallet')); + analyticsService.instance.sendEvent(ConnectErrorEvent( + message: message, + )); + _logger.e('[$runtimeType] $message', error: e); + } + if (e is ReownSignError || e is ReownCoreError) { + onModalError.broadcast(ModalError(e.message)); + analyticsService.instance.sendEvent(ConnectErrorEvent( + message: e.message, + )); + _logger.e('[$runtimeType] ${e.message}', error: e); + } + } + return await expirePreviousInactivePairings(); + } + + @override + void launchConnectedWallet() async { + _checkInitialized(); + + final walletInfo = explorerService.instance.getConnectedWallet(); + if (walletInfo == null) { + // if walletInfo is null could mean that either + // 1. There's no wallet connected (shouldn't happen) + // 2. Wallet is connected on another device through qr code + return; + } + + final isCoinbase = _currentSession!.sessionService.isCoinbase == true; + if (walletInfo.isCoinbase || isCoinbase) { + // Coinbase Wallet is getting launched at every request by it's own SDK + // SO no need to do it here. + return; + } + + if (_currentSession!.sessionService.isMagic) { + // There's no wallet to launch when connected with Email + return; + } + + final metadataRedirect = _currentSession!.peer?.metadata.redirect; + + final walletRedirect = explorerService.instance.getWalletRedirect( + walletInfo, + ); + + if (walletRedirect == null) { + return; + } + + try { + final link = metadataRedirect?.native ?? metadataRedirect?.universal; + uriService.instance.openRedirect( + walletRedirect.copyWith(mobile: link), + pType: PlatformUtils.getPlatformType(), + ); + } catch (e) { + onModalError.broadcast(ErrorOpeningWallet()); + } + } + + @override + Future reconnectRelay() async { + if (!_appKit.core.relayClient.isConnected) { + await _appKit.core.relayClient.connect(); + } + } + + @override + Future disconnect({bool disconnectAllSessions = true}) async { + _checkInitialized(); + + _status = AppKitModalStatus.initializing; + _notify(); + + if (_currentSession?.sessionService.isCoinbase == true) { + try { + await coinbaseService.instance.resetSession(); + } catch (_) { + _status = AppKitModalStatus.initialized; + _notify(); + return; + } + } + if (_currentSession?.sessionService.isMagic == true) { + await Future.delayed(Duration(milliseconds: 300)); + final disconnected = await magicService.instance.disconnect(); + if (!disconnected) { + _status = AppKitModalStatus.initialized; + _notify(); + return; + } + } + + try { + // If we want to disconnect all sessions, loop through them and disconnect them + if (disconnectAllSessions) { + for (final SessionData session in _appKit.sessions.getAll()) { + await _disconnectSession(session.pairingTopic, session.topic); + } + } else { + // Disconnect the session + await _disconnectSession( + _currentSession?.pairingTopic, + _currentSession?.topic, + ); + } + try { + if (siweService.instance!.signOutOnDisconnect) { + await siweService.instance!.signOut(); + } + } catch (_) {} + + analyticsService.instance.sendEvent(DisconnectSuccessEvent()); + if (!(_currentSession?.sessionService.isWC == true)) { + // if sessionService.isWC then _cleanSession() is being called on sessionDelete event + return await _cleanSession(); + } + return; + } catch (e) { + analyticsService.instance.sendEvent(DisconnectErrorEvent()); + _status = AppKitModalStatus.initialized; + _notify(); + } + } + + @override + void closeModal({bool disconnectSession = false}) async { + _disconnectOnClose = disconnectSession; + // If we aren't open, then we can't and shouldn't close + _close(); + if (_context != null) { + final canPop = Navigator.of(_context!, rootNavigator: true).canPop(); + if (canPop) { + Navigator.of(_context!, rootNavigator: true).pop(); + } + } + _notify(); + } + + void _close() async { + if (!_isOpen) { + return; + } + _isOpen = false; + if (_disconnectOnClose) { + _disconnectOnClose = false; + final currentKey = widgetStack.instance.getCurrent().key; + if (currentKey == KeyConstants.approveSiwePageKey) { + analyticsService.instance.sendEvent(ClickCancelSiwe( + network: _currentSelectedChainId ?? '', + )); + } + await disconnect(); + selectWallet(null); + } + toastService.instance.clear(); + analyticsService.instance.sendEvent(ModalCloseEvent( + connected: _isConnected, + )); + } + + @override + void selectWallet(AppKitModalWalletInfo? walletInfo) { + _selectedWallet = walletInfo; + } + + @override + void launchBlockExplorer() async { + if ((selectedChain?.explorerUrl ?? '').isNotEmpty) { + final blockExplorer = selectedChain!.explorerUrl; + final address = _currentSession?.address ?? ''; + final explorerUrl = '$blockExplorer/address/$address'; + await uriService.instance.launchUrl( + Uri.parse(explorerUrl), + mode: LaunchMode.externalApplication, + ); + } + } + + @override + Future> requestReadContract({ + required String? topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + EthereumAddress? sender, + List parameters = const [], + }) async { + if (_currentSession == null) { + throw AppKitModalException('Session is null'); + } + String reqChainId = chainId; + final isValidChainId = NamespaceUtils.isValidChainId(chainId); + if (!isValidChainId) { + if (selectedChain!.chainId == chainId) { + reqChainId = '${CoreConstants.namespace}:$chainId'; + } else { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: 'chainId should conform to "CAIP-2" format', + ); + } + } + // + _logger.d('[$runtimeType] requestWriteContract, chainId: $reqChainId'); + + final networkInfo = AppKitModalNetworks.getNetworkById( + reqChainId.split(':').first, + reqChainId.split(':').last, + )!; + + try { + if (selectedChain == null) { + throw AppKitModalException( + 'You must select a chain before reading a contract', + ); + } + return await _appKit.requestReadContract( + deployedContract: deployedContract, + functionName: functionName, + rpcUrl: networkInfo.rpcUrl, + sender: sender, + parameters: parameters, + ); + } catch (e) { + rethrow; + } + } + + @override + Future requestWriteContract({ + required String? topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + required Transaction transaction, + List parameters = const [], + String? method, + }) async { + if (_currentSession == null) { + throw AppKitModalException('Session is null'); + } + String reqChainId = chainId; + final isValidChainId = NamespaceUtils.isValidChainId(chainId); + if (!isValidChainId) { + if (selectedChain!.chainId == chainId) { + reqChainId = '${CoreConstants.namespace}:$chainId'; + } else { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: 'chainId should conform to "CAIP-2" format', + ); + } + } + // + _logger.d('[$runtimeType] requestWriteContract, chainId: $reqChainId'); + + try { + return await _appKit.requestWriteContract( + topic: topic ?? '', + chainId: reqChainId, + deployedContract: deployedContract, + functionName: functionName, + transaction: transaction, + parameters: parameters, + method: method, + ); + } catch (e) { + rethrow; + } + } + + @override + Future request({ + required String? topic, + required String chainId, + required SessionRequestParams request, + String? switchToChainId, + }) async { + if (_currentSession == null) { + throw AppKitModalException('Session is null'); + } + String reqChainId = chainId; + final isValidChainId = NamespaceUtils.isValidChainId(chainId); + if (!isValidChainId) { + if (selectedChain!.chainId == chainId) { + reqChainId = '${CoreConstants.namespace}:$chainId'; + } else { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: 'chainId should conform to "CAIP-2" format', + ); + } + } + // + _logger.d( + '[$runtimeType] request, chainId: $reqChainId, ' + '${jsonEncode(request.toJson())}', + ); + try { + if (_currentSession!.sessionService.isMagic) { + return await magicService.instance.request( + chainId: reqChainId, + request: request, + ); + } + if (_currentSession!.sessionService.isCoinbase) { + return await await coinbaseService.instance.request( + chainId: switchToChainId ?? reqChainId, + request: request, + ); + } + return await _appKit.request( + topic: topic!, + chainId: reqChainId, + request: request, + ); + } catch (e) { + if (_isUserRejectedError(e)) { + _logger.i('[$runtimeType] User declined request'); + onModalError.broadcast(UserRejectedConnection()); + if (request.method == MethodsConstants.walletSwitchEthChain || + request.method == MethodsConstants.walletAddEthChain) { + rethrow; + } + return 'User rejected'; + } else { + if (e is CoinbaseServiceException) { + // If the error is due to no session on Coinbase Wallet we disconnnect the session on Modal. + // This is the only way to detect a missing session since Coinbase Wallet is not sending any event. + // disconnect(); + throw AppKitModalException('Coinbase Wallet Error'); + } + rethrow; + } + } + } + + @override + Future dispose() async { + if (_status == AppKitModalStatus.initialized) { + await disconnect(); + await expirePreviousInactivePairings(); + _unregisterListeners(); + _status = AppKitModalStatus.idle; + _logger.d('[$runtimeType] dispose'); + } + super.dispose(); + } + + @override + final Event onModalConnect = Event(); + + @override + final Event onModalUpdate = Event(); + + @override + final Event onModalNetworkChange = Event(); + + @override + final Event onModalDisconnect = Event(); + + @override + final Event onModalError = Event(); + + @override + final Event onSessionExpireEvent = Event(); + + @override + final Event onSessionUpdateEvent = Event(); + + @override + final Event onSessionEventEvent = Event(); + + ////////* PRIVATE METHODS *///////// + + void _notify() => notifyListeners(); + + bool get _initializeCoinbaseSDK { + final cbId = CoinbaseService.defaultWalletData.listing.id; + final included = (explorerService.instance.includedWalletIds ?? {}); + final excluded = (explorerService.instance.excludedWalletIds ?? {}); + + if (included.isNotEmpty) { + return included.contains(cbId); + } + if (excluded.isNotEmpty) { + return !excluded.contains(cbId); + } + + return true; + } + + void _setRequiredNamespaces(Map? requiredNSpaces) { + if (requiredNSpaces != null) { + // Set the required namespaces declared by the user on AppKitModal object + _requiredNamespaces = requiredNSpaces.map( + (key, value) => MapEntry( + key, + RequiredNamespace( + chains: value.chains, + methods: value.methods, + events: value.events, + ), + ), + ); + } else { + // Set the required namespaces to everything in our chain presets + _requiredNamespaces = {}; + } + + final wrongNamespace = _requiredNamespaces.keys.firstWhereOrNull( + (k) => k != CoreConstants.namespace, + ); + if (wrongNamespace != null) { + throw AppKitModalException('Only eip155 blockains are supported'); + } + + _logger.d('[$runtimeType] _requiredNamespaces $_requiredNamespaces'); + } + + void _setOptionalNamespaces(Map? optionalNSpaces) { + if (optionalNSpaces != null) { + // Set the optional namespaces declared by the user on AppKitModal object + _optionalNamespaces = optionalNSpaces.map( + (key, value) => MapEntry( + key, + RequiredNamespace( + chains: value.chains, + methods: value.methods, + events: value.events, + ), + ), + ); + } else { + // Set the optional namespaces to everything in our chain presets + final namespaces = AppKitModalNetworks.supported.keys; + for (var ns in namespaces) { + final chains = AppKitModalNetworks.supported[ns] ?? []; + _optionalNamespaces[ns] = RequiredNamespace( + chains: chains.map((e) => '$ns:${e.chainId}').toList(), + methods: MethodsConstants.allMethods.toSet().toList(), + events: EventsConstants.allEvents.toSet().toList(), + ); + } + } + + final wrongNamespace = _optionalNamespaces.keys.firstWhereOrNull( + (k) => k != CoreConstants.namespace, + ); + if (wrongNamespace != null) { + throw AppKitModalException( + 'Only ${CoreConstants.namespace} networks are supported', + ); + } + + _logger.d('[$runtimeType] _optionalNamespaces $_optionalNamespaces'); + } + + /// Loads account balance and avatar. + /// Returns true if it was able to actually load data (i.e. there is a selected chain and session) + @override + Future loadAccountData() async { + // If there is no selected chain or session, stop. No account to load in. + if (_currentSelectedChainId == null || + _currentSession == null || + _currentSession?.address == null) { + return; + } + + // Get the chain balance. + _chainBalance = await blockchainService.instance.rpcRequest( + chainId: '${CoreConstants.namespace}:$_currentSelectedChainId', + request: SessionRequestParams( + method: 'eth_getBalance', + params: [_currentSession!.address!, 'latest'], + ), + ); + final tokenName = selectedChain?.currency ?? ''; + balanceNotifier.value = '$_chainBalance $tokenName'; + + // Get the avatar, each chainId is just a number in string form. + try { + final blockchainId = await blockchainService.instance.getIdentity( + _currentSession!.address!, + ); + _avatarUrl = blockchainId.avatar; + _logger.i('[$runtimeType] loadAccountData'); + } catch (e) { + _logger.e('[$runtimeType] loadAccountData $e'); + } + _notify(); + } + + @override + Future requestSwitchToChain(AppKitModalNetworkInfo newChain) async { + if (_currentSession?.sessionService.isMagic == true) { + await selectChain(newChain); + return; + } + final currentChain = '${CoreConstants.namespace}:$_currentSelectedChainId'; + final newChainId = '${CoreConstants.namespace}:${newChain.chainId}'; + _logger.i('[$runtimeType] requesting switch to chain $newChainId'); + try { + await request( + topic: _currentSession?.topic ?? '', + chainId: currentChain, + switchToChainId: newChainId, + request: SessionRequestParams( + method: MethodsConstants.walletSwitchEthChain, + params: [ + {'chainId': newChain.chainHexId} + ], + ), + ); + _currentSelectedChainId = newChain.chainId; + await _setSesionAndChainData(_currentSession!); + return; + } catch (e) { + _logger.i('[$runtimeType] requestSwitchToChain error $e'); + // if request errors due to user rejection then set the previous chain + if (_isUserRejectedError(e)) { + // fallback to current chain if rejected by user + await _setLocalEthChain(_currentSelectedChainId!); + throw JsonRpcError(code: 5002, message: 'User rejected methods.'); + } else { + try { + // Otherwise it meas chain has to be added. + return await requestAddChain(newChain); + } catch (e) { + rethrow; + } + } + } + } + + @override + Future requestAddChain(AppKitModalNetworkInfo newChain) async { + final topic = _currentSession?.topic ?? ''; + final chainId = '${CoreConstants.namespace}:$_currentSelectedChainId'; + final newChainId = '${CoreConstants.namespace}:${newChain.chainId}'; + _logger.i('[$runtimeType] requesting switch to add chain $newChainId'); + try { + await request( + topic: topic, + chainId: chainId, + switchToChainId: newChainId, + request: SessionRequestParams( + method: MethodsConstants.walletAddEthChain, + params: [newChain.toJson()], + ), + ); + _currentSelectedChainId = newChain.chainId; + await _setSesionAndChainData(_currentSession!); + return; + } catch (e) { + _logger.i('[$runtimeType] requestAddChain error $e'); + await _setLocalEthChain(_currentSelectedChainId!); + throw JsonRpcError(code: 5002, message: 'User rejected methods.'); + } + } + + bool _isUserRejectedError(dynamic e) { + if (e is JsonRpcError) { + final stringError = e.toJson().toString().toLowerCase(); + final userRejected = stringError.contains('rejected'); + final userDisapproved = stringError.contains('user disapproved'); + return userRejected || userDisapproved; + } + if (e is CoinbaseServiceException) { + final stringError = e.message.toLowerCase(); + final userDenied = stringError.contains('user denied'); + return userDenied; + } + return false; + } + + Future _disconnectSession(String? pairingTopic, String? topic) async { + // Disconnecting the session will produce the onSessionDisconnect callback + if (topic != null) { + await _appKit.disconnectSession( + topic: topic, + reason: Errors.getSdkError(Errors.USER_DISCONNECTED).toSignError(), + ); + } + if (pairingTopic != null) { + await _appKit.core.pairing.disconnect(topic: pairingTopic); + } + } + + Future _cleanSession({SessionDelete? args, bool event = true}) async { + try { + final storedWalletId = _storage.get(StorageConstants.recentWalletId); + final walletId = storedWalletId?['walletId']; + await _storage.deleteAll(); + await explorerService.instance.storeRecentWalletId(walletId); + } catch (_) { + await _storage.deleteAll(); + } + if (event) { + onModalDisconnect.broadcast(ModalDisconnect( + topic: args?.topic, + id: args?.id, + )); + } + _currentSelectedChainId = null; + _isConnected = false; + _currentSession = null; + _lastChainEmitted = null; + _supportsOneClickAuth = false; + _status = AppKitModalStatus.initialized; + _notify(); + } + + void _checkInitialized() { + if (_status != AppKitModalStatus.initialized && + _status != AppKitModalStatus.initializing) { + throw AppKitModalException( + 'AppKitModal must be initialized before calling this method.', + ); + } + } + + void _registerListeners() { + // Magic + magicService.instance.onMagicConnect.subscribe(_onMagicConnectEvent); + magicService.instance.onMagicLoginSuccess.subscribe(_onMagicLoginEvent); + magicService.instance.onMagicError.subscribe(_onMagicErrorEvent); + magicService.instance.onMagicUpdate.subscribe(_onMagicSessionUpdateEvent); + magicService.instance.onMagicRpcRequest.subscribe(_onMagicRequest); + // + // Coinbase + coinbaseService.instance.onCoinbaseConnect.subscribe( + _onCoinbaseConnectEvent, + ); + coinbaseService.instance.onCoinbaseError.subscribe( + _onCoinbaseErrorEvent, + ); + coinbaseService.instance.onCoinbaseSessionUpdate.subscribe( + _onCoinbaseSessionUpdateEvent, + ); + // + _appKit.onSessionAuthResponse.subscribe(_onSessionAuthResponse); + _appKit.onSessionConnect.subscribe(_onSessionConnect); + _appKit.onSessionDelete.subscribe(_onSessionDelete); + _appKit.onSessionExpire.subscribe(_onSessionExpire); + _appKit.onSessionUpdate.subscribe(_onSessionUpdate); + _appKit.onSessionEvent.subscribe(_onSessionEvent); + // Core + _appKit.core.relayClient.onRelayClientConnect.subscribe( + _onRelayClientConnect, + ); + _appKit.core.relayClient.onRelayClientError.subscribe( + _onRelayClientError, + ); + _appKit.core.relayClient.onRelayClientDisconnect.subscribe( + _onRelayClientDisconnect, + ); + } + + void _onNetworkChainRequireSIWE(ModalNetworkChange? args) async { + try { + if (siweService.instance!.signOutOnNetworkChange) { + await siweService.instance!.signOut(); + _disconnectOnClose = true; + widgetStack.instance.push(ApproveSIWEPage( + onSiweFinish: _oneSIWEFinish, + )); + } + } catch (e) { + _logger.e('[$runtimeType] _onNetworkChainRequireSIWE $e'); + } + } + + void _unregisterListeners() { + // Magic + magicService.instance.onMagicLoginSuccess.unsubscribe(_onMagicLoginEvent); + magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); + magicService.instance.onMagicUpdate.unsubscribe(_onMagicSessionUpdateEvent); + magicService.instance.onMagicRpcRequest.unsubscribe(_onMagicRequest); + // + // Coinbase + coinbaseService.instance.onCoinbaseConnect.unsubscribe( + _onCoinbaseConnectEvent, + ); + coinbaseService.instance.onCoinbaseError.unsubscribe( + _onCoinbaseErrorEvent, + ); + coinbaseService.instance.onCoinbaseSessionUpdate.unsubscribe( + _onCoinbaseSessionUpdateEvent, + ); + // + _appKit.onSessionAuthResponse.unsubscribe(_onSessionAuthResponse); + _appKit.onSessionConnect.unsubscribe(_onSessionConnect); + _appKit.onSessionDelete.unsubscribe(_onSessionDelete); + _appKit.onSessionEvent.unsubscribe(_onSessionEvent); + _appKit.onSessionUpdate.unsubscribe(_onSessionUpdate); + // Core + _appKit.core.relayClient.onRelayClientConnect.unsubscribe( + _onRelayClientConnect, + ); + _appKit.core.relayClient.onRelayClientError.unsubscribe( + _onRelayClientError, + ); + _appKit.core.relayClient.onRelayClientDisconnect.unsubscribe( + _onRelayClientDisconnect, + ); + } + + String? _getStoredChainId(String? defaultValue) { + if (_storage.has(StorageConstants.selectedChainId)) { + final storedChain = _storage.get(StorageConstants.selectedChainId); + debugPrint('storedChain $storedChain'); + return storedChain?['chainId'] as String? ?? defaultValue; + } + return defaultValue; + } +} + +extension _EmailConnectorExtension on AppKitModal { + // Login event should be treated like Connect event for regular wallets + Future _onMagicLoginEvent(MagicLoginEvent? args) async { + final debugString = jsonEncode(args?.data?.toJson()); + _logger.i('[$runtimeType] _onMagicLoginEvent: $debugString'); + if (args!.data != null) { + final newChainId = _getStoredChainId('${args.data!.chainId}')!; + _currentSelectedChainId = newChainId; + // + final magicData = args.data?.copytWith(chainId: int.tryParse(newChainId)); + final session = AppKitModalSession(magicData: magicData); + await _setSesionAndChainData(session); + onModalConnect.broadcast(ModalConnect(session)); + if (_selectedWallet == null) { + await _storage.delete(StorageConstants.recentWalletId); + await _storage.delete(StorageConstants.connectedWalletData); + } + // + if (siweService.instance!.enabled) { + if (!_isOpen) { + await _checkSIWEStatus(); + onModalUpdate.broadcast(ModalConnect(_currentSession!)); + } else { + _disconnectOnClose = true; + final theme = AppKitModalTheme.maybeOf(_context!); + await magicService.instance.syncTheme(theme); + widgetStack.instance.push(ApproveSIWEPage( + onSiweFinish: _oneSIWEFinish, + )); + } + } else { + if (_isOpen) { + closeModal(); + } + } + } + } + + Future _onMagicSessionUpdateEvent(MagicSessionEvent? args) async { + _logger.d('[$runtimeType] _onMagicUpdateEvent: $args'); + if (args != null) { + try { + final newEmail = args.email ?? _currentSession!.email; + final address = args.address ?? _currentSession!.address!; + final chainId = args.chainId?.toString() ?? _currentSession!.chainId; + _currentSelectedChainId = chainId; + // + final session = _currentSession!.copyWith( + magicData: MagicData( + email: newEmail, + address: address, + chainId: int.parse(chainId), + peer: magicService.instance.metadata, + self: ConnectionMetadata( + metadata: _appKit.metadata, + publicKey: '', + ), + ), + ); + await _setSesionAndChainData(session); + onModalUpdate.broadcast(ModalConnect(session)); + } catch (e, s) { + _logger.d( + '[$runtimeType] _onMagicUpdateEvent: $e', + stackTrace: s, + ); + } + } + } + + Future _onMagicErrorEvent(MagicErrorEvent? args) async { + _logger.d('[$runtimeType] _onMagicErrorEvent ${args?.error}'); + final errorMessage = args?.error ?? 'Something went wrong'; + if (!errorMessage.toLowerCase().contains('user denied')) { + onModalError.broadcast(ModalError(errorMessage)); + } + _notify(); + } + + void _onMagicRequest(MagicRequestEvent? args) { + _logger.d('[$runtimeType] _onMagicRequest ${args?.toString()}'); + if (args?.result != null) { + if (args!.result is JsonRpcError && widgetStack.instance.canPop()) { + widgetStack.instance.pop(); + } else { + closeModal(); + } + } + } + + Future _onMagicConnectEvent(MagicConnectEvent? event) async { + if (event?.connected == false) { + if (_currentSession != null) { + onModalConnect.broadcast(ModalConnect(_currentSession!)); + } + } + } +} + +extension _CoinbaseConnectorExtension on AppKitModal { + void _onCoinbaseConnectEvent(CoinbaseConnectEvent? args) async { + final debugString = jsonEncode(args?.data?.toJson()); + _logger.i('[$runtimeType] _onCoinbaseConnectEvent: $debugString'); + if (args?.data != null) { + final newChainId = _getStoredChainId('${args!.data!.chainId}')!; + _currentSelectedChainId = newChainId; + // + final session = AppKitModalSession(coinbaseData: args.data!); + await _setSesionAndChainData(session); + onModalConnect.broadcast(ModalConnect(session)); + // + if (siweService.instance!.enabled) { + _disconnectOnClose = true; + widgetStack.instance.push(ApproveSIWEPage( + onSiweFinish: _oneSIWEFinish, + )); + } else { + if (_isOpen) { + closeModal(); + } + } + } + } + + void _onCoinbaseSessionUpdateEvent(CoinbaseSessionEvent? args) async { + _logger.i('[$runtimeType] _onCoinbaseSessionUpdateEvent $args'); + if (args != null) { + try { + final address = args.address ?? _currentSession!.address!; + final chainId = args.chainId ?? _currentSession!.chainId; + _currentSelectedChainId = chainId; + // + final chain = AppKitModalNetworks.getNetworkById( + CoreConstants.namespace, + chainId, + ); + final session = _currentSession!.copyWith( + coinbaseData: CoinbaseData( + address: address, + chainName: chain?.name ?? '', + chainId: int.parse(chainId), + peer: coinbaseService.instance.metadata, + self: ConnectionMetadata( + metadata: _appKit.metadata, + publicKey: await coinbaseService.instance.ownPublicKey, + ), + ), + ); + await _setSesionAndChainData(session); + onModalUpdate.broadcast(ModalConnect(session)); + } catch (e, s) { + _logger.d( + '[$runtimeType] _onCoinbaseSessionUpdateEvent: $e', + stackTrace: s, + ); + } + } + } + + void _onCoinbaseErrorEvent(CoinbaseErrorEvent? args) async { + _logger.d('[$runtimeType] _onCoinbaseErrorEvent ${args?.error}'); + final errorMessage = args?.error ?? 'Something went wrong'; + if (!errorMessage.toLowerCase().contains('user denied')) { + onModalError.broadcast(ModalError(errorMessage)); + } + } +} + +extension _AppKitModalExtension on AppKitModal { + void _onSessionAuthResponse(SessionAuthResponse? args) async { + final debugString = jsonEncode(args?.toJson()); + dev.log('[$runtimeType] _onSessionAuthResponse: $debugString'); + if (args?.session != null) { + // IF 1-CA SUPPORTED WE SHOULD CALL SIWECONGIF METHODS HERE + final session = await _settleSession(args!.session!); + // + try { + // Verify message with just the first cacao + final Cacao cacao = args.auths!.first; + final message = _appKit.formatAuthMessage( + iss: cacao.p.iss, + cacaoPayload: CacaoRequestPayload.fromCacaoPayload(cacao.p), + ); + final clientId = await _appKit.core.crypto.getClientId(); + await siweService.instance!.verifyMessage( + message: message, + signature: cacao.s.s, + clientId: clientId, + ); + } catch (e) { + _logger.e('[$runtimeType] _onSessionAuthResponse $e'); + await disconnect(); + return; + } + // + final siweSession = await siweService.instance!.getSession(); + final newSession = session.copyWith(siweSession: siweSession); + // + await _storeSession(newSession); + onModalConnect.broadcast(ModalConnect(newSession)); + // + if (_isOpen) { + closeModal(); + } + } + } + + void _onSessionConnect(SessionConnect? args) async { + final debugString = jsonEncode(args?.session.toJson()); + dev.log('[$runtimeType] _onSessionConnect: $debugString'); + final siweEnabled = siweService.instance!.enabled; + if (_supportsOneClickAuth && siweEnabled) return; + if (args != null) { + // IF SIWE CALLBACK (1-CA NOT SUPPORTED) SIWECONGIF METHODS ARE CALLED ON ApproveSIWEPage + final session = await _settleSession(args.session); + onModalConnect.broadcast(ModalConnect(session)); + // + if (siweService.instance!.enabled) { + _disconnectOnClose = true; + widgetStack.instance.push(ApproveSIWEPage( + onSiweFinish: _oneSIWEFinish, + )); + } else { + if (_isOpen) { + closeModal(); + } + } + } + } + + // HAS TO BE CALLED JUST ONCE ON CONNECTION + Future _settleSession(SessionData sessionData) async { + if (_currentSelectedChainId == null) { + final chains = NamespaceUtils.getChainIdsFromNamespaces( + namespaces: sessionData.namespaces, + )..sort((a, b) => a.compareTo(b)); + final chainId = chains.first.split(':').last.toString(); + _currentSelectedChainId = chainId; + } + final session = AppKitModalSession(sessionData: sessionData); + await _setSesionAndChainData(session); + if (_selectedWallet == null) { + analyticsService.instance.sendEvent(ConnectSuccessEvent( + name: 'WalletConnect', + method: AnalyticsPlatform.qrcode, + )); + await _storage.delete(StorageConstants.recentWalletId); + await _storage.delete(StorageConstants.connectedWalletData); + } else { + explorerService.instance.storeConnectedWallet(_selectedWallet); + final walletName = _selectedWallet!.listing.name; + analyticsService.instance.sendEvent(ConnectSuccessEvent( + name: walletName, + method: AnalyticsPlatform.mobile, + )); + } + return session; + } + + void _oneSIWEFinish(AppKitModalSession updatedSession) async { + await _storeSession(updatedSession); + try { + await _storage.set( + StorageConstants.selectedChainId, + {'chainId': _currentSelectedChainId!}, + ); + debugPrint('_oneSIWEFinish {\'chainId\': $_currentSelectedChainId}'); + } catch (e) { + _logger.e('[$runtimeType] _setLocalEthChain error: $e'); + } + onModalUpdate.broadcast(ModalConnect(updatedSession)); + closeModal(); + analyticsService.instance.sendEvent(SiweAuthSuccess( + network: _currentSelectedChainId!, + )); + } + + void _onSessionEvent(SessionEvent? args) async { + _logger.i('[$runtimeType] session event $args'); + onSessionEventEvent.broadcast(args); + if (args?.name == EventsConstants.chainChanged) { + _currentSelectedChainId = args?.data?.toString(); + } else if (args?.name == EventsConstants.accountsChanged) { + try { + // TODO implement account change + if (siweService.instance!.signOutOnAccountChange) { + await siweService.instance!.signOut(); + } + } catch (_) {} + } + _notify(); + } + + void _onSessionUpdate(SessionUpdate? args) async { + _logger.i('[$runtimeType] session update $args'); + if (args != null) { + final wcSessions = _appKit.sessions.getAll(); + if (wcSessions.isEmpty) return; + // + final session = _appKit.sessions.get(args.topic); + final updatedSession = AppKitModalSession( + sessionData: session!.copyWith( + namespaces: args.namespaces, + ), + ); + await _setSesionAndChainData(updatedSession); + onSessionUpdateEvent.broadcast(args); + onModalUpdate.broadcast(ModalConnect(updatedSession)); + } + } + + void _onSessionExpire(SessionExpire? args) { + _logger.i('[$runtimeType] session expire $args'); + onSessionExpireEvent.broadcast(args); + } + + void _onSessionDelete(SessionDelete? args) { + _logger.i('[$runtimeType] session delete $args'); + _cleanSession(args: args); + } + + void _onRelayClientConnect(EventArgs? args) { + _logger.i('[$runtimeType] relay client connected'); + final service = _currentSession?.sessionService ?? AppKitModalConnector.wc; + if (service.isWC && _serviceInitialized) { + _status = AppKitModalStatus.initialized; + _notify(); + } + } + + void _onRelayClientDisconnect(EventArgs? args) { + _logger.i('[$runtimeType] relay client disconnected'); + final service = _currentSession?.sessionService ?? AppKitModalConnector.wc; + if (service.isWC && _serviceInitialized) { + _status = AppKitModalStatus.idle; + _notify(); + } + } + + void _onRelayClientError(ErrorEvent? args) { + _logger.i('[$runtimeType] relay client error: ${args?.error}'); + final service = _currentSession?.sessionService ?? AppKitModalConnector.wc; + if (service.isWC) { + _status = AppKitModalStatus.error; + _notify(); + } + } +} diff --git a/packages/reown_appkit/lib/modal/assets/AppIcon.png b/packages/reown_appkit/lib/modal/assets/AppIcon.png new file mode 100644 index 0000000..dc72967 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/AppIcon.png differ diff --git a/packages/reown_appkit/lib/modal/assets/account_copy.svg b/packages/reown_appkit/lib/modal/assets/account_copy.svg new file mode 100644 index 0000000..2ee2721 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/account_copy.svg @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/account_disconnect.svg b/packages/reown_appkit/lib/modal/assets/account_disconnect.svg new file mode 100644 index 0000000..1320e91 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/account_disconnect.svg @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/all_wallets.svg b/packages/reown_appkit/lib/modal/assets/dark/all_wallets.svg new file mode 100644 index 0000000..b26d319 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/all_wallets.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/all_wallets_button.svg b/packages/reown_appkit/lib/modal/assets/dark/all_wallets_button.svg new file mode 100644 index 0000000..1eade78 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/all_wallets_button.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/code_button.svg b/packages/reown_appkit/lib/modal/assets/dark/code_button.svg new file mode 100644 index 0000000..d30270c --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/code_button.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/desktop_button.svg b/packages/reown_appkit/lib/modal/assets/dark/desktop_button.svg new file mode 100644 index 0000000..6e107ea --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/desktop_button.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/extension_button.svg b/packages/reown_appkit/lib/modal/assets/dark/extension_button.svg new file mode 100644 index 0000000..97194fd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/extension_button.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/input_cancel.svg b/packages/reown_appkit/lib/modal/assets/dark/input_cancel.svg new file mode 100644 index 0000000..0df4bb1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/input_cancel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/logo_walletconnect.svg b/packages/reown_appkit/lib/modal/assets/dark/logo_walletconnect.svg new file mode 100644 index 0000000..496eb92 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/logo_walletconnect.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/mobile_button.svg b/packages/reown_appkit/lib/modal/assets/dark/mobile_button.svg new file mode 100644 index 0000000..24f881e --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/mobile_button.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/qr_code.svg b/packages/reown_appkit/lib/modal/assets/dark/qr_code.svg new file mode 100644 index 0000000..683ebd0 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/qr_code.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/qr_code_button.svg b/packages/reown_appkit/lib/modal/assets/dark/qr_code_button.svg new file mode 100644 index 0000000..a26ba69 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/qr_code_button.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/web_button.svg b/packages/reown_appkit/lib/modal/assets/dark/web_button.svg new file mode 100644 index 0000000..14456aa --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/web_button.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/chart.svg b/packages/reown_appkit/lib/modal/assets/help/chart.svg new file mode 100644 index 0000000..09240c4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/chart.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/compass.svg b/packages/reown_appkit/lib/modal/assets/help/compass.svg new file mode 100644 index 0000000..65deb7a --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/compass.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/help/dao.svg b/packages/reown_appkit/lib/modal/assets/help/dao.svg new file mode 100644 index 0000000..fcadde7 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/dao.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/defi.svg b/packages/reown_appkit/lib/modal/assets/help/defi.svg new file mode 100644 index 0000000..b6a81b4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/defi.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/eth.svg b/packages/reown_appkit/lib/modal/assets/help/eth.svg new file mode 100644 index 0000000..5065bb2 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/eth.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/key.svg b/packages/reown_appkit/lib/modal/assets/help/key.svg new file mode 100644 index 0000000..d216311 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/key.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/layers.svg b/packages/reown_appkit/lib/modal/assets/help/layers.svg new file mode 100644 index 0000000..edcd82a --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/layers.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/lock.svg b/packages/reown_appkit/lib/modal/assets/help/lock.svg new file mode 100644 index 0000000..8688c46 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/lock.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/network.svg b/packages/reown_appkit/lib/modal/assets/help/network.svg new file mode 100644 index 0000000..411324b --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/network.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/noun.svg b/packages/reown_appkit/lib/modal/assets/help/noun.svg new file mode 100644 index 0000000..a388f68 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/noun.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/painting.svg b/packages/reown_appkit/lib/modal/assets/help/painting.svg new file mode 100644 index 0000000..bed6897 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/painting.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/system.svg b/packages/reown_appkit/lib/modal/assets/help/system.svg new file mode 100644 index 0000000..6f91fca --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/system.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/help/user.svg b/packages/reown_appkit/lib/modal/assets/help/user.svg new file mode 100644 index 0000000..a3f5035 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/help/user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/arrow_top_right.svg b/packages/reown_appkit/lib/modal/assets/icons/arrow_top_right.svg new file mode 100644 index 0000000..db277d6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/arrow_top_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/checkmark.svg b/packages/reown_appkit/lib/modal/assets/icons/checkmark.svg new file mode 100644 index 0000000..d20253e --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/chevron_left.svg b/packages/reown_appkit/lib/modal/assets/icons/chevron_left.svg new file mode 100644 index 0000000..153cf96 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/chevron_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/chevron_right.svg b/packages/reown_appkit/lib/modal/assets/icons/chevron_right.svg new file mode 100644 index 0000000..3a90acf --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/chevron_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/close.svg b/packages/reown_appkit/lib/modal/assets/icons/close.svg new file mode 100644 index 0000000..0d69cec --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/code.svg b/packages/reown_appkit/lib/modal/assets/icons/code.svg new file mode 100644 index 0000000..15f765c --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/code.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/coin.svg b/packages/reown_appkit/lib/modal/assets/icons/coin.svg new file mode 100644 index 0000000..e9e9f1b --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/coin.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/compass.svg b/packages/reown_appkit/lib/modal/assets/icons/compass.svg new file mode 100644 index 0000000..a281c91 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/compass.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/copy.svg b/packages/reown_appkit/lib/modal/assets/icons/copy.svg new file mode 100644 index 0000000..b5d3b47 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/copy.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/copy_14.svg b/packages/reown_appkit/lib/modal/assets/icons/copy_14.svg new file mode 100644 index 0000000..6d47729 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/copy_14.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/disconnect.svg b/packages/reown_appkit/lib/modal/assets/icons/disconnect.svg new file mode 100644 index 0000000..4a0bf12 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/disconnect.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/dots.svg b/packages/reown_appkit/lib/modal/assets/icons/dots.svg new file mode 100644 index 0000000..1d091b5 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/dots.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/extension.svg b/packages/reown_appkit/lib/modal/assets/icons/extension.svg new file mode 100644 index 0000000..bc5945b --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/extension.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/help.svg b/packages/reown_appkit/lib/modal/assets/icons/help.svg new file mode 100644 index 0000000..1d59ea2 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/help.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/info.svg b/packages/reown_appkit/lib/modal/assets/icons/info.svg new file mode 100644 index 0000000..afba99d --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/info.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/mail.svg b/packages/reown_appkit/lib/modal/assets/icons/mail.svg new file mode 100644 index 0000000..0daf8ed --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/mail.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/mobile.svg b/packages/reown_appkit/lib/modal/assets/icons/mobile.svg new file mode 100644 index 0000000..c0a725e --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/mobile.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/network.svg b/packages/reown_appkit/lib/modal/assets/icons/network.svg new file mode 100644 index 0000000..d5ed933 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/network.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/refresh.svg b/packages/reown_appkit/lib/modal/assets/icons/refresh.svg new file mode 100644 index 0000000..53b6528 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/refresh_back.svg b/packages/reown_appkit/lib/modal/assets/icons/refresh_back.svg new file mode 100644 index 0000000..a6d6fe6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/refresh_back.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/regular/wallet.svg b/packages/reown_appkit/lib/modal/assets/icons/regular/wallet.svg new file mode 100644 index 0000000..4f088a1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/regular/wallet.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/search.svg b/packages/reown_appkit/lib/modal/assets/icons/search.svg new file mode 100644 index 0000000..14aee4f --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/swap_horizontal.svg b/packages/reown_appkit/lib/modal/assets/icons/swap_horizontal.svg new file mode 100644 index 0000000..e510a19 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/swap_horizontal.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/verif.svg b/packages/reown_appkit/lib/modal/assets/icons/verif.svg new file mode 100644 index 0000000..7cc6798 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/verif.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/wallet.svg b/packages/reown_appkit/lib/modal/assets/icons/wallet.svg new file mode 100644 index 0000000..e876af1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/wallet.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/warning.svg b/packages/reown_appkit/lib/modal/assets/icons/warning.svg new file mode 100644 index 0000000..77a3378 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/warning.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/icons/wc.svg b/packages/reown_appkit/lib/modal/assets/icons/wc.svg new file mode 100644 index 0000000..a04f830 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/icons/wc.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/all_wallets.svg b/packages/reown_appkit/lib/modal/assets/light/all_wallets.svg new file mode 100644 index 0000000..33665eb --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/all_wallets.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/all_wallets_button.svg b/packages/reown_appkit/lib/modal/assets/light/all_wallets_button.svg new file mode 100644 index 0000000..772cc5d --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/all_wallets_button.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/code_button.svg b/packages/reown_appkit/lib/modal/assets/light/code_button.svg new file mode 100644 index 0000000..520f14f --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/code_button.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/desktop_button.svg b/packages/reown_appkit/lib/modal/assets/light/desktop_button.svg new file mode 100644 index 0000000..1d355bd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/desktop_button.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/extension_button.svg b/packages/reown_appkit/lib/modal/assets/light/extension_button.svg new file mode 100644 index 0000000..4408858 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/extension_button.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/input_cancel.svg b/packages/reown_appkit/lib/modal/assets/light/input_cancel.svg new file mode 100644 index 0000000..296955e --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/input_cancel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/logo_walletconnect.svg b/packages/reown_appkit/lib/modal/assets/light/logo_walletconnect.svg new file mode 100644 index 0000000..2649238 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/logo_walletconnect.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/mobile_button.svg b/packages/reown_appkit/lib/modal/assets/light/mobile_button.svg new file mode 100644 index 0000000..a6671c9 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/mobile_button.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/qr_code.svg b/packages/reown_appkit/lib/modal/assets/light/qr_code.svg new file mode 100644 index 0000000..39295fb --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/qr_code.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/qr_code_button.svg b/packages/reown_appkit/lib/modal/assets/light/qr_code_button.svg new file mode 100644 index 0000000..f18fb28 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/qr_code_button.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/web_button.svg b/packages/reown_appkit/lib/modal/assets/light/web_button.svg new file mode 100644 index 0000000..16fa958 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/web_button.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/network_placeholder.svg b/packages/reown_appkit/lib/modal/assets/network_placeholder.svg new file mode 100644 index 0000000..3e0fcb3 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/network_placeholder.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/png/2.0x/app_store.png b/packages/reown_appkit/lib/modal/assets/png/2.0x/app_store.png new file mode 100644 index 0000000..d0b85c3 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/2.0x/app_store.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/2.0x/google_play.png b/packages/reown_appkit/lib/modal/assets/png/2.0x/google_play.png new file mode 100644 index 0000000..2dde1e5 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/2.0x/google_play.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/2.0x/logo_wc.png b/packages/reown_appkit/lib/modal/assets/png/2.0x/logo_wc.png new file mode 100644 index 0000000..356718b Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/2.0x/logo_wc.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/3.0x/app_store.png b/packages/reown_appkit/lib/modal/assets/png/3.0x/app_store.png new file mode 100644 index 0000000..534a6d6 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/3.0x/app_store.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/3.0x/google_play.png b/packages/reown_appkit/lib/modal/assets/png/3.0x/google_play.png new file mode 100644 index 0000000..4bb06a0 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/3.0x/google_play.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/3.0x/logo_wc.png b/packages/reown_appkit/lib/modal/assets/png/3.0x/logo_wc.png new file mode 100644 index 0000000..845a40a Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/3.0x/logo_wc.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/app_store.png b/packages/reown_appkit/lib/modal/assets/png/app_store.png new file mode 100644 index 0000000..4542fc9 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/app_store.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/google_play.png b/packages/reown_appkit/lib/modal/assets/png/google_play.png new file mode 100644 index 0000000..dea3344 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/google_play.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/logo_wc.png b/packages/reown_appkit/lib/modal/assets/png/logo_wc.png new file mode 100644 index 0000000..356718b Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/logo_wc.png differ diff --git a/packages/reown_appkit/lib/modal/assets/token_placeholder.svg b/packages/reown_appkit/lib/modal/assets/token_placeholder.svg new file mode 100644 index 0000000..0d4311e --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/token_placeholder.svg @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/wallet_placeholder.svg b/packages/reown_appkit/lib/modal/assets/wallet_placeholder.svg new file mode 100644 index 0000000..3960a3f --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/wallet_placeholder.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/constants/key_constants.dart b/packages/reown_appkit/lib/modal/constants/key_constants.dart new file mode 100644 index 0000000..1ecbb7b --- /dev/null +++ b/packages/reown_appkit/lib/modal/constants/key_constants.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class KeyConstants { + static const Key selectNetworkPage = Key('selectNetworkPage'); + static const Key accountPage = Key('accountPage'); + static const Key addressCopyButton = Key('addressCopyButton'); + static const Key disconnectButton = Key('disconnectButton'); + static const Key w3mAccountButton = Key('w3mAccountButton'); + static const Key chainSwapButton = Key('chainSwapButton'); + static const Key editEmailPage = Key('editEmailPage'); + static const Key upgradeWalletPage = Key('upgradeWalletPage'); + static const Key helpPageKey = Key('helpPageKey'); + static const Key qrCodePageKey = Key('qrCodePageKey'); + static const Key walletListShortPageKey = Key('walletListShortPageKey'); + static const Key walletListLongPageKey = Key('walletListLongPageKey'); + static const Key connectWalletPageKey = Key('connectWalletPageKey'); + static const Key connecNetworkPageKey = Key('connecNetworkPageKey'); + static const Key qrCodeAndWalletListPageKey = + Key('qrCodeAndWalletListFullPageKey'); + static const Key getAWalletPageKey = Key('getAWalletPageKey'); + static const Key approveTransactionPage = Key('approveTransactionPage'); + static const Key confirmEmailPage = Key('confirmEmailPage'); + static const Key approveSiwePageKey = Key('approveSiwePageKey'); + + // Buttons + static const Key helpButtonKey = Key('helpButtonKey'); + static const Key closeModalButtonKey = Key('closeModalButtonKey'); + static const Key getAWalletButtonKey = Key('getAWalletButtonKey'); + static const Key navbarBackButtonKey = Key('navbarBackButtonKey'); + static const Key gridListViewAllButtonKey = Key('gridListViewAllButtonKey'); +} diff --git a/packages/reown_appkit/lib/modal/constants/string_constants.dart b/packages/reown_appkit/lib/modal/constants/string_constants.dart new file mode 100644 index 0000000..a23273d --- /dev/null +++ b/packages/reown_appkit/lib/modal/constants/string_constants.dart @@ -0,0 +1,55 @@ +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_sign/version.dart' as reown_sign; + +class CoreConstants { + // Request Headers + static const X_SDK_TYPE = 'w3m'; // TODO change to 'appkit' + static const X_SDK_VERSION = packageVersion; + static const X_CORE_SDK_VERSION = 'flutter_${reown_sign.packageVersion}'; + static const String namespace = 'eip155'; +} + +class UIConstants { + // UI + static const String selectNetwork = 'Select network'; + static const String selectNetworkShort = 'Network'; + static const String connected = 'Connected'; + static const String error = 'Error'; + static const String copyAddress = 'Copy Address'; + static const String disconnect = 'Disconnect'; + static const String addressCopied = 'Address copied'; + static const String noChain = 'No Chain'; + static const String connectButtonError = 'Network Error'; + static const String connectButtonReconnecting = 'Reconnecting'; + static const String connectButtonIdle = 'Connect wallet'; + static const String connectButtonIdleShort = 'Connect'; + static const String connectButtonConnecting = 'Connecting...'; + static const String connectButtonConnected = 'Disconnect'; + static const String noResults = 'No results found'; +} + +class StorageConstants { + // Storage + static const String recentWalletId = 'w3m_recentWallet'; + static const String connectedWalletData = 'w3m_walletData'; + static const String selectedChainId = 'w3m_selectedChainId'; + static const String modalSession = 'w3m_session'; +} + +class UrlConstants { + static const String apiService = 'https://api.web3modal.com'; + static const String blockChainService = 'https://rpc.walletconnect.org'; + static const String analyticsService = 'https://pulse.walletconnect.org'; + static const String cloudService = 'https://cloud.walletconnect.com'; + static const String exploreWallets = + 'https://explorer.walletconnect.com/?type=wallet'; + static const String secureService = + 'https://secure-mobile.walletconnect.com/mobile-sdk'; + + // + static const String secureDashboard = + 'https://secure.walletconnect.com/dashboard'; + static const String learnMoreUrl = + 'https://ethereum.org/en/developers/docs/networks'; + static const String docsUrl = 'https://docs.walletconnect.com'; +} diff --git a/packages/reown_appkit/lib/modal/constants/style_constants.dart b/packages/reown_appkit/lib/modal/constants/style_constants.dart new file mode 100644 index 0000000..d2adf3b --- /dev/null +++ b/packages/reown_appkit/lib/modal/constants/style_constants.dart @@ -0,0 +1,24 @@ +const kListItemHeight = 56.0; + +const kGridItemWidth = 76.0; +const kGridItemHeight = 96.0; + +const kNavbarHeight = 64.0; +const kShortWalletListCount = 4; +const kLShortWalletListCount = 3; +const kSearchFieldHeight = 40.0; + +const kPadding16 = 16.0; +const kPadding12 = 12.0; +const kPadding8 = 8.0; +const kPadding6 = 6.0; + +const kGridAxisSpacing = 12.0; +const kGridAxisCountP = 4; +const kGridAxisCountPW = 5; +const kGridAxisCountL = 8; +const kGridAxisCountLW = 9; + +const kListViewSeparatorHeight = 8.0; + +const kSelectedWalletIconHeight = 80.0; diff --git a/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart b/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart new file mode 100644 index 0000000..ec8ff64 --- /dev/null +++ b/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +enum AppKitModalStatus { + idle, + initializing, + initialized, + error; + + bool get isInitialized => this == initialized; + bool get isLoading => this == initializing; + bool get isError => this == error; +} + +/// Either a [projectId] and [metadata] must be provided or an already created [appKit]. +/// optionalNamespaces is mostly not needed, if you use it, the values set here will override every optionalNamespaces set in evey chain +abstract class IAppKitModal with ChangeNotifier { + BuildContext? get modalContext; + + /// Whether or not this object has been initialized. + AppKitModalStatus get status; + + bool get hasNamespaces; + + /// The object that manages sessions, authentication, events, and requests for WalletConnect. + IReownAppKit? get appKit; + + /// Variable that can be used to check if the modal is visible on screen. + bool get isOpen; + + /// Variable that can be used to check if appKitModal is connected + bool get isConnected; + + /// The URI that can be used to connect to this dApp. + /// This is only available after the [openModalView] function is called. + String? get wcUri; + + /// The current session's data. + AppKitModalSession? get session; + + /// The url to the account's avatar image. + /// Pass this into a [Image.network] and it will load the avatar image. + String? get avatarUrl; + + /// Returns the balance of the currently connected wallet on the selected chain. + String get chainBalance; + + ValueNotifier get balanceNotifier; + + /// The currently selected chain. + AppKitModalNetworkInfo? get selectedChain; + + /// The currently selected wallet. + AppKitModalWalletInfo? get selectedWallet; + + /// Sets up the explorer and appKit if they already been initialized. + Future init(); + + // @Deprecated( + // 'Add context param to AppKitModal and use openNetworksView() instead') + // Future openNetworks(BuildContext context); + + Future openNetworksView(); + + /// Opens the modal with the provided [startWidget] (if any). + /// If none is provided, the default state will be used based on platform. + Future openModalView([Widget? startWidget]); + + /// Connects to the relay if not already connected. + /// If the relay is already connected, this does nothing. + Future reconnectRelay(); + + /// Sets the [selectedWallet] to be connected + void selectWallet(AppKitModalWalletInfo? walletInfo); + + /// Sets the [selectedChain] and gets the [chainBalance]. + /// If the wallet is already connected, it will request the chain to be changed and will update the session with the new chain. + /// If [chainInfo] is null this will disconnect the wallet. + Future selectChain( + AppKitModalNetworkInfo? chainInfo, { + bool switchChain = false, + }); + + /// Launch blockchain explorer for the current chain in external browser + void launchBlockExplorer(); + + /// Used to expire and delete any inactive pairing + Future expirePreviousInactivePairings(); + + /// This will do nothing if [isConnected] is true. + Future buildConnectionUri(); + + /// Connects the [selectedWallet] previously selected + Future connectSelectedWallet({bool inBrowser = false}); + + /// Opens the native wallet [selectedWallet] after connected + void launchConnectedWallet(); + + /// List of available chains to be added in connected wallet + List? getAvailableChains(); + + /// List of approved chains by connected wallet + List? getApprovedChains(); + + /// List of approved methods by connected wallet + List? getApprovedMethods(); + + /// List of approved events by connected wallet + List? getApprovedEvents(); + + Future loadAccountData(); + + /// Disconnects the session and pairing, if any. + /// If there is no session, this does nothing. + Future disconnect({bool disconnectAllSessions = true}); + + Future> requestReadContract({ + required String? topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + EthereumAddress? sender, + List parameters = const [], + }); + + Future requestWriteContract({ + required String? topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + required Transaction transaction, + List parameters = const [], + String? method, + }); + + /// Make a request + Future request({ + required String? topic, + required String chainId, + String? switchToChainId, + required SessionRequestParams request, + }); + + Future requestSwitchToChain(AppKitModalNetworkInfo newChain); + Future requestAddChain(AppKitModalNetworkInfo newChain); + + /// Closes the modal. + void closeModal({bool disconnectSession = false}); + + @override + Future dispose(); + + /* EVENTS DECLARATIONS */ + + abstract final Event onModalConnect; + abstract final Event onModalUpdate; + abstract final Event onModalNetworkChange; + abstract final Event onModalDisconnect; + abstract final Event onModalError; + // + abstract final Event onSessionExpireEvent; + abstract final Event onSessionUpdateEvent; + abstract final Event onSessionEventEvent; +} diff --git a/packages/reown_appkit/lib/modal/models/grid_item.dart b/packages/reown_appkit/lib/modal/models/grid_item.dart new file mode 100644 index 0000000..92366fc --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/grid_item.dart @@ -0,0 +1,31 @@ +class GridItem { + final String image; + final String id; + final String title; + final bool disabled; + final T data; + + GridItem({ + required this.image, + required this.id, + required this.title, + required this.data, + this.disabled = false, + }); + + GridItem copyWith({ + String? image, + String? id, + String? title, + bool? disabled, + T? data, + }) { + return GridItem( + image: image ?? this.image, + id: id ?? this.id, + title: title ?? this.title, + disabled: disabled ?? this.disabled, + data: data ?? this.data, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart new file mode 100644 index 0000000..77c7fed --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart @@ -0,0 +1,54 @@ +import 'package:reown_appkit/reown_appkit.dart'; + +class ModalConnect extends EventArgs { + final AppKitModalSession session; + ModalConnect(this.session); + + @override + String toString() { + return 'ModalConnect(session: ${session.toJson()})'; + } +} + +class ModalNetworkChange extends EventArgs { + final String chainId; + ModalNetworkChange({required this.chainId}); + + @override + String toString() { + return 'ModalNetworkChange(chainId: $chainId)'; + } +} + +class ModalDisconnect extends EventArgs { + final String? topic; + final int? id; + ModalDisconnect({this.topic, this.id}); + + @override + String toString() { + return 'ModalDisconnect(topic: $topic, id: $id)'; + } +} + +class ModalError extends EventArgs { + final String message; + ModalError(this.message); + + @override + String toString() { + return 'ModalError(message: $message)'; + } +} + +class WalletNotInstalled extends ModalError { + WalletNotInstalled() : super('Wallet app not installed'); +} + +class ErrorOpeningWallet extends ModalError { + ErrorOpeningWallet() : super('Unable to open Wallet app'); +} + +class UserRejectedConnection extends ModalError { + UserRejectedConnection() : super('User rejected connection'); +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_exceptions.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_exceptions.dart new file mode 100644 index 0000000..72cf0b9 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_exceptions.dart @@ -0,0 +1,5 @@ +class AppKitModalException implements Exception { + final dynamic message; + final dynamic stackTrace; + AppKitModalException(this.message, [this.stackTrace]) : super(); +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart new file mode 100644 index 0000000..8698749 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart @@ -0,0 +1,6 @@ +export 'appkit_network_info.dart'; +export 'appkit_wallet_info.dart'; +export 'appkit_siwe_config.dart'; +export 'appkit_modal_session.dart'; +export 'appkit_modal_events.dart'; +export 'appkit_modal_exceptions.dart'; diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart new file mode 100644 index 0000000..3d19e6f --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart @@ -0,0 +1,318 @@ +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/coinbase_service.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/models/coinbase_data.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_data.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +// TODO AppKitModal this should be hidden +enum AppKitModalConnector { + wc, + coinbase, + magic, + none; + + bool get isWC => this == AppKitModalConnector.wc; + bool get isCoinbase => this == AppKitModalConnector.coinbase; + bool get isMagic => this == AppKitModalConnector.magic; + bool get noSession => this == AppKitModalConnector.none; +} + +class AppKitModalSession { + SessionData? _sessionData; + CoinbaseData? _coinbaseData; + MagicData? _magicData; + SIWESession? _siweSession; + + AppKitModalSession({ + SessionData? sessionData, + CoinbaseData? coinbaseData, + MagicData? magicData, + SIWESession? siweSession, + }) : _sessionData = sessionData, + _coinbaseData = coinbaseData, + _magicData = magicData, + _siweSession = siweSession; + + /// USED TO READ THE SESSION FROM LOCAL STORAGE + factory AppKitModalSession.fromMap(Map map) { + final sessionDataString = map['sessionData']; + final coinbaseDataString = map['coinbaseData']; + final magicDataString = map['magicData']; + final siweSession = map['siweSession']; + return AppKitModalSession( + sessionData: sessionDataString != null + ? SessionData.fromJson(sessionDataString) + : null, + coinbaseData: coinbaseDataString != null + ? CoinbaseData.fromJson(coinbaseDataString) + : null, + magicData: + magicDataString != null ? MagicData.fromJson(magicDataString) : null, + siweSession: + siweSession != null ? SIWESession.fromJson(siweSession) : null, + ); + } + + AppKitModalSession copyWith({ + SessionData? sessionData, + CoinbaseData? coinbaseData, + MagicData? magicData, + SIWESession? siweSession, + }) { + return AppKitModalSession( + sessionData: sessionData ?? _sessionData, + coinbaseData: coinbaseData ?? _coinbaseData, + magicData: magicData ?? _magicData, + siweSession: siweSession ?? _siweSession, + ); + } + + AppKitModalConnector get sessionService { + if (_sessionData != null) { + return AppKitModalConnector.wc; + } + if (_coinbaseData != null) { + return AppKitModalConnector.coinbase; + } + if (_magicData != null) { + return AppKitModalConnector.magic; + } + + return AppKitModalConnector.none; + } + + bool hasSwitchMethod() { + if (sessionService.noSession) { + return false; + } + if (sessionService.isCoinbase) { + return true; + } + + final nsMethods = getApprovedMethods() ?? []; + final supportsAddChain = nsMethods.contains( + MethodsConstants.walletAddEthChain, + ); + return supportsAddChain; + } + + List? getApprovedMethods() { + if (sessionService.noSession) { + return null; + } + if (sessionService.isCoinbase) { + return CoinbaseService.supportedMethods; + } + if (sessionService.isMagic) { + return MagicService.supportedMethods; + } + + final sessionNamespaces = _sessionData!.namespaces; + final namespace = sessionNamespaces[CoreConstants.namespace]; + final methodsList = namespace?.methods.toSet().toList(); + return methodsList ?? []; + } + + List? getApprovedEvents() { + if (sessionService.noSession) { + return null; + } + if (sessionService.isCoinbase) { + return []; + } + if (sessionService.isMagic) { + return []; + } + + final sessionNamespaces = _sessionData!.namespaces; + final namespace = sessionNamespaces[CoreConstants.namespace]; + final eventsList = namespace?.events.toSet().toList(); + return eventsList ?? []; + } + + List? getApprovedChains() { + if (sessionService.noSession) { + return null; + } + // We can not know which chains are approved from Coinbase or Magic + if (!sessionService.isWC) { + return [chainId]; + } + + final accounts = getAccounts() ?? []; + final approvedChains = NamespaceUtils.getChainsFromAccounts(accounts); + return approvedChains; + } + + List? getAccounts() { + if (sessionService.noSession) { + return null; + } + if (sessionService.isCoinbase) { + return ['${CoreConstants.namespace}:$chainId:$address']; + } + if (sessionService.isMagic) { + return ['${CoreConstants.namespace}:$chainId:$address']; + } + + final sessionNamespaces = _sessionData!.namespaces; + return sessionNamespaces[CoreConstants.namespace]?.accounts ?? []; + } + + Redirect? getSessionRedirect() { + if (sessionService.noSession) { + return null; + } + + return _sessionData?.peer.metadata.redirect; + } + + // toJson() would convert AppKitModalSession to a SessionData kind of map + // no matter if Coinbase Wallet or Email Wallet is connected + Map toJson() => { + if (topic != null) 'topic': topic, + if (pairingTopic != null) 'pairingTopic': pairingTopic, + if (relay != null) 'relay': relay, + if (expiry != null) 'expiry': expiry, + if (acknowledged != null) 'acknowledged': acknowledged, + if (controller != null) 'controller': controller, + 'namespaces': _namespaces(), + if (requiredNamespaces != null) + 'requiredNamespaces': requiredNamespaces, + if (optionalNamespaces != null) + 'optionalNamespaces': optionalNamespaces, + 'self': self?.toJson(), + 'peer': peer?.toJson(), + }; +} + +extension AppKitModalSessionExtension on AppKitModalSession { + String? get topic => _sessionData?.topic; + String? get pairingTopic => _sessionData?.pairingTopic; + Relay? get relay => _sessionData?.relay; + int? get expiry => _sessionData?.expiry; + bool? get acknowledged => _sessionData?.acknowledged; + String? get controller => _sessionData?.controller; + Map? get namespaces => _sessionData?.namespaces; + Map? get requiredNamespaces => + _sessionData?.requiredNamespaces; + Map? get optionalNamespaces => + _sessionData?.optionalNamespaces; + Map? get sessionProperties => _sessionData?.sessionProperties; + + ConnectionMetadata? get self { + if (sessionService.isCoinbase) { + return _coinbaseData?.self; + } + if (sessionService.isMagic) { + return _magicData?.self; + } + return _sessionData?.self; + } + + ConnectionMetadata? get peer { + if (sessionService.isCoinbase) { + return _coinbaseData?.peer; + } + if (sessionService.isMagic) { + return _magicData?.peer; + } + return _sessionData?.peer; + } + + // + String get email => _magicData?.email ?? ''; + + // + String? get address { + if (sessionService.noSession) { + return null; + } + if (sessionService.isCoinbase) { + return _coinbaseData!.address; + } + if (sessionService.isMagic) { + return _magicData!.address; + } + final namespace = namespaces?[CoreConstants.namespace]; + final accounts = namespace?.accounts ?? []; + if (accounts.isNotEmpty) { + return NamespaceUtils.getAccount(accounts.first); + } + return null; + } + + String get chainId { + if (sessionService.isWC) { + final chainIds = NamespaceUtils.getChainIdsFromNamespaces( + namespaces: namespaces ?? {}, + ); + if (chainIds.isNotEmpty) { + return (chainIds..sort()).first.split(':')[1]; + } + } + if (sessionService.isCoinbase) { + return _coinbaseData!.chainId.toString(); + } + if (sessionService.isMagic) { + return _magicData!.chainId.toString(); + } + return '1'; + } + + String? get connectedWalletName { + if (sessionService.isCoinbase) { + return CoinbaseService.defaultWalletData.listing.name; + } + if (sessionService.isMagic) { + return MagicService.defaultWalletData.listing.name; + } + if (sessionService.isWC) { + return peer?.metadata.name; + } + return null; + } + + Map toRawJson() { + return { + ...(_sessionData?.toJson() ?? {}), + ...(_coinbaseData?.toJson() ?? {}), + ...(_magicData?.toJson() ?? {}), + }; + } + + Map? _namespaces() { + if (sessionService.isCoinbase) { + return { + CoreConstants.namespace: Namespace( + chains: ['${CoreConstants.namespace}:$chainId'], + accounts: ['${CoreConstants.namespace}:$chainId:$address'], + methods: [...CoinbaseService.supportedMethods], + events: [], + ), + }; + } + if (sessionService.isMagic) { + return { + CoreConstants.namespace: Namespace( + chains: ['${CoreConstants.namespace}:$chainId'], + accounts: ['${CoreConstants.namespace}:$chainId:$address'], + methods: [...MagicService.supportedMethods], + events: [], + ), + }; + } + return namespaces; + } + + /// USED TO STORE THE SESSION IN LOCAL STORAGE + Map toMap() { + return { + if (_sessionData != null) 'sessionData': _sessionData!.toJson(), + if (_coinbaseData != null) 'coinbaseData': _coinbaseData?.toJson(), + if (_magicData != null) 'magicData': _magicData?.toJson(), + if (_siweSession != null) 'siweSession': _siweSession?.toJson(), + }; + } +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_network_info.dart b/packages/reown_appkit/lib/modal/models/public/appkit_network_info.dart new file mode 100644 index 0000000..84de4d6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_network_info.dart @@ -0,0 +1,40 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'appkit_network_info.freezed.dart'; + +@freezed +class AppKitModalNetworkInfo with _$AppKitModalNetworkInfo { + factory AppKitModalNetworkInfo({ + required String name, + required String chainId, + required String currency, + required String rpcUrl, + required String explorerUrl, + @Default([]) List extraRpcUrls, + @Default(false) isTestNetwork, + String? chainIcon, + }) = _AppKitNetworkInfo; +} + +extension AppKitNetworkInfoExtension on AppKitModalNetworkInfo { + String get chainHexId => '0x${int.parse(chainId).toRadixString(16)}'; + + Map toJson() { + return { + 'chainId': chainHexId, + 'chainName': name, + 'nativeCurrency': { + 'name': currency, + 'symbol': currency, + 'decimals': 18, + }, + 'rpcUrls': [ + rpcUrl, + ...extraRpcUrls, + ], + 'blockExplorerUrls': [ + explorerUrl, + ], + }; + } +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_network_info.freezed.dart b/packages/reown_appkit/lib/modal/models/public/appkit_network_info.freezed.dart new file mode 100644 index 0000000..78db1b6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_network_info.freezed.dart @@ -0,0 +1,299 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_network_info.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalNetworkInfo { + String get name => throw _privateConstructorUsedError; + String get chainId => throw _privateConstructorUsedError; + String get currency => throw _privateConstructorUsedError; + String get rpcUrl => throw _privateConstructorUsedError; + String get explorerUrl => throw _privateConstructorUsedError; + List get extraRpcUrls => throw _privateConstructorUsedError; + dynamic get isTestNetwork => throw _privateConstructorUsedError; + String? get chainIcon => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalNetworkInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalNetworkInfoCopyWith<$Res> { + factory $AppKitModalNetworkInfoCopyWith(AppKitModalNetworkInfo value, + $Res Function(AppKitModalNetworkInfo) then) = + _$AppKitModalNetworkInfoCopyWithImpl<$Res, AppKitModalNetworkInfo>; + @useResult + $Res call( + {String name, + String chainId, + String currency, + String rpcUrl, + String explorerUrl, + List extraRpcUrls, + dynamic isTestNetwork, + String? chainIcon}); +} + +/// @nodoc +class _$AppKitModalNetworkInfoCopyWithImpl<$Res, + $Val extends AppKitModalNetworkInfo> + implements $AppKitModalNetworkInfoCopyWith<$Res> { + _$AppKitModalNetworkInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? chainId = null, + Object? currency = null, + Object? rpcUrl = null, + Object? explorerUrl = null, + Object? extraRpcUrls = null, + Object? isTestNetwork = freezed, + Object? chainIcon = freezed, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + currency: null == currency + ? _value.currency + : currency // ignore: cast_nullable_to_non_nullable + as String, + rpcUrl: null == rpcUrl + ? _value.rpcUrl + : rpcUrl // ignore: cast_nullable_to_non_nullable + as String, + explorerUrl: null == explorerUrl + ? _value.explorerUrl + : explorerUrl // ignore: cast_nullable_to_non_nullable + as String, + extraRpcUrls: null == extraRpcUrls + ? _value.extraRpcUrls + : extraRpcUrls // ignore: cast_nullable_to_non_nullable + as List, + isTestNetwork: freezed == isTestNetwork + ? _value.isTestNetwork + : isTestNetwork // ignore: cast_nullable_to_non_nullable + as dynamic, + chainIcon: freezed == chainIcon + ? _value.chainIcon + : chainIcon // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitNetworkInfoImplCopyWith<$Res> + implements $AppKitModalNetworkInfoCopyWith<$Res> { + factory _$$AppKitNetworkInfoImplCopyWith(_$AppKitNetworkInfoImpl value, + $Res Function(_$AppKitNetworkInfoImpl) then) = + __$$AppKitNetworkInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String name, + String chainId, + String currency, + String rpcUrl, + String explorerUrl, + List extraRpcUrls, + dynamic isTestNetwork, + String? chainIcon}); +} + +/// @nodoc +class __$$AppKitNetworkInfoImplCopyWithImpl<$Res> + extends _$AppKitModalNetworkInfoCopyWithImpl<$Res, _$AppKitNetworkInfoImpl> + implements _$$AppKitNetworkInfoImplCopyWith<$Res> { + __$$AppKitNetworkInfoImplCopyWithImpl(_$AppKitNetworkInfoImpl _value, + $Res Function(_$AppKitNetworkInfoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? chainId = null, + Object? currency = null, + Object? rpcUrl = null, + Object? explorerUrl = null, + Object? extraRpcUrls = null, + Object? isTestNetwork = freezed, + Object? chainIcon = freezed, + }) { + return _then(_$AppKitNetworkInfoImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + currency: null == currency + ? _value.currency + : currency // ignore: cast_nullable_to_non_nullable + as String, + rpcUrl: null == rpcUrl + ? _value.rpcUrl + : rpcUrl // ignore: cast_nullable_to_non_nullable + as String, + explorerUrl: null == explorerUrl + ? _value.explorerUrl + : explorerUrl // ignore: cast_nullable_to_non_nullable + as String, + extraRpcUrls: null == extraRpcUrls + ? _value._extraRpcUrls + : extraRpcUrls // ignore: cast_nullable_to_non_nullable + as List, + isTestNetwork: + freezed == isTestNetwork ? _value.isTestNetwork! : isTestNetwork, + chainIcon: freezed == chainIcon + ? _value.chainIcon + : chainIcon // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$AppKitNetworkInfoImpl implements _AppKitNetworkInfo { + _$AppKitNetworkInfoImpl( + {required this.name, + required this.chainId, + required this.currency, + required this.rpcUrl, + required this.explorerUrl, + final List extraRpcUrls = const [], + this.isTestNetwork = false, + this.chainIcon}) + : _extraRpcUrls = extraRpcUrls; + + @override + final String name; + @override + final String chainId; + @override + final String currency; + @override + final String rpcUrl; + @override + final String explorerUrl; + final List _extraRpcUrls; + @override + @JsonKey() + List get extraRpcUrls { + if (_extraRpcUrls is EqualUnmodifiableListView) return _extraRpcUrls; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_extraRpcUrls); + } + + @override + @JsonKey() + final dynamic isTestNetwork; + @override + final String? chainIcon; + + @override + String toString() { + return 'AppKitModalNetworkInfo(name: $name, chainId: $chainId, currency: $currency, rpcUrl: $rpcUrl, explorerUrl: $explorerUrl, extraRpcUrls: $extraRpcUrls, isTestNetwork: $isTestNetwork, chainIcon: $chainIcon)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitNetworkInfoImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.chainId, chainId) || other.chainId == chainId) && + (identical(other.currency, currency) || + other.currency == currency) && + (identical(other.rpcUrl, rpcUrl) || other.rpcUrl == rpcUrl) && + (identical(other.explorerUrl, explorerUrl) || + other.explorerUrl == explorerUrl) && + const DeepCollectionEquality() + .equals(other._extraRpcUrls, _extraRpcUrls) && + const DeepCollectionEquality() + .equals(other.isTestNetwork, isTestNetwork) && + (identical(other.chainIcon, chainIcon) || + other.chainIcon == chainIcon)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + name, + chainId, + currency, + rpcUrl, + explorerUrl, + const DeepCollectionEquality().hash(_extraRpcUrls), + const DeepCollectionEquality().hash(isTestNetwork), + chainIcon); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitNetworkInfoImplCopyWith<_$AppKitNetworkInfoImpl> get copyWith => + __$$AppKitNetworkInfoImplCopyWithImpl<_$AppKitNetworkInfoImpl>( + this, _$identity); +} + +abstract class _AppKitNetworkInfo implements AppKitModalNetworkInfo { + factory _AppKitNetworkInfo( + {required final String name, + required final String chainId, + required final String currency, + required final String rpcUrl, + required final String explorerUrl, + final List extraRpcUrls, + final dynamic isTestNetwork, + final String? chainIcon}) = _$AppKitNetworkInfoImpl; + + @override + String get name; + @override + String get chainId; + @override + String get currency; + @override + String get rpcUrl; + @override + String get explorerUrl; + @override + List get extraRpcUrls; + @override + dynamic get isTestNetwork; + @override + String? get chainIcon; + @override + @JsonKey(ignore: true) + _$$AppKitNetworkInfoImplCopyWith<_$AppKitNetworkInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart new file mode 100644 index 0000000..8ea32e1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +part 'appkit_siwe_config.g.dart'; +part 'appkit_siwe_config.freezed.dart'; + +class SIWEConfig { + final Future Function() getNonce; + final Future Function() getMessageParams; + final String Function(SIWECreateMessageArgs args) createMessage; + final Future Function(SIWEVerifyMessageArgs args) verifyMessage; + final Future Function() getSession; + final Future Function() signOut; + // Callback when user signs in + final Function(SIWESession session)? onSignIn; + // Callback when user signs out + final VoidCallback? onSignOut; + // Defaults to true + final bool enabled; + // In milliseconds, defaults to 5 minutes + final int nonceRefetchIntervalMs; + // In milliseconds, defaults to 5 minutes + final int sessionRefetchIntervalMs; + // Defaults to true + final bool signOutOnDisconnect; + // Defaults to true + final bool signOutOnAccountChange; + // Defaults to true + final bool signOutOnNetworkChange; + // + + SIWEConfig({ + required this.getNonce, + required this.getMessageParams, + required this.createMessage, + required this.verifyMessage, + required this.getSession, + required this.signOut, + this.onSignIn, + this.onSignOut, + this.enabled = true, + this.signOutOnDisconnect = true, + this.signOutOnAccountChange = true, + this.signOutOnNetworkChange = true, + this.nonceRefetchIntervalMs = 300000, + this.sessionRefetchIntervalMs = 300000, + }); +} + +@freezed +class SIWECreateMessageArgs with _$SIWECreateMessageArgs { + const factory SIWECreateMessageArgs({ + required String chainId, + required String domain, + required String nonce, + required String uri, + required String address, + @Default('1') String version, + @Default(CacaoHeader(t: 'eip4361')) CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + String? iat, + }) = _SIWECreateMessageArgs; + + factory SIWECreateMessageArgs.fromSIWEMessageArgs( + SIWEMessageArgs params, { + required String address, + required String chainId, + required String nonce, + required CacaoHeader type, + }) { + final now = DateTime.now(); + return SIWECreateMessageArgs( + chainId: chainId, + nonce: nonce, + address: address, + version: '1', + iat: params.iat ?? + DateTime.utc( + now.year, + now.month, + now.day, + now.hour, + now.minute, + now.second, + now.millisecond, + ).toIso8601String(), + domain: params.domain, + uri: params.uri, + type: type, + nbf: params.nbf, + exp: params.exp, + statement: params.statement, + requestId: params.requestId, + resources: params.resources, + expiry: params.expiry, + ); + } + + factory SIWECreateMessageArgs.fromJson(Map json) => + _$SIWECreateMessageArgsFromJson(json); +} + +@freezed +class SIWEMessageArgs with _$SIWEMessageArgs { + const factory SIWEMessageArgs({ + required String domain, + required String uri, + @Default(CacaoHeader(t: 'eip4361')) CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + String? iat, + List? methods, + }) = _SIWEMessageArgs; + + factory SIWEMessageArgs.fromJson(Map json) => + _$SIWEMessageArgsFromJson(json); +} + +@freezed +class SIWEVerifyMessageArgs with _$SIWEVerifyMessageArgs { + const factory SIWEVerifyMessageArgs({ + required String message, + required String signature, + Cacao? cacao, // for One-Click Auth + String? clientId, // Not really used in mobile platforms + }) = _SIWEVerifyMessageArgs; + + factory SIWEVerifyMessageArgs.fromJson(Map json) => + _$SIWEVerifyMessageArgsFromJson(json); +} + +@freezed +class SIWESession with _$SIWESession { + const factory SIWESession({ + required String address, + required List chains, + }) = _SIWESession; + + factory SIWESession.fromJson(Map json) => + _$SIWESessionFromJson(json); + + @override + String toString() => 'SIWESession($address, $chains)'; +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.freezed.dart b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.freezed.dart new file mode 100644 index 0000000..813334e --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.freezed.dart @@ -0,0 +1,1232 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_siwe_config.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +SIWECreateMessageArgs _$SIWECreateMessageArgsFromJson( + Map json) { + return _SIWECreateMessageArgs.fromJson(json); +} + +/// @nodoc +mixin _$SIWECreateMessageArgs { + String get chainId => throw _privateConstructorUsedError; + String get domain => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + String get uri => throw _privateConstructorUsedError; + String get address => throw _privateConstructorUsedError; + String get version => throw _privateConstructorUsedError; + CacaoHeader? get type => throw _privateConstructorUsedError; + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + int? get expiry => throw _privateConstructorUsedError; + String? get iat => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SIWECreateMessageArgsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SIWECreateMessageArgsCopyWith<$Res> { + factory $SIWECreateMessageArgsCopyWith(SIWECreateMessageArgs value, + $Res Function(SIWECreateMessageArgs) then) = + _$SIWECreateMessageArgsCopyWithImpl<$Res, SIWECreateMessageArgs>; + @useResult + $Res call( + {String chainId, + String domain, + String nonce, + String uri, + String address, + String version, + CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + String? iat}); + + $CacaoHeaderCopyWith<$Res>? get type; +} + +/// @nodoc +class _$SIWECreateMessageArgsCopyWithImpl<$Res, + $Val extends SIWECreateMessageArgs> + implements $SIWECreateMessageArgsCopyWith<$Res> { + _$SIWECreateMessageArgsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chainId = null, + Object? domain = null, + Object? nonce = null, + Object? uri = null, + Object? address = null, + Object? version = null, + Object? type = freezed, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + Object? expiry = freezed, + Object? iat = freezed, + }) { + return _then(_value.copyWith( + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + uri: null == uri + ? _value.uri + : uri // ignore: cast_nullable_to_non_nullable + as String, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as CacaoHeader?, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + expiry: freezed == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int?, + iat: freezed == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $CacaoHeaderCopyWith<$Res>? get type { + if (_value.type == null) { + return null; + } + + return $CacaoHeaderCopyWith<$Res>(_value.type!, (value) { + return _then(_value.copyWith(type: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SIWECreateMessageArgsImplCopyWith<$Res> + implements $SIWECreateMessageArgsCopyWith<$Res> { + factory _$$SIWECreateMessageArgsImplCopyWith( + _$SIWECreateMessageArgsImpl value, + $Res Function(_$SIWECreateMessageArgsImpl) then) = + __$$SIWECreateMessageArgsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String chainId, + String domain, + String nonce, + String uri, + String address, + String version, + CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + String? iat}); + + @override + $CacaoHeaderCopyWith<$Res>? get type; +} + +/// @nodoc +class __$$SIWECreateMessageArgsImplCopyWithImpl<$Res> + extends _$SIWECreateMessageArgsCopyWithImpl<$Res, + _$SIWECreateMessageArgsImpl> + implements _$$SIWECreateMessageArgsImplCopyWith<$Res> { + __$$SIWECreateMessageArgsImplCopyWithImpl(_$SIWECreateMessageArgsImpl _value, + $Res Function(_$SIWECreateMessageArgsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chainId = null, + Object? domain = null, + Object? nonce = null, + Object? uri = null, + Object? address = null, + Object? version = null, + Object? type = freezed, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + Object? expiry = freezed, + Object? iat = freezed, + }) { + return _then(_$SIWECreateMessageArgsImpl( + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + uri: null == uri + ? _value.uri + : uri // ignore: cast_nullable_to_non_nullable + as String, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as CacaoHeader?, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + expiry: freezed == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int?, + iat: freezed == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SIWECreateMessageArgsImpl implements _SIWECreateMessageArgs { + const _$SIWECreateMessageArgsImpl( + {required this.chainId, + required this.domain, + required this.nonce, + required this.uri, + required this.address, + this.version = '1', + this.type = const CacaoHeader(t: 'eip4361'), + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources, + this.expiry, + this.iat}) + : _resources = resources; + + factory _$SIWECreateMessageArgsImpl.fromJson(Map json) => + _$$SIWECreateMessageArgsImplFromJson(json); + + @override + final String chainId; + @override + final String domain; + @override + final String nonce; + @override + final String uri; + @override + final String address; + @override + @JsonKey() + final String version; + @override + @JsonKey() + final CacaoHeader? type; + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + final int? expiry; + @override + final String? iat; + + @override + String toString() { + return 'SIWECreateMessageArgs(chainId: $chainId, domain: $domain, nonce: $nonce, uri: $uri, address: $address, version: $version, type: $type, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources, expiry: $expiry, iat: $iat)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SIWECreateMessageArgsImpl && + (identical(other.chainId, chainId) || other.chainId == chainId) && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.uri, uri) || other.uri == uri) && + (identical(other.address, address) || other.address == address) && + (identical(other.version, version) || other.version == version) && + (identical(other.type, type) || other.type == type) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources) && + (identical(other.expiry, expiry) || other.expiry == expiry) && + (identical(other.iat, iat) || other.iat == iat)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + chainId, + domain, + nonce, + uri, + address, + version, + type, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources), + expiry, + iat); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SIWECreateMessageArgsImplCopyWith<_$SIWECreateMessageArgsImpl> + get copyWith => __$$SIWECreateMessageArgsImplCopyWithImpl< + _$SIWECreateMessageArgsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SIWECreateMessageArgsImplToJson( + this, + ); + } +} + +abstract class _SIWECreateMessageArgs implements SIWECreateMessageArgs { + const factory _SIWECreateMessageArgs( + {required final String chainId, + required final String domain, + required final String nonce, + required final String uri, + required final String address, + final String version, + final CacaoHeader? type, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources, + final int? expiry, + final String? iat}) = _$SIWECreateMessageArgsImpl; + + factory _SIWECreateMessageArgs.fromJson(Map json) = + _$SIWECreateMessageArgsImpl.fromJson; + + @override + String get chainId; + @override + String get domain; + @override + String get nonce; + @override + String get uri; + @override + String get address; + @override + String get version; + @override + CacaoHeader? get type; + @override + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + int? get expiry; + @override + String? get iat; + @override + @JsonKey(ignore: true) + _$$SIWECreateMessageArgsImplCopyWith<_$SIWECreateMessageArgsImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SIWEMessageArgs _$SIWEMessageArgsFromJson(Map json) { + return _SIWEMessageArgs.fromJson(json); +} + +/// @nodoc +mixin _$SIWEMessageArgs { + String get domain => throw _privateConstructorUsedError; + String get uri => throw _privateConstructorUsedError; + CacaoHeader? get type => throw _privateConstructorUsedError; + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + int? get expiry => throw _privateConstructorUsedError; + String? get iat => throw _privateConstructorUsedError; + List? get methods => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SIWEMessageArgsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SIWEMessageArgsCopyWith<$Res> { + factory $SIWEMessageArgsCopyWith( + SIWEMessageArgs value, $Res Function(SIWEMessageArgs) then) = + _$SIWEMessageArgsCopyWithImpl<$Res, SIWEMessageArgs>; + @useResult + $Res call( + {String domain, + String uri, + CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + String? iat, + List? methods}); + + $CacaoHeaderCopyWith<$Res>? get type; +} + +/// @nodoc +class _$SIWEMessageArgsCopyWithImpl<$Res, $Val extends SIWEMessageArgs> + implements $SIWEMessageArgsCopyWith<$Res> { + _$SIWEMessageArgsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? domain = null, + Object? uri = null, + Object? type = freezed, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + Object? expiry = freezed, + Object? iat = freezed, + Object? methods = freezed, + }) { + return _then(_value.copyWith( + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + uri: null == uri + ? _value.uri + : uri // ignore: cast_nullable_to_non_nullable + as String, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as CacaoHeader?, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + expiry: freezed == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int?, + iat: freezed == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String?, + methods: freezed == methods + ? _value.methods + : methods // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $CacaoHeaderCopyWith<$Res>? get type { + if (_value.type == null) { + return null; + } + + return $CacaoHeaderCopyWith<$Res>(_value.type!, (value) { + return _then(_value.copyWith(type: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SIWEMessageArgsImplCopyWith<$Res> + implements $SIWEMessageArgsCopyWith<$Res> { + factory _$$SIWEMessageArgsImplCopyWith(_$SIWEMessageArgsImpl value, + $Res Function(_$SIWEMessageArgsImpl) then) = + __$$SIWEMessageArgsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String domain, + String uri, + CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + String? iat, + List? methods}); + + @override + $CacaoHeaderCopyWith<$Res>? get type; +} + +/// @nodoc +class __$$SIWEMessageArgsImplCopyWithImpl<$Res> + extends _$SIWEMessageArgsCopyWithImpl<$Res, _$SIWEMessageArgsImpl> + implements _$$SIWEMessageArgsImplCopyWith<$Res> { + __$$SIWEMessageArgsImplCopyWithImpl( + _$SIWEMessageArgsImpl _value, $Res Function(_$SIWEMessageArgsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? domain = null, + Object? uri = null, + Object? type = freezed, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + Object? expiry = freezed, + Object? iat = freezed, + Object? methods = freezed, + }) { + return _then(_$SIWEMessageArgsImpl( + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + uri: null == uri + ? _value.uri + : uri // ignore: cast_nullable_to_non_nullable + as String, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as CacaoHeader?, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + expiry: freezed == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int?, + iat: freezed == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String?, + methods: freezed == methods + ? _value._methods + : methods // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SIWEMessageArgsImpl implements _SIWEMessageArgs { + const _$SIWEMessageArgsImpl( + {required this.domain, + required this.uri, + this.type = const CacaoHeader(t: 'eip4361'), + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources, + this.expiry, + this.iat, + final List? methods}) + : _resources = resources, + _methods = methods; + + factory _$SIWEMessageArgsImpl.fromJson(Map json) => + _$$SIWEMessageArgsImplFromJson(json); + + @override + final String domain; + @override + final String uri; + @override + @JsonKey() + final CacaoHeader? type; + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + final int? expiry; + @override + final String? iat; + final List? _methods; + @override + List? get methods { + final value = _methods; + if (value == null) return null; + if (_methods is EqualUnmodifiableListView) return _methods; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'SIWEMessageArgs(domain: $domain, uri: $uri, type: $type, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources, expiry: $expiry, iat: $iat, methods: $methods)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SIWEMessageArgsImpl && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.uri, uri) || other.uri == uri) && + (identical(other.type, type) || other.type == type) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources) && + (identical(other.expiry, expiry) || other.expiry == expiry) && + (identical(other.iat, iat) || other.iat == iat) && + const DeepCollectionEquality().equals(other._methods, _methods)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + domain, + uri, + type, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources), + expiry, + iat, + const DeepCollectionEquality().hash(_methods)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SIWEMessageArgsImplCopyWith<_$SIWEMessageArgsImpl> get copyWith => + __$$SIWEMessageArgsImplCopyWithImpl<_$SIWEMessageArgsImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SIWEMessageArgsImplToJson( + this, + ); + } +} + +abstract class _SIWEMessageArgs implements SIWEMessageArgs { + const factory _SIWEMessageArgs( + {required final String domain, + required final String uri, + final CacaoHeader? type, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources, + final int? expiry, + final String? iat, + final List? methods}) = _$SIWEMessageArgsImpl; + + factory _SIWEMessageArgs.fromJson(Map json) = + _$SIWEMessageArgsImpl.fromJson; + + @override + String get domain; + @override + String get uri; + @override + CacaoHeader? get type; + @override + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + int? get expiry; + @override + String? get iat; + @override + List? get methods; + @override + @JsonKey(ignore: true) + _$$SIWEMessageArgsImplCopyWith<_$SIWEMessageArgsImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SIWEVerifyMessageArgs _$SIWEVerifyMessageArgsFromJson( + Map json) { + return _SIWEVerifyMessageArgs.fromJson(json); +} + +/// @nodoc +mixin _$SIWEVerifyMessageArgs { + String get message => throw _privateConstructorUsedError; + String get signature => throw _privateConstructorUsedError; + Cacao? get cacao => throw _privateConstructorUsedError; // for One-Click Auth + String? get clientId => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SIWEVerifyMessageArgsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SIWEVerifyMessageArgsCopyWith<$Res> { + factory $SIWEVerifyMessageArgsCopyWith(SIWEVerifyMessageArgs value, + $Res Function(SIWEVerifyMessageArgs) then) = + _$SIWEVerifyMessageArgsCopyWithImpl<$Res, SIWEVerifyMessageArgs>; + @useResult + $Res call({String message, String signature, Cacao? cacao, String? clientId}); + + $CacaoCopyWith<$Res>? get cacao; +} + +/// @nodoc +class _$SIWEVerifyMessageArgsCopyWithImpl<$Res, + $Val extends SIWEVerifyMessageArgs> + implements $SIWEVerifyMessageArgsCopyWith<$Res> { + _$SIWEVerifyMessageArgsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? message = null, + Object? signature = null, + Object? cacao = freezed, + Object? clientId = freezed, + }) { + return _then(_value.copyWith( + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as String, + cacao: freezed == cacao + ? _value.cacao + : cacao // ignore: cast_nullable_to_non_nullable + as Cacao?, + clientId: freezed == clientId + ? _value.clientId + : clientId // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $CacaoCopyWith<$Res>? get cacao { + if (_value.cacao == null) { + return null; + } + + return $CacaoCopyWith<$Res>(_value.cacao!, (value) { + return _then(_value.copyWith(cacao: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SIWEVerifyMessageArgsImplCopyWith<$Res> + implements $SIWEVerifyMessageArgsCopyWith<$Res> { + factory _$$SIWEVerifyMessageArgsImplCopyWith( + _$SIWEVerifyMessageArgsImpl value, + $Res Function(_$SIWEVerifyMessageArgsImpl) then) = + __$$SIWEVerifyMessageArgsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String message, String signature, Cacao? cacao, String? clientId}); + + @override + $CacaoCopyWith<$Res>? get cacao; +} + +/// @nodoc +class __$$SIWEVerifyMessageArgsImplCopyWithImpl<$Res> + extends _$SIWEVerifyMessageArgsCopyWithImpl<$Res, + _$SIWEVerifyMessageArgsImpl> + implements _$$SIWEVerifyMessageArgsImplCopyWith<$Res> { + __$$SIWEVerifyMessageArgsImplCopyWithImpl(_$SIWEVerifyMessageArgsImpl _value, + $Res Function(_$SIWEVerifyMessageArgsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? message = null, + Object? signature = null, + Object? cacao = freezed, + Object? clientId = freezed, + }) { + return _then(_$SIWEVerifyMessageArgsImpl( + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as String, + cacao: freezed == cacao + ? _value.cacao + : cacao // ignore: cast_nullable_to_non_nullable + as Cacao?, + clientId: freezed == clientId + ? _value.clientId + : clientId // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SIWEVerifyMessageArgsImpl implements _SIWEVerifyMessageArgs { + const _$SIWEVerifyMessageArgsImpl( + {required this.message, + required this.signature, + this.cacao, + this.clientId}); + + factory _$SIWEVerifyMessageArgsImpl.fromJson(Map json) => + _$$SIWEVerifyMessageArgsImplFromJson(json); + + @override + final String message; + @override + final String signature; + @override + final Cacao? cacao; +// for One-Click Auth + @override + final String? clientId; + + @override + String toString() { + return 'SIWEVerifyMessageArgs(message: $message, signature: $signature, cacao: $cacao, clientId: $clientId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SIWEVerifyMessageArgsImpl && + (identical(other.message, message) || other.message == message) && + (identical(other.signature, signature) || + other.signature == signature) && + (identical(other.cacao, cacao) || other.cacao == cacao) && + (identical(other.clientId, clientId) || + other.clientId == clientId)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, message, signature, cacao, clientId); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SIWEVerifyMessageArgsImplCopyWith<_$SIWEVerifyMessageArgsImpl> + get copyWith => __$$SIWEVerifyMessageArgsImplCopyWithImpl< + _$SIWEVerifyMessageArgsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SIWEVerifyMessageArgsImplToJson( + this, + ); + } +} + +abstract class _SIWEVerifyMessageArgs implements SIWEVerifyMessageArgs { + const factory _SIWEVerifyMessageArgs( + {required final String message, + required final String signature, + final Cacao? cacao, + final String? clientId}) = _$SIWEVerifyMessageArgsImpl; + + factory _SIWEVerifyMessageArgs.fromJson(Map json) = + _$SIWEVerifyMessageArgsImpl.fromJson; + + @override + String get message; + @override + String get signature; + @override + Cacao? get cacao; + @override // for One-Click Auth + String? get clientId; + @override + @JsonKey(ignore: true) + _$$SIWEVerifyMessageArgsImplCopyWith<_$SIWEVerifyMessageArgsImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SIWESession _$SIWESessionFromJson(Map json) { + return _SIWESession.fromJson(json); +} + +/// @nodoc +mixin _$SIWESession { + String get address => throw _privateConstructorUsedError; + List get chains => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SIWESessionCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SIWESessionCopyWith<$Res> { + factory $SIWESessionCopyWith( + SIWESession value, $Res Function(SIWESession) then) = + _$SIWESessionCopyWithImpl<$Res, SIWESession>; + @useResult + $Res call({String address, List chains}); +} + +/// @nodoc +class _$SIWESessionCopyWithImpl<$Res, $Val extends SIWESession> + implements $SIWESessionCopyWith<$Res> { + _$SIWESessionCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? address = null, + Object? chains = null, + }) { + return _then(_value.copyWith( + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + chains: null == chains + ? _value.chains + : chains // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SIWESessionImplCopyWith<$Res> + implements $SIWESessionCopyWith<$Res> { + factory _$$SIWESessionImplCopyWith( + _$SIWESessionImpl value, $Res Function(_$SIWESessionImpl) then) = + __$$SIWESessionImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String address, List chains}); +} + +/// @nodoc +class __$$SIWESessionImplCopyWithImpl<$Res> + extends _$SIWESessionCopyWithImpl<$Res, _$SIWESessionImpl> + implements _$$SIWESessionImplCopyWith<$Res> { + __$$SIWESessionImplCopyWithImpl( + _$SIWESessionImpl _value, $Res Function(_$SIWESessionImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? address = null, + Object? chains = null, + }) { + return _then(_$SIWESessionImpl( + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + chains: null == chains + ? _value._chains + : chains // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SIWESessionImpl implements _SIWESession { + const _$SIWESessionImpl( + {required this.address, required final List chains}) + : _chains = chains; + + factory _$SIWESessionImpl.fromJson(Map json) => + _$$SIWESessionImplFromJson(json); + + @override + final String address; + final List _chains; + @override + List get chains { + if (_chains is EqualUnmodifiableListView) return _chains; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_chains); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SIWESessionImpl && + (identical(other.address, address) || other.address == address) && + const DeepCollectionEquality().equals(other._chains, _chains)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, address, const DeepCollectionEquality().hash(_chains)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SIWESessionImplCopyWith<_$SIWESessionImpl> get copyWith => + __$$SIWESessionImplCopyWithImpl<_$SIWESessionImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SIWESessionImplToJson( + this, + ); + } +} + +abstract class _SIWESession implements SIWESession { + const factory _SIWESession( + {required final String address, + required final List chains}) = _$SIWESessionImpl; + + factory _SIWESession.fromJson(Map json) = + _$SIWESessionImpl.fromJson; + + @override + String get address; + @override + List get chains; + @override + @JsonKey(ignore: true) + _$$SIWESessionImplCopyWith<_$SIWESessionImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.g.dart b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.g.dart new file mode 100644 index 0000000..6e1b6c5 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.g.dart @@ -0,0 +1,119 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'appkit_siwe_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SIWECreateMessageArgsImpl _$$SIWECreateMessageArgsImplFromJson( + Map json) => + _$SIWECreateMessageArgsImpl( + chainId: json['chainId'] as String, + domain: json['domain'] as String, + nonce: json['nonce'] as String, + uri: json['uri'] as String, + address: json['address'] as String, + version: json['version'] as String? ?? '1', + type: json['type'] == null + ? const CacaoHeader(t: 'eip4361') + : CacaoHeader.fromJson(json['type'] as Map), + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + expiry: (json['expiry'] as num?)?.toInt(), + iat: json['iat'] as String?, + ); + +Map _$$SIWECreateMessageArgsImplToJson( + _$SIWECreateMessageArgsImpl instance) => + { + 'chainId': instance.chainId, + 'domain': instance.domain, + 'nonce': instance.nonce, + 'uri': instance.uri, + 'address': instance.address, + 'version': instance.version, + 'type': instance.type?.toJson(), + 'nbf': instance.nbf, + 'exp': instance.exp, + 'statement': instance.statement, + 'requestId': instance.requestId, + 'resources': instance.resources, + 'expiry': instance.expiry, + 'iat': instance.iat, + }; + +_$SIWEMessageArgsImpl _$$SIWEMessageArgsImplFromJson( + Map json) => + _$SIWEMessageArgsImpl( + domain: json['domain'] as String, + uri: json['uri'] as String, + type: json['type'] == null + ? const CacaoHeader(t: 'eip4361') + : CacaoHeader.fromJson(json['type'] as Map), + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + expiry: (json['expiry'] as num?)?.toInt(), + iat: json['iat'] as String?, + methods: + (json['methods'] as List?)?.map((e) => e as String).toList(), + ); + +Map _$$SIWEMessageArgsImplToJson( + _$SIWEMessageArgsImpl instance) => + { + 'domain': instance.domain, + 'uri': instance.uri, + 'type': instance.type?.toJson(), + 'nbf': instance.nbf, + 'exp': instance.exp, + 'statement': instance.statement, + 'requestId': instance.requestId, + 'resources': instance.resources, + 'expiry': instance.expiry, + 'iat': instance.iat, + 'methods': instance.methods, + }; + +_$SIWEVerifyMessageArgsImpl _$$SIWEVerifyMessageArgsImplFromJson( + Map json) => + _$SIWEVerifyMessageArgsImpl( + message: json['message'] as String, + signature: json['signature'] as String, + cacao: json['cacao'] == null + ? null + : Cacao.fromJson(json['cacao'] as Map), + clientId: json['clientId'] as String?, + ); + +Map _$$SIWEVerifyMessageArgsImplToJson( + _$SIWEVerifyMessageArgsImpl instance) => + { + 'message': instance.message, + 'signature': instance.signature, + 'cacao': instance.cacao?.toJson(), + 'clientId': instance.clientId, + }; + +_$SIWESessionImpl _$$SIWESessionImplFromJson(Map json) => + _$SIWESessionImpl( + address: json['address'] as String, + chains: + (json['chains'] as List).map((e) => e as String).toList(), + ); + +Map _$$SIWESessionImplToJson(_$SIWESessionImpl instance) => + { + 'address': instance.address, + 'chains': instance.chains, + }; diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.dart b/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.dart new file mode 100644 index 0000000..64d087a --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.dart @@ -0,0 +1,157 @@ +import 'dart:convert'; + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'appkit_wallet_info.freezed.dart'; +part 'appkit_wallet_info.g.dart'; + +@freezed +class AppKitModalWalletInfo with _$AppKitModalWalletInfo { + const factory AppKitModalWalletInfo({ + required Listing listing, + required bool installed, + required bool recent, + }) = _AppKitWalletInfo; + + factory AppKitModalWalletInfo.fromJson(Map json) => + _$AppKitModalWalletInfoFromJson(json); +} + +extension AppKitWalletInfoExtension on AppKitModalWalletInfo { + bool get isCoinbase => + listing.id == + 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa'; +} + +class Listing { + final String id; + final String name; + final String homepage; + final String imageId; + final int order; + final String? mobileLink; + final String? desktopLink; + final String? webappLink; + final String? linkMode; + final String? appStore; + final String? playStore; + final String? rdns; + final List? injected; + + const Listing({ + required this.id, + required this.name, + required this.homepage, + required this.imageId, + required this.order, + this.mobileLink, + this.desktopLink, + this.webappLink, + this.linkMode, + this.appStore, + this.playStore, + this.rdns, + this.injected, + }); + + Listing copyWith({ + String? id, + String? name, + String? homepage, + String? imageId, + int? order, + String? mobileLink, + String? desktopLink, + String? webappLink, + String? linkMode, + String? appStore, + String? playStore, + String? rdns, + List? injected, + }) => + Listing( + id: id ?? this.id, + name: name ?? this.name, + homepage: homepage ?? this.homepage, + imageId: imageId ?? this.imageId, + order: order ?? this.order, + mobileLink: mobileLink ?? this.mobileLink, + desktopLink: desktopLink ?? this.desktopLink, + webappLink: webappLink ?? this.webappLink, + linkMode: linkMode ?? this.linkMode, + appStore: appStore ?? this.appStore, + playStore: playStore ?? this.playStore, + rdns: rdns ?? this.rdns, + injected: injected ?? this.injected, + ); + + factory Listing.fromRawJson(String str) => Listing.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Listing.fromJson(Object? json) { + final j = json as Map? ?? {}; + return Listing( + id: j['id'].toString(), + name: j['name'], + homepage: j['homepage'], + imageId: j['image_id'], + order: j['order'], + mobileLink: j['mobile_link'], + desktopLink: j['desktop_link'], + webappLink: j['webapp_link'], + appStore: j['app_store'], + playStore: j['play_store'], + rdns: j['rdns'], + injected: j['injected'] == null + ? [] + : List.from( + j['injected']!.map((x) => Injected.fromJson(x)), + ), + ); + } + + Map toJson() => { + 'id': id, + 'name': name, + 'homepage': homepage, + 'image_id': imageId, + 'order': order, + 'mobile_link': mobileLink, + 'desktop_link': desktopLink, + 'webapp_link': webappLink, + 'app_store': appStore, + 'play_store': playStore, + 'rdns': rdns, + 'injected': injected == null + ? [] + : List.from(injected!.map((x) => x.toJson())), + }; +} + +class Injected { + final String namespace; + final String injectedId; + + Injected({required this.namespace, required this.injectedId}); + + Injected copyWith({String? namespace, String? injectedId}) => Injected( + namespace: namespace ?? this.namespace, + injectedId: injectedId ?? this.injectedId, + ); + + factory Injected.fromRawJson(String str) => + Injected.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Injected.fromJson(Map json) => Injected( + namespace: json['namespace'], + injectedId: json['injected_id'], + ); + + Map toJson() => { + 'namespace': namespace, + 'injected_id': injectedId, + }; +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.freezed.dart b/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.freezed.dart new file mode 100644 index 0000000..29ecfe2 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.freezed.dart @@ -0,0 +1,191 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_wallet_info.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +AppKitModalWalletInfo _$AppKitModalWalletInfoFromJson( + Map json) { + return _AppKitWalletInfo.fromJson(json); +} + +/// @nodoc +mixin _$AppKitModalWalletInfo { + Listing get listing => throw _privateConstructorUsedError; + bool get installed => throw _privateConstructorUsedError; + bool get recent => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AppKitModalWalletInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalWalletInfoCopyWith<$Res> { + factory $AppKitModalWalletInfoCopyWith(AppKitModalWalletInfo value, + $Res Function(AppKitModalWalletInfo) then) = + _$AppKitModalWalletInfoCopyWithImpl<$Res, AppKitModalWalletInfo>; + @useResult + $Res call({Listing listing, bool installed, bool recent}); +} + +/// @nodoc +class _$AppKitModalWalletInfoCopyWithImpl<$Res, + $Val extends AppKitModalWalletInfo> + implements $AppKitModalWalletInfoCopyWith<$Res> { + _$AppKitModalWalletInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? listing = null, + Object? installed = null, + Object? recent = null, + }) { + return _then(_value.copyWith( + listing: null == listing + ? _value.listing + : listing // ignore: cast_nullable_to_non_nullable + as Listing, + installed: null == installed + ? _value.installed + : installed // ignore: cast_nullable_to_non_nullable + as bool, + recent: null == recent + ? _value.recent + : recent // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitWalletInfoImplCopyWith<$Res> + implements $AppKitModalWalletInfoCopyWith<$Res> { + factory _$$AppKitWalletInfoImplCopyWith(_$AppKitWalletInfoImpl value, + $Res Function(_$AppKitWalletInfoImpl) then) = + __$$AppKitWalletInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Listing listing, bool installed, bool recent}); +} + +/// @nodoc +class __$$AppKitWalletInfoImplCopyWithImpl<$Res> + extends _$AppKitModalWalletInfoCopyWithImpl<$Res, _$AppKitWalletInfoImpl> + implements _$$AppKitWalletInfoImplCopyWith<$Res> { + __$$AppKitWalletInfoImplCopyWithImpl(_$AppKitWalletInfoImpl _value, + $Res Function(_$AppKitWalletInfoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? listing = null, + Object? installed = null, + Object? recent = null, + }) { + return _then(_$AppKitWalletInfoImpl( + listing: null == listing + ? _value.listing + : listing // ignore: cast_nullable_to_non_nullable + as Listing, + installed: null == installed + ? _value.installed + : installed // ignore: cast_nullable_to_non_nullable + as bool, + recent: null == recent + ? _value.recent + : recent // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$AppKitWalletInfoImpl implements _AppKitWalletInfo { + const _$AppKitWalletInfoImpl( + {required this.listing, required this.installed, required this.recent}); + + factory _$AppKitWalletInfoImpl.fromJson(Map json) => + _$$AppKitWalletInfoImplFromJson(json); + + @override + final Listing listing; + @override + final bool installed; + @override + final bool recent; + + @override + String toString() { + return 'AppKitModalWalletInfo(listing: $listing, installed: $installed, recent: $recent)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitWalletInfoImpl && + (identical(other.listing, listing) || other.listing == listing) && + (identical(other.installed, installed) || + other.installed == installed) && + (identical(other.recent, recent) || other.recent == recent)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, listing, installed, recent); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitWalletInfoImplCopyWith<_$AppKitWalletInfoImpl> get copyWith => + __$$AppKitWalletInfoImplCopyWithImpl<_$AppKitWalletInfoImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$AppKitWalletInfoImplToJson( + this, + ); + } +} + +abstract class _AppKitWalletInfo implements AppKitModalWalletInfo { + const factory _AppKitWalletInfo( + {required final Listing listing, + required final bool installed, + required final bool recent}) = _$AppKitWalletInfoImpl; + + factory _AppKitWalletInfo.fromJson(Map json) = + _$AppKitWalletInfoImpl.fromJson; + + @override + Listing get listing; + @override + bool get installed; + @override + bool get recent; + @override + @JsonKey(ignore: true) + _$$AppKitWalletInfoImplCopyWith<_$AppKitWalletInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.g.dart b/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.g.dart new file mode 100644 index 0000000..5ab9367 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_wallet_info.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'appkit_wallet_info.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AppKitWalletInfoImpl _$$AppKitWalletInfoImplFromJson( + Map json) => + _$AppKitWalletInfoImpl( + listing: Listing.fromJson(json['listing']), + installed: json['installed'] as bool, + recent: json['recent'] as bool, + ); + +Map _$$AppKitWalletInfoImplToJson( + _$AppKitWalletInfoImpl instance) => + { + 'listing': instance.listing.toJson(), + 'installed': instance.installed, + 'recent': instance.recent, + }; diff --git a/packages/reown_appkit/lib/modal/models/w3m_chain_info.freezed.dart b/packages/reown_appkit/lib/modal/models/w3m_chain_info.freezed.dart new file mode 100644 index 0000000..6778597 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/w3m_chain_info.freezed.dart @@ -0,0 +1,277 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'w3m_chain_info.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitNetworkInfo { + String get name => throw _privateConstructorUsedError; + String get chainId => throw _privateConstructorUsedError; + String get currency => throw _privateConstructorUsedError; + String get rpcUrl => throw _privateConstructorUsedError; + String get explorerUrl => throw _privateConstructorUsedError; + List get extraRpcUrls => throw _privateConstructorUsedError; + String? get chainIcon => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitNetworkInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitNetworkInfoCopyWith<$Res> { + factory $AppKitNetworkInfoCopyWith( + AppKitNetworkInfo value, $Res Function(AppKitNetworkInfo) then) = + _$AppKitNetworkInfoCopyWithImpl<$Res, AppKitNetworkInfo>; + @useResult + $Res call( + {String name, + String chainId, + String currency, + String rpcUrl, + String explorerUrl, + List extraRpcUrls, + String? chainIcon}); +} + +/// @nodoc +class _$AppKitNetworkInfoCopyWithImpl<$Res, $Val extends AppKitNetworkInfo> + implements $AppKitNetworkInfoCopyWith<$Res> { + _$AppKitNetworkInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? chainId = null, + Object? currency = null, + Object? rpcUrl = null, + Object? explorerUrl = null, + Object? extraRpcUrls = null, + Object? chainIcon = freezed, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + currency: null == currency + ? _value.currency + : currency // ignore: cast_nullable_to_non_nullable + as String, + rpcUrl: null == rpcUrl + ? _value.rpcUrl + : rpcUrl // ignore: cast_nullable_to_non_nullable + as String, + explorerUrl: null == explorerUrl + ? _value.explorerUrl + : explorerUrl // ignore: cast_nullable_to_non_nullable + as String, + extraRpcUrls: null == extraRpcUrls + ? _value.extraRpcUrls + : extraRpcUrls // ignore: cast_nullable_to_non_nullable + as List, + chainIcon: freezed == chainIcon + ? _value.chainIcon + : chainIcon // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitNetworkInfoImplCopyWith<$Res> + implements $AppKitNetworkInfoCopyWith<$Res> { + factory _$$AppKitNetworkInfoImplCopyWith(_$AppKitNetworkInfoImpl value, + $Res Function(_$AppKitNetworkInfoImpl) then) = + __$$AppKitNetworkInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String name, + String chainId, + String currency, + String rpcUrl, + String explorerUrl, + List extraRpcUrls, + String? chainIcon}); +} + +/// @nodoc +class __$$AppKitNetworkInfoImplCopyWithImpl<$Res> + extends _$AppKitNetworkInfoCopyWithImpl<$Res, _$AppKitNetworkInfoImpl> + implements _$$AppKitNetworkInfoImplCopyWith<$Res> { + __$$AppKitNetworkInfoImplCopyWithImpl(_$AppKitNetworkInfoImpl _value, + $Res Function(_$AppKitNetworkInfoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? chainId = null, + Object? currency = null, + Object? rpcUrl = null, + Object? explorerUrl = null, + Object? extraRpcUrls = null, + Object? chainIcon = freezed, + }) { + return _then(_$AppKitNetworkInfoImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + currency: null == currency + ? _value.currency + : currency // ignore: cast_nullable_to_non_nullable + as String, + rpcUrl: null == rpcUrl + ? _value.rpcUrl + : rpcUrl // ignore: cast_nullable_to_non_nullable + as String, + explorerUrl: null == explorerUrl + ? _value.explorerUrl + : explorerUrl // ignore: cast_nullable_to_non_nullable + as String, + extraRpcUrls: null == extraRpcUrls + ? _value._extraRpcUrls + : extraRpcUrls // ignore: cast_nullable_to_non_nullable + as List, + chainIcon: freezed == chainIcon + ? _value.chainIcon + : chainIcon // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$AppKitNetworkInfoImpl implements _AppKitNetworkInfo { + _$AppKitNetworkInfoImpl( + {required this.name, + required this.chainId, + required this.currency, + required this.rpcUrl, + required this.explorerUrl, + final List extraRpcUrls = const [], + this.chainIcon}) + : _extraRpcUrls = extraRpcUrls; + + @override + final String name; + @override + final String chainId; + @override + final String currency; + @override + final String rpcUrl; + @override + final String explorerUrl; + final List _extraRpcUrls; + @override + @JsonKey() + List get extraRpcUrls { + if (_extraRpcUrls is EqualUnmodifiableListView) return _extraRpcUrls; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_extraRpcUrls); + } + + @override + final String? chainIcon; + + @override + String toString() { + return 'AppKitNetworkInfo(name: $name, chainId: $chainId, currency: $currency, rpcUrl: $rpcUrl, explorerUrl: $explorerUrl, extraRpcUrls: $extraRpcUrls, chainIcon: $chainIcon)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitNetworkInfoImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.chainId, chainId) || other.chainId == chainId) && + (identical(other.currency, currency) || + other.currency == currency) && + (identical(other.rpcUrl, rpcUrl) || other.rpcUrl == rpcUrl) && + (identical(other.explorerUrl, explorerUrl) || + other.explorerUrl == explorerUrl) && + const DeepCollectionEquality() + .equals(other._extraRpcUrls, _extraRpcUrls) && + (identical(other.chainIcon, chainIcon) || + other.chainIcon == chainIcon)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + name, + chainId, + currency, + rpcUrl, + explorerUrl, + const DeepCollectionEquality().hash(_extraRpcUrls), + chainIcon); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitNetworkInfoImplCopyWith<_$AppKitNetworkInfoImpl> get copyWith => + __$$AppKitNetworkInfoImplCopyWithImpl<_$AppKitNetworkInfoImpl>( + this, _$identity); +} + +abstract class _AppKitNetworkInfo implements AppKitNetworkInfo { + factory _AppKitNetworkInfo( + {required final String name, + required final String chainId, + required final String currency, + required final String rpcUrl, + required final String explorerUrl, + final List extraRpcUrls, + final String? chainIcon}) = _$AppKitNetworkInfoImpl; + + @override + String get name; + @override + String get chainId; + @override + String get currency; + @override + String get rpcUrl; + @override + String get explorerUrl; + @override + List get extraRpcUrls; + @override + String? get chainIcon; + @override + @JsonKey(ignore: true) + _$$AppKitNetworkInfoImplCopyWith<_$AppKitNetworkInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/models/w3m_wallet_info.freezed.dart b/packages/reown_appkit/lib/modal/models/w3m_wallet_info.freezed.dart new file mode 100644 index 0000000..65a9624 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/w3m_wallet_info.freezed.dart @@ -0,0 +1,189 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'w3m_wallet_info.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +AppKitWalletInfo _$AppKitWalletInfoFromJson(Map json) { + return _AppKitWalletInfo.fromJson(json); +} + +/// @nodoc +mixin _$AppKitWalletInfo { + Listing get listing => throw _privateConstructorUsedError; + bool get installed => throw _privateConstructorUsedError; + bool get recent => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AppKitWalletInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitWalletInfoCopyWith<$Res> { + factory $AppKitWalletInfoCopyWith( + AppKitWalletInfo value, $Res Function(AppKitWalletInfo) then) = + _$AppKitWalletInfoCopyWithImpl<$Res, AppKitWalletInfo>; + @useResult + $Res call({Listing listing, bool installed, bool recent}); +} + +/// @nodoc +class _$AppKitWalletInfoCopyWithImpl<$Res, $Val extends AppKitWalletInfo> + implements $AppKitWalletInfoCopyWith<$Res> { + _$AppKitWalletInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? listing = null, + Object? installed = null, + Object? recent = null, + }) { + return _then(_value.copyWith( + listing: null == listing + ? _value.listing + : listing // ignore: cast_nullable_to_non_nullable + as Listing, + installed: null == installed + ? _value.installed + : installed // ignore: cast_nullable_to_non_nullable + as bool, + recent: null == recent + ? _value.recent + : recent // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitWalletInfoImplCopyWith<$Res> + implements $AppKitWalletInfoCopyWith<$Res> { + factory _$$AppKitWalletInfoImplCopyWith(_$AppKitWalletInfoImpl value, + $Res Function(_$AppKitWalletInfoImpl) then) = + __$$AppKitWalletInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Listing listing, bool installed, bool recent}); +} + +/// @nodoc +class __$$AppKitWalletInfoImplCopyWithImpl<$Res> + extends _$AppKitWalletInfoCopyWithImpl<$Res, _$AppKitWalletInfoImpl> + implements _$$AppKitWalletInfoImplCopyWith<$Res> { + __$$AppKitWalletInfoImplCopyWithImpl(_$AppKitWalletInfoImpl _value, + $Res Function(_$AppKitWalletInfoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? listing = null, + Object? installed = null, + Object? recent = null, + }) { + return _then(_$AppKitWalletInfoImpl( + listing: null == listing + ? _value.listing + : listing // ignore: cast_nullable_to_non_nullable + as Listing, + installed: null == installed + ? _value.installed + : installed // ignore: cast_nullable_to_non_nullable + as bool, + recent: null == recent + ? _value.recent + : recent // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$AppKitWalletInfoImpl implements _AppKitWalletInfo { + const _$AppKitWalletInfoImpl( + {required this.listing, required this.installed, required this.recent}); + + factory _$AppKitWalletInfoImpl.fromJson(Map json) => + _$$AppKitWalletInfoImplFromJson(json); + + @override + final Listing listing; + @override + final bool installed; + @override + final bool recent; + + @override + String toString() { + return 'AppKitWalletInfo(listing: $listing, installed: $installed, recent: $recent)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitWalletInfoImpl && + (identical(other.listing, listing) || other.listing == listing) && + (identical(other.installed, installed) || + other.installed == installed) && + (identical(other.recent, recent) || other.recent == recent)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, listing, installed, recent); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitWalletInfoImplCopyWith<_$AppKitWalletInfoImpl> get copyWith => + __$$AppKitWalletInfoImplCopyWithImpl<_$AppKitWalletInfoImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$AppKitWalletInfoImplToJson( + this, + ); + } +} + +abstract class _AppKitWalletInfo implements AppKitWalletInfo { + const factory _AppKitWalletInfo( + {required final Listing listing, + required final bool installed, + required final bool recent}) = _$AppKitWalletInfoImpl; + + factory _AppKitWalletInfo.fromJson(Map json) = + _$AppKitWalletInfoImpl.fromJson; + + @override + Listing get listing; + @override + bool get installed; + @override + bool get recent; + @override + @JsonKey(ignore: true) + _$$AppKitWalletInfoImplCopyWith<_$AppKitWalletInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/models/w3m_wallet_info.g.dart b/packages/reown_appkit/lib/modal/models/w3m_wallet_info.g.dart new file mode 100644 index 0000000..c313147 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/w3m_wallet_info.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'w3m_wallet_info.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AppKitWalletInfoImpl _$$AppKitWalletInfoImplFromJson( + Map json) => + _$AppKitWalletInfoImpl( + listing: Listing.fromJson(json['listing']), + installed: json['installed'] as bool, + recent: json['recent'] as bool, + ); + +Map _$$AppKitWalletInfoImplToJson( + _$AppKitWalletInfoImpl instance) => + { + 'listing': instance.listing.toJson(), + 'installed': instance.installed, + 'recent': instance.recent, + }; diff --git a/packages/reown_appkit/lib/modal/pages/about_networks.dart b/packages/reown_appkit/lib/modal/pages/about_networks.dart new file mode 100644 index 0000000..9205d85 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/about_networks.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; +import 'package:reown_appkit/modal/widgets/help/help_section.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; + +class AboutNetworks extends StatelessWidget { + const AboutNetworks() : super(key: KeyConstants.helpPageKey); + + @override + Widget build(BuildContext context) { + return ModalNavbar( + title: 'What is a network?', + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Column( + children: [ + HelpSection( + title: 'The system’s nuts and bolts', + description: + 'A network is what brings the blockchain to life, as this technical infrastructure allows apps to access the ledger and smart contract services.', + images: [ + 'lib/modal/assets/help/network.svg', + 'lib/modal/assets/help/layers.svg', + 'lib/modal/assets/help/system.svg', + ], + ), + HelpSection( + title: 'Designed for different uses', + description: + 'Each network is designed differently, and may therefore suit certain apps and experiences.', + images: [ + 'lib/modal/assets/help/noun.svg', + 'lib/modal/assets/help/defi.svg', + 'lib/modal/assets/help/dao.svg', + ], + ), + ], + ), + const SizedBox(height: 8), + SimpleIconButton( + onTap: () => launchUrlString( + UrlConstants.learnMoreUrl, + mode: LaunchMode.externalApplication, + ), + rightIcon: 'lib/modal/assets/icons/arrow_top_right.svg', + title: 'Learn more', + size: BaseButtonSize.small, + iconSize: 12.0, + fontSize: 14.0, + ), + const SizedBox(height: 8.0), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/about_wallets.dart b/packages/reown_appkit/lib/modal/pages/about_wallets.dart new file mode 100644 index 0000000..32d627c --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/about_wallets.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/pages/get_wallet_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; +import 'package:reown_appkit/modal/widgets/help/help_section.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; + +class AboutWallets extends StatelessWidget { + const AboutWallets() : super(key: KeyConstants.helpPageKey); + + @override + Widget build(BuildContext context) { + return ModalNavbar( + title: 'What is a wallet?', + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Column( + children: [ + HelpSection( + title: 'One login for all of web3', + description: + 'Log in to any app by connecting your wallet. Say goodbye to countless passwords!', + images: [ + 'lib/modal/assets/help/key.svg', + 'lib/modal/assets/help/user.svg', + 'lib/modal/assets/help/lock.svg', + ], + ), + HelpSection( + title: 'A home for your digital assets', + description: + 'A wallet lets you store, send, and receive digital assets like cryptocurrencies and NFTs.', + images: [ + 'lib/modal/assets/help/chart.svg', + 'lib/modal/assets/help/painting.svg', + 'lib/modal/assets/help/eth.svg', + ], + ), + HelpSection( + title: 'Your gateway to a new web', + description: + 'With your wallet, you can explore and interact with DeFi, NFTs, DAOS, and much more.', + images: [ + 'lib/modal/assets/help/compass.svg', + 'lib/modal/assets/help/noun.svg', + 'lib/modal/assets/help/dao.svg', + ], + ), + ], + ), + const SizedBox(height: 8), + SimpleIconButton( + onTap: () { + widgetStack.instance.push( + const GetWalletPage(), + event: ClickGetWalletEvent(), + ); + }, + leftIcon: 'lib/modal/assets/icons/wallet.svg', + title: 'Get a wallet', + ), + const SizedBox(height: 8.0), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/account_page.dart b/packages/reown_appkit/lib/modal/pages/account_page.dart new file mode 100644 index 0000000..a97538f --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/account_page.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/pages/edit_email_page.dart'; +import 'package:reown_appkit/modal/pages/upgrade_wallet_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/widgets/circular_loader.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/avatars/account_orb.dart'; +import 'package:reown_appkit/modal/widgets/buttons/address_copy_button.dart'; +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/account_list_item.dart'; +import 'package:reown_appkit/modal/widgets/text/appkit_balance.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AccountPage extends StatefulWidget { + const AccountPage() : super(key: KeyConstants.accountPage); + + @override + State createState() => _AccountPageState(); +} + +class _AccountPageState extends State with WidgetsBindingObserver { + IAppKitModal? _service; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance.addPostFrameCallback((_) { + _service = ModalProvider.of(context).service; + _service?.addListener(_rebuild); + _rebuild(); + }); + } + + void _rebuild() => setState(() {}); + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _rebuild(); + } + } + + @override + void dispose() { + _service?.removeListener(_rebuild); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_service == null) { + return ContentLoading(viewHeight: 400.0); + } + + return ModalNavbar( + title: '', + safeAreaLeft: true, + safeAreaRight: true, + safeAreaBottom: true, + divider: false, + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: kPadding12), + child: _DefaultAccountView( + service: _service!, + ), + ), + ); + } +} + +class _DefaultAccountView extends StatelessWidget { + const _DefaultAccountView({required IAppKitModal service}) + : _service = service; + final IAppKitModal _service; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + final isEmailLogin = _service.session?.sessionService.isMagic ?? false; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + const Orb(size: 72.0), + const SizedBox.square(dimension: kPadding12), + const AddressCopyButton(), + const BalanceText(), + Visibility( + visible: _service.selectedChain?.explorerUrl != null, + child: Padding( + padding: const EdgeInsets.only(top: kPadding12), + child: SimpleIconButton( + onTap: () => _service.launchBlockExplorer(), + leftIcon: 'lib/modal/assets/icons/compass.svg', + rightIcon: 'lib/modal/assets/icons/arrow_top_right.svg', + title: 'Block Explorer', + backgroundColor: themeColors.background125, + foregroundColor: themeColors.foreground150, + overlayColor: MaterialStateProperty.all( + themeColors.background200, + ), + ), + ), + ), + ], + ), + const SizedBox.square(dimension: kPadding12), + Visibility( + visible: isEmailLogin, + child: Column( + children: [ + const SizedBox.square(dimension: kPadding8), + AccountListItem( + padding: const EdgeInsets.symmetric( + horizontal: kPadding8, + vertical: kPadding12, + ), + iconWidget: Padding( + padding: const EdgeInsets.all(4.0), + child: RoundedIcon( + borderRadius: radiuses.isSquare() + ? 0.0 + : radiuses.isCircular() + ? 40.0 + : 8.0, + size: 40.0, + assetPath: 'lib/modal/assets/icons/regular/wallet.svg', + assetColor: themeColors.accent100, + circleColor: themeColors.accenGlass010, + borderColor: themeColors.accenGlass010, + ), + ), + title: 'Upgrade your wallet', + subtitle: 'Transition to a self-custodial wallet', + hightlighted: true, + flexible: true, + titleStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + onTap: () => widgetStack.instance.push(UpgradeWalletPage()), + ), + ], + ), + ), + Visibility( + visible: isEmailLogin, + child: Column( + children: [ + const SizedBox.square(dimension: kPadding8), + AccountListItem( + iconPath: 'lib/modal/assets/icons/mail.svg', + iconColor: themeColors.foreground100, + title: _service.session?.email ?? '', + titleStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + onTap: () { + widgetStack.instance.push(EditEmailPage()); + }, + ), + ], + ), + ), + const SizedBox.square(dimension: kPadding8), + _SelectNetworkButton(), + const SizedBox.square(dimension: kPadding8), + AccountListItem( + iconPath: 'lib/modal/assets/icons/disconnect.svg', + trailing: _service.status.isLoading + ? Row( + children: [ + CircularLoader(size: 18.0, strokeWidth: 2.0), + SizedBox.square(dimension: kPadding12), + ], + ) + : const SizedBox.shrink(), + title: 'Disconnect', + titleStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground200, + ), + onTap: _service.status.isLoading + ? null + : () => _service.closeModal(disconnectSession: true), + ), + ], + ); + } +} + +class _SelectNetworkButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + final service = ModalProvider.of(context).service; + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final chainId = service.selectedChain?.chainId ?? ''; + final imageId = AppKitModalNetworks.getNetworkIconId(chainId); + final tokenImage = explorerService.instance.getAssetImageUrl(imageId); + final radiuses = AppKitModalTheme.radiusesOf(context); + return AccountListItem( + iconWidget: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: imageId.isEmpty + ? RoundedIcon( + assetPath: 'lib/modal/assets/icons/network.svg', + assetColor: themeColors.inverse100, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ) + : RoundedIcon( + borderRadius: radiuses.isSquare() ? 0.0 : null, + imageUrl: tokenImage, + assetColor: themeColors.background100, + ), + ), + title: service.selectedChain?.name ?? 'Unsupported network', + titleStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + onTap: () { + widgetStack.instance.push( + AppKitModalSelectNetworkPage(), + event: ClickNetworksEvent(), + ); + }, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart b/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart new file mode 100644 index 0000000..52d01d9 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; + +class ApproveTransactionPage extends StatefulWidget { + const ApproveTransactionPage() + : super(key: KeyConstants.approveTransactionPage); + + @override + State createState() => _ApproveTransactionPageState(); +} + +class _ApproveTransactionPageState extends State { + @override + Widget build(BuildContext context) { + magicService.instance.controller.runJavaScript( + 'document.body.style.zoom = "1%"', + ); + return ModalNavbar( + title: 'Approve Transaction', + noClose: true, + safeAreaLeft: true, + safeAreaRight: true, + body: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: ResponsiveData.maxHeightOf(context), + ), + child: magicService.instance.webview, + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/approve_siwe.dart b/packages/reown_appkit/lib/modal/pages/approve_siwe.dart new file mode 100644 index 0000000..42485ce --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/approve_siwe.dart @@ -0,0 +1,288 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/services/siwe_service/siwe_service_singleton.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/avatars/account_avatar.dart'; +import 'package:reown_appkit/modal/widgets/buttons/primary_button.dart'; +import 'package:reown_appkit/modal/widgets/buttons/secondary_button.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/avatars/wallet_avatar.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class ApproveSIWEPage extends StatefulWidget { + final Function(AppKitModalSession session) onSiweFinish; + const ApproveSIWEPage({ + required this.onSiweFinish, + }) : super(key: KeyConstants.approveSiwePageKey); + + @override + State createState() => _ApproveSIWEPageState(); +} + +class _ApproveSIWEPageState extends State { + IAppKitModal? _appKitModal; + double _position = 0.0; + static const _duration = Duration(milliseconds: 1500); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _position = (MediaQuery.of(context).size.width / 2) + 8.0; + _appKitModal = ModalProvider.of(context).service; + Future.delayed(Duration(milliseconds: 200), () { + _animate(); + }); + }); + }); + } + + void _animate() { + if (!mounted) return; + setState(() { + if (_position == (MediaQuery.of(context).size.width / 2) - 12.0) { + _position = (MediaQuery.of(context).size.width / 2) + 8.0; + } else { + _position = (MediaQuery.of(context).size.width / 2) - 12.0; + } + }); + Future.delayed(_duration, _animate); + } + + bool _waitingSign = false; + void _signIn() async { + setState(() => _waitingSign = true); + try { + final address = _appKitModal!.session!.address!; + String chainId = _appKitModal!.selectedChain?.chainId ?? '1'; + analyticsService.instance.sendEvent(ClickSignSiweMessage( + network: chainId, + )); + chainId = '${CoreConstants.namespace}:$chainId'; + // + final message = await siweService.instance!.createMessage( + chainId: chainId, + address: address, + ); + // + _appKitModal!.launchConnectedWallet(); + final signature = await siweService.instance!.signMessageRequest( + message, + session: _appKitModal!.session!, + ); + // + final clientId = await _appKitModal!.appKit!.core.crypto.getClientId(); + await siweService.instance!.verifyMessage( + message: message, + signature: signature, + clientId: clientId, + ); + // + final siweSession = await siweService.instance!.getSession(); + final newSession = + _appKitModal!.session!.copyWith(siweSession: siweSession); + // + widget.onSiweFinish(newSession); + // + } on JsonRpcError catch (e) { + _handleError(e.message); + } on AppKitModalException catch (e) { + _handleError(e.message); + } catch (e) { + _handleError(e.toString()); + } + } + + void _handleError(String? error) { + debugPrint('[$runtimeType] _handleError $error'); + String chainId = _appKitModal!.selectedChain?.chainId ?? '1'; + analyticsService.instance.sendEvent(SiweAuthError(network: chainId)); + toastService.instance.show(ToastMessage( + type: ToastType.error, + text: error ?? 'Something went wrong.', + )); + if (!mounted) return; + setState(() => _waitingSign = false); + } + + void _cancelSIWE() async { + _appKitModal?.closeModal(disconnectSession: true); + } + + @override + Widget build(BuildContext context) { + if (_appKitModal == null) return ContentLoading(); + + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + String peerIcon; + try { + peerIcon = _appKitModal?.session?.peer?.metadata.icons.first ?? ''; + } catch (e) { + peerIcon = ''; + } + String selfIcon; + try { + selfIcon = _appKitModal?.session?.self?.metadata.icons.first ?? ''; + } catch (e) { + selfIcon = ''; + } + return ModalNavbar( + title: 'Sign In', + noClose: true, + safeAreaLeft: true, + safeAreaRight: true, + safeAreaBottom: true, + onBack: _cancelSIWE, + body: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox.square(dimension: kPadding12), + const SizedBox.square(dimension: kPadding8), + SizedBox( + height: 76.0, + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + AnimatedPositioned( + duration: _duration, + curve: Curves.easeInOut, + top: 0, + left: _position, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + borderRadius: + _appKitModal!.session!.sessionService.isMagic || + peerIcon.isEmpty + ? BorderRadius.circular(60.0) + : BorderRadius.circular(radiuses.radiusM), + color: themeColors.background150, + ), + child: _appKitModal!.session!.sessionService.isMagic + ? AccountAvatar( + service: _appKitModal!, + size: 60.0, + ) + : SizedBox( + width: 60.0, + height: 60.0, + child: peerIcon.isEmpty + ? RoundedIcon( + borderRadius: + radiuses.isSquare() ? 0.0 : 60.0, + size: 60.0, + padding: 12.0, + assetPath: + 'lib/modal/assets/icons/regular/wallet.svg', + assetColor: themeColors.accent100, + circleColor: themeColors.accenGlass010, + borderColor: themeColors.accenGlass010, + ) + : ListAvatar( + imageUrl: peerIcon, + borderRadius: radiuses.radiusS, + ), + ), + ), + ), + AnimatedPositioned( + duration: _duration, + curve: Curves.easeInOut, + top: 0, + right: _position, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(36.0), + color: themeColors.background150, + ), + child: selfIcon.isEmpty + ? AccountAvatar( + service: _appKitModal!, + size: 60.0, + ) + : SizedBox( + width: 60.0, + height: 60.0, + child: ListAvatar( + imageUrl: selfIcon, + borderRadius: 30.0, + ), + ), + ), + ), + ], + ), + ), + const SizedBox.square(dimension: kPadding12), + const SizedBox.square(dimension: kPadding8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 60.0), + child: Text( + '${_appKitModal!.appKit!.metadata.name} needs to connect to your wallet', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph400.copyWith( + color: themeColors.foreground100, + ), + ), + ), + const SizedBox.square(dimension: kPadding12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: Text( + 'Sign this message to prove you own this wallet and proceed. Canceling will disconnect you.', + textAlign: TextAlign.center, + style: themeData.textStyles.small400.copyWith( + color: themeColors.foreground200, + ), + ), + ), + const SizedBox.square(dimension: kPadding12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: kPadding12), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + const SizedBox.square(dimension: 4.0), + Expanded( + child: SecondaryButton( + title: 'Cancel', + onTap: _cancelSIWE, + ), + ), + const SizedBox.square(dimension: kPadding8), + Expanded( + child: PrimaryButton( + title: 'Sign', + onTap: _signIn, + loading: _waitingSign, + ), + ), + const SizedBox.square(dimension: 4.0), + ], + ), + ), + const SizedBox.square(dimension: kPadding8), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart b/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart new file mode 100644 index 0000000..493883c --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; + +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/verify_otp_view.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; + +class ConfirmEmailPage extends StatefulWidget { + const ConfirmEmailPage() : super(key: KeyConstants.confirmEmailPage); + + @override + State createState() => _ConfirmEmailPageState(); +} + +class _ConfirmEmailPageState extends State { + @override + void initState() { + super.initState(); + magicService.instance.onMagicError.subscribe(_onMagicErrorEvent); + } + + @override + void dispose() { + magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); + magicService.instance.step.value = EmailLoginStep.idle; + super.dispose(); + } + + void _onMagicErrorEvent(MagicErrorEvent? event) { + toastService.instance.show(ToastMessage( + type: ToastType.error, + text: event?.error ?? 'Something went wrong.', + )); + if (event is ConnectEmailErrorEvent) { + _goBack(); + } else { + setState(() {}); + } + } + + void _goBack() { + magicService.instance.step.value = EmailLoginStep.idle; + magicService.instance.setEmail(''); + FocusManager.instance.primaryFocus?.unfocus(); + widgetStack.instance.pop(); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: magicService.instance.step, + builder: (context, action, _) { + final title = (action == EmailLoginStep.verifyDevice) + ? 'Register device' + : 'Confirm Email'; + return ModalNavbar( + title: title, + safeAreaLeft: true, + safeAreaRight: true, + onBack: _goBack, + body: Builder( + builder: (_) { + if (action == EmailLoginStep.verifyDevice) { + return _VerifyDeviceView(); + } + if (action == EmailLoginStep.verifyOtp) { + return VerifyOtpView( + currentEmail: magicService.instance.email.value, + resendEmail: magicService.instance.connectEmail, + verifyOtp: magicService.instance.connectOtp, + ); + } + return ContentLoading(viewHeight: 200.0); + }, + ), + ); + }, + ); + } +} + +class _VerifyDeviceView extends StatefulWidget { + @override + State<_VerifyDeviceView> createState() => __VerifyDeviceViewState(); +} + +class __VerifyDeviceViewState extends State<_VerifyDeviceView> { + late DateTime _resendEnabledAt; + + @override + void initState() { + super.initState(); + _resendEnabledAt = DateTime.now().add(Duration(seconds: 30)); + } + + void _resendEmail() async { + final diff = DateTime.now().difference(_resendEnabledAt).inSeconds; + if (diff < 0) { + toastService.instance.show(ToastMessage( + type: ToastType.error, + text: 'Try again after ${diff.abs()} seconds', + )); + } else { + final email = magicService.instance.email.value; + await magicService.instance.connectEmail(value: email); + _resendEnabledAt = DateTime.now().add(Duration(seconds: 30)); + toastService.instance.show(ToastMessage( + type: ToastType.success, + text: 'Link email resent', + )); + } + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final textStyles = AppKitModalTheme.getDataOf(context).textStyles; + final radiuses = AppKitModalTheme.radiusesOf(context); + final borderRadiusIcon = radiuses.isSquare() ? 0.0 : 20.0; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: kPadding8, + horizontal: kPadding12, + ), + child: Column( + children: [ + const SizedBox.square(dimension: kPadding16), + RoundedIcon( + assetPath: 'lib/modal/assets/icons/verif.svg', + assetColor: themeColors.accent100, + circleColor: themeColors.accent100.withOpacity(0.15), + borderColor: Colors.transparent, + padding: 22.0, + size: 70.0, + borderRadius: borderRadiusIcon, + ), + const SizedBox.square(dimension: kPadding16), + Text( + 'Approve the login link we sent to ', + textAlign: TextAlign.center, + style: textStyles.paragraph400.copyWith( + color: themeColors.foreground100, + ), + ), + Text( + magicService.instance.email.value, + textAlign: TextAlign.center, + style: textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: kPadding12), + Text( + 'The link expires in 20 minutes', + style: textStyles.small400.copyWith( + color: themeColors.foreground200, + ), + ), + const SizedBox.square(dimension: kPadding16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Didn\'t receive it?', + style: textStyles.small400.copyWith( + color: themeColors.foreground200, + ), + ), + TextButton( + onPressed: () { + _resendEmail(); + }, + child: Text( + 'Resend email', + style: textStyles.small600.copyWith( + color: themeColors.accent100, + ), + ), + style: ButtonStyle( + overlayColor: MaterialStateProperty.all( + themeColors.accenGlass010, + ), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(horizontal: 8.0), + ), + visualDensity: VisualDensity.compact, + ), + ), + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/connect_wallet_page.dart b/packages/reown_appkit/lib/modal/pages/connect_wallet_page.dart new file mode 100644 index 0000000..9ce28f4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/connect_wallet_page.dart @@ -0,0 +1,348 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/services/siwe_service/siwe_service_singleton.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/segmented_control.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/avatars/wallet_avatar.dart'; +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/download_wallet_item.dart'; +import 'package:reown_appkit/modal/widgets/avatars/loading_border.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class ConnectWalletPage extends StatefulWidget { + const ConnectWalletPage() : super(key: KeyConstants.connectWalletPageKey); + + @override + State createState() => _ConnectWalletPageState(); +} + +class _ConnectWalletPageState extends State + with WidgetsBindingObserver { + IAppKitModal? _service; + SegmentOption _selectedSegment = SegmentOption.mobile; + ModalError? errorEvent; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _service = ModalProvider.of(context).service; + _service?.onModalError.subscribe(_errorListener); + }); + Future.delayed(const Duration(milliseconds: 300), () { + _service?.connectSelectedWallet(); + }); + }); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + final isOpen = _service?.isOpen ?? false; + final isConnected = _service?.isConnected ?? false; + if (isOpen && isConnected && !siweService.instance!.enabled) { + Future.delayed(Duration(seconds: 1), () { + if (!mounted) return; + _service?.closeModal(); + }); + } + } + } + + void _errorListener(ModalError? event) => setState(() => errorEvent = event); + + @override + void dispose() { + _service?.onModalError.unsubscribe(_errorListener); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_service == null) { + return ContentLoading(); + } + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final isPortrait = ResponsiveData.isPortrait(context); + final maxWidth = isPortrait + ? ResponsiveData.maxWidthOf(context) + : ResponsiveData.maxHeightOf(context) - + kNavbarHeight - + (kPadding16 * 2); + // + final walletRedirect = explorerService.instance.getWalletRedirect( + _service!.selectedWallet, + ); + final webOnlyWallet = walletRedirect?.webOnly == true; + final mobileOnlyWallet = walletRedirect?.mobileOnly == true; + // + final selectedWallet = _service!.selectedWallet; + final walletName = selectedWallet?.listing.name ?? 'Wallet'; + final imageId = selectedWallet?.listing.imageId ?? ''; + final imageUrl = explorerService.instance.getWalletImageUrl(imageId); + // + return ModalNavbar( + title: walletName, + onBack: () { + _service?.selectWallet(null); + widgetStack.instance.pop(); + }, + body: SingleChildScrollView( + scrollDirection: isPortrait ? Axis.vertical : Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: kPadding16), + child: Flex( + direction: isPortrait ? Axis.vertical : Axis.horizontal, + children: [ + Container( + constraints: BoxConstraints(maxWidth: maxWidth), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox.square(dimension: 12.0), + Visibility( + visible: !webOnlyWallet && !mobileOnlyWallet, + child: SegmentedControl( + onChange: (option) => setState(() { + _selectedSegment = option; + }), + ), + ), + const SizedBox.square(dimension: 20.0), + LoadingBorder( + // animate: walletInstalled && !errorConnection, + animate: errorEvent == null, + borderRadius: themeData.radiuses.isSquare() + ? 0 + : themeData.radiuses.radiusM + 4.0, + child: _WalletAvatar( + imageUrl: imageUrl, + // errorConnection: errorConnection, + errorConnection: errorEvent is ErrorOpeningWallet || + errorEvent is UserRejectedConnection, + themeColors: themeColors, + ), + ), + const SizedBox.square(dimension: 20.0), + errorEvent is ErrorOpeningWallet || + errorEvent is UserRejectedConnection + ? Text( + errorEvent is ErrorOpeningWallet + ? 'Error opening wallet' + : 'Connection declined', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.error100, + ), + ) + : errorEvent is WalletNotInstalled && + _selectedSegment == SegmentOption.mobile + ? Text( + 'App not installed', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ) + : Text( + 'Continue in $walletName', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: 8.0), + errorEvent is ErrorOpeningWallet || + errorEvent is UserRejectedConnection + ? Text( + errorEvent is ErrorOpeningWallet + ? 'Unable to connect with $walletName' + : 'Connection can be declined by the user or if a previous request is still active', + textAlign: TextAlign.center, + style: themeData.textStyles.small500.copyWith( + color: themeColors.foreground200, + ), + ) + : errorEvent is WalletNotInstalled && + _selectedSegment == SegmentOption.mobile + ? SizedBox.shrink() + : Text( + webOnlyWallet || + _selectedSegment == SegmentOption.browser + ? 'Open and continue in a new browser tab' + : 'Accept connection request in the wallet', + textAlign: TextAlign.center, + style: themeData.textStyles.small500.copyWith( + color: themeColors.foreground200, + ), + ), + const SizedBox.square(dimension: kPadding16), + Visibility( + visible: isPortrait && + _selectedSegment != SegmentOption.browser && + errorEvent == null, + child: SimpleIconButton( + onTap: () => _service!.connectSelectedWallet(), + leftIcon: 'lib/modal/assets/icons/refresh_back.svg', + title: 'Try again', + backgroundColor: Colors.transparent, + foregroundColor: themeColors.accent100, + ), + ), + Visibility( + visible: isPortrait && + (webOnlyWallet || + _selectedSegment == SegmentOption.browser), + child: SimpleIconButton( + onTap: () => _service!.connectSelectedWallet( + inBrowser: _selectedSegment == SegmentOption.browser, + ), + rightIcon: 'lib/modal/assets/icons/arrow_top_right.svg', + title: 'Open', + backgroundColor: Colors.transparent, + foregroundColor: themeColors.accent100, + ), + ), + ], + ), + ), + if (!isPortrait) const SizedBox.square(dimension: kPadding16), + Container( + constraints: BoxConstraints(maxWidth: maxWidth), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (isPortrait) const SizedBox.square(dimension: kPadding12), + Visibility( + visible: !isPortrait && + _selectedSegment != SegmentOption.browser && + errorEvent == null, + child: SimpleIconButton( + onTap: () => _service!.connectSelectedWallet(), + leftIcon: 'lib/modal/assets/icons/refresh_back.svg', + title: 'Try again', + backgroundColor: Colors.transparent, + foregroundColor: themeColors.accent100, + ), + ), + Visibility( + visible: !isPortrait && + (webOnlyWallet || + _selectedSegment == SegmentOption.browser), + child: SimpleIconButton( + onTap: () => _service!.connectSelectedWallet( + inBrowser: _selectedSegment == SegmentOption.browser, + ), + leftIcon: 'lib/modal/assets/icons/arrow_top_right.svg', + title: 'Open', + backgroundColor: Colors.transparent, + foregroundColor: themeColors.accent100, + ), + ), + if (!isPortrait) const SizedBox.square(dimension: kPadding8), + SimpleIconButton( + onTap: () => _copyToClipboard(context), + leftIcon: 'lib/modal/assets/icons/copy_14.svg', + iconSize: 13.0, + title: 'Copy link', + fontSize: 14.0, + backgroundColor: Colors.transparent, + foregroundColor: themeColors.foreground200, + overlayColor: MaterialStateProperty.all( + themeColors.background200, + ), + withBorder: false, + ), + if (!isPortrait) const SizedBox.square(dimension: kPadding8), + if (errorEvent is WalletNotInstalled && + _selectedSegment == SegmentOption.mobile) + Column( + children: [ + if (isPortrait) + const SizedBox.square(dimension: kPadding16), + if (selectedWallet != null) + DownloadWalletItem( + walletInfo: selectedWallet, + webOnly: webOnlyWallet, + ), + const SizedBox.square(dimension: kPadding16), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } + + Future _copyToClipboard(BuildContext context) async { + final service = ModalProvider.of(context).service; + await Clipboard.setData(ClipboardData(text: service.wcUri!)); + toastService.instance.show( + ToastMessage(type: ToastType.success, text: 'Link copied'), + ); + } +} + +class _WalletAvatar extends StatelessWidget { + const _WalletAvatar({ + required this.imageUrl, + required this.errorConnection, + required this.themeColors, + }); + + final String imageUrl; + final bool errorConnection; + final AppKitModalColors themeColors; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + ListAvatar(imageUrl: imageUrl), + Positioned( + bottom: 0, + right: 0, + child: Visibility( + visible: errorConnection, + child: Container( + decoration: BoxDecoration( + color: themeColors.background125, + borderRadius: BorderRadius.all(Radius.circular(30.0)), + ), + padding: const EdgeInsets.all(1.0), + clipBehavior: Clip.antiAlias, + child: RoundedIcon( + assetPath: 'lib/modal/assets/icons/close.svg', + assetColor: themeColors.error100, + circleColor: themeColors.error100.withOpacity(0.2), + borderColor: themeColors.background125, + padding: 4.0, + size: 24.0, + ), + ), + ), + ), + ], + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/connet_network_page.dart b/packages/reown_appkit/lib/modal/pages/connet_network_page.dart new file mode 100644 index 0000000..27e01bb --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/connet_network_page.dart @@ -0,0 +1,226 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/services/siwe_service/siwe_service_singleton.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/avatars/wallet_avatar.dart'; +import 'package:reown_appkit/modal/widgets/avatars/loading_border.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class ConnectNetworkPage extends StatefulWidget { + final AppKitModalNetworkInfo chainInfo; + const ConnectNetworkPage({ + required this.chainInfo, + }) : super(key: KeyConstants.connecNetworkPageKey); + + @override + State createState() => _ConnectNetworkPageState(); +} + +class _ConnectNetworkPageState extends State + with WidgetsBindingObserver { + IAppKitModal? _service; + ModalError? errorEvent; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance.addPostFrameCallback((_) { + _service = ModalProvider.of(context).service; + _service?.onModalError.subscribe(_errorListener); + setState(() {}); + Future.delayed(const Duration(milliseconds: 300), () => _connect()); + }); + } + + void _connect() async { + errorEvent = null; + _service!.launchConnectedWallet(); + try { + await _service!.requestSwitchToChain(widget.chainInfo); + final chainId = widget.chainInfo.chainId; + final chainInfo = AppKitModalNetworks.getNetworkById( + CoreConstants.namespace, + chainId, + ); + if (chainInfo != null) { + Future.delayed(const Duration(milliseconds: 300), () { + if (!siweService.instance!.enabled) { + widgetStack.instance.pop(); + } + }); + } + } catch (e) { + setState(() {}); + } + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + if (_service?.session?.sessionService.isCoinbase == true) { + if (_service?.selectedChain?.chainId == widget.chainInfo.chainId) { + if (!siweService.instance!.enabled) { + widgetStack.instance.pop(); + } + } + } + } + } + + void _errorListener(ModalError? event) => setState(() => errorEvent = event); + + @override + void dispose() { + _service?.onModalError.unsubscribe(_errorListener); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_service == null) { + return ContentLoading(); + } + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final isPortrait = ResponsiveData.isPortrait(context); + final maxWidth = isPortrait + ? ResponsiveData.maxWidthOf(context) + : ResponsiveData.maxHeightOf(context) - + kNavbarHeight - + (kPadding16 * 2); + // + final chainId = widget.chainInfo.chainId; + final imageId = AppKitModalNetworks.getNetworkIconId(chainId); + final imageUrl = explorerService.instance.getAssetImageUrl(imageId); + // + return ModalNavbar( + title: widget.chainInfo.name, + noClose: true, + body: SingleChildScrollView( + scrollDirection: isPortrait ? Axis.vertical : Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: kPadding16), + child: Flex( + direction: isPortrait ? Axis.vertical : Axis.horizontal, + children: [ + Container( + constraints: BoxConstraints(maxWidth: maxWidth), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox.square(dimension: 20.0), + LoadingBorder( + animate: errorEvent == null, + isNetwork: true, + borderRadius: themeData.radiuses.isSquare() + ? 0 + : themeData.radiuses.radiusM + 4.0, + // themeData.radiuses.radiusM + 4.0 + child: _WalletAvatar( + imageUrl: imageUrl, + errorConnection: errorEvent is UserRejectedConnection, + themeColors: themeColors, + ), + ), + const SizedBox.square(dimension: 20.0), + errorEvent != null + ? Text( + 'Switch declined', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.error100, + ), + ) + : Text( + 'Continue in ${_service?.session?.peer?.metadata.name ?? 'wallet'}', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: 8.0), + errorEvent != null + ? Text( + 'Switch can be declined by the user or if a previous request is still active', + textAlign: TextAlign.center, + style: themeData.textStyles.small500.copyWith( + color: themeColors.foreground200, + ), + ) + : Text( + 'Accept switch request in your wallet', + textAlign: TextAlign.center, + style: themeData.textStyles.small500.copyWith( + color: themeColors.foreground200, + ), + ), + const SizedBox.square(dimension: kPadding16), + ], + ), + ), + ], + ), + ), + ); + } +} + +class _WalletAvatar extends StatelessWidget { + const _WalletAvatar({ + required this.imageUrl, + required this.errorConnection, + required this.themeColors, + }); + + final String imageUrl; + final bool errorConnection; + final AppKitModalColors themeColors; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + ListAvatar( + imageUrl: imageUrl, + isNetwork: true, + ), + Positioned( + bottom: 0, + right: 0, + child: Visibility( + visible: errorConnection, + child: Container( + decoration: BoxDecoration( + color: themeColors.background125, + borderRadius: BorderRadius.all(Radius.circular(30.0)), + ), + padding: const EdgeInsets.all(1.0), + clipBehavior: Clip.antiAlias, + child: RoundedIcon( + assetPath: 'lib/modal/assets/icons/close.svg', + assetColor: themeColors.error100, + circleColor: themeColors.error100.withOpacity(0.2), + borderColor: themeColors.background125, + padding: 4.0, + size: 24.0, + ), + ), + ), + ), + ], + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/edit_email_page.dart b/packages/reown_appkit/lib/modal/pages/edit_email_page.dart new file mode 100644 index 0000000..29e7e4c --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/edit_email_page.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/pages/confirm_email_page.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/utils/core_utils.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/buttons/primary_button.dart'; +import 'package:reown_appkit/modal/widgets/buttons/secondary_button.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/input_email.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/verify_otp_view.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; + +class EditEmailPage extends StatefulWidget { + const EditEmailPage() : super(key: KeyConstants.editEmailPage); + + @override + State createState() => _EditEmailPageState(); +} + +class _EditEmailPageState extends State { + late final String _currentEmailValue; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + magicService.instance.onMagicError.subscribe(_onMagicErrorEvent); + _currentEmailValue = magicService.instance.email.value; + if (!magicService.instance.isConnected.value) { + magicService.instance.connectEmail(value: _currentEmailValue); + widgetStack.instance.popAllAndPush(ConfirmEmailPage()); + } + }); + } + + @override + void dispose() { + magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); + super.dispose(); + } + + void _onMagicErrorEvent(MagicErrorEvent? event) { + toastService.instance.show(ToastMessage( + type: ToastType.error, + text: event?.error ?? 'An error occurred.', + )); + setState(() {}); + } + + void _goBack() { + FocusManager.instance.primaryFocus?.unfocus(); + magicService.instance.setEmail(_currentEmailValue); + magicService.instance.setNewEmail(''); + widgetStack.instance.pop(); + magicService.instance.step.value = EmailLoginStep.idle; + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: magicService.instance.step, + builder: (context, action, _) { + String title = 'Edit Email'; + if (action == EmailLoginStep.verifyOtp) { + title = 'Confirm current email'; + } + if (action == EmailLoginStep.verifyOtp2) { + title = 'Confirm new email'; + } + return ModalNavbar( + title: title, + safeAreaLeft: true, + safeAreaRight: true, + onBack: _goBack, + body: Builder( + builder: (_) { + if (action == EmailLoginStep.loading) { + return ContentLoading(viewHeight: 200.0); + } + if (action == EmailLoginStep.verifyOtp || + action == EmailLoginStep.verifyOtp2) { + return VerifyOtpView( + currentEmail: (action == EmailLoginStep.verifyOtp2) + ? magicService.instance.newEmail.value + : magicService.instance.email.value, + resendEmail: _resendEmail, + verifyOtp: (action == EmailLoginStep.verifyOtp2) + ? magicService.instance.updateEmailSecondaryOtp + : magicService.instance.updateEmailPrimaryOtp, + ); + } + return _EditEmailView(); + }, + ), + ); + }, + ); + } + + Future _resendEmail({String? value}) async { + final email = magicService.instance.newEmail.value; + magicService.instance.updateEmail(value: email); + } +} + +class _EditEmailView extends StatefulWidget { + @override + State<_EditEmailView> createState() => __EditEmailViewState(); +} + +class __EditEmailViewState extends State<_EditEmailView> { + String _newEmailValue = ''; + late final String _currentEmailValue; + bool _isValidEmail = false; + + @override + void initState() { + super.initState(); + _currentEmailValue = magicService.instance.email.value; + _newEmailValue = _currentEmailValue; + } + + void _onValueChange(String value) { + magicService.instance.setNewEmail(value); + _newEmailValue = value; + final valid = CoreUtils.isValidEmail(value); + setState(() { + _isValidEmail = valid && (_newEmailValue != _currentEmailValue); + }); + } + + void _onSubmittedEmail(String value) { + FocusManager.instance.primaryFocus?.unfocus(); + // magicService.instance.setNewEmail(value); + magicService.instance.updateEmail(value: value); + } + + void _goBack() { + FocusManager.instance.primaryFocus?.unfocus(); + magicService.instance.setEmail(_currentEmailValue); + magicService.instance.setNewEmail(''); + widgetStack.instance.pop(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(kPadding8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InputEmailWidget( + suffixIcon: const SizedBox.shrink(), + initialValue: _currentEmailValue, + onValueChange: _onValueChange, + onSubmitted: _onSubmittedEmail, + ), + const SizedBox.square(dimension: 4.0), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + const SizedBox.square(dimension: 4.0), + Expanded( + child: SecondaryButton( + title: 'Cancel', + onTap: _goBack, + ), + ), + const SizedBox.square(dimension: kPadding8), + Expanded( + child: PrimaryButton( + title: 'Save', + onTap: _isValidEmail + ? () => _onSubmittedEmail(_newEmailValue) + : null, + ), + ), + const SizedBox.square(dimension: 4.0), + ], + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart b/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart new file mode 100644 index 0000000..35ca109 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart @@ -0,0 +1,90 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/services/uri_service/url_utils_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/models/grid_item.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/all_wallets_item.dart'; +import 'package:reown_appkit/modal/widgets/lists/wallets_list.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/widgets/value_listenable_builders/explorer_service_items_listener.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; + +class GetWalletPage extends StatelessWidget { + const GetWalletPage() : super(key: KeyConstants.getAWalletPageKey); + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final isPortrait = ResponsiveData.isPortrait(context); + final maxHeight = isPortrait + ? (kListItemHeight * 7) + : ResponsiveData.maxHeightOf(context); + return ModalNavbar( + title: 'Get a Wallet', + body: ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxHeight), + child: ExplorerServiceItemsListener( + builder: (context, initialised, items, _) { + if (!initialised) { + return const ContentLoading(); + } + + final notInstalledItems = items + .where((GridItem w) => + !w.data.installed && !w.data.recent) + .toList(); + final itemsToShow = notInstalledItems + .getRange(0, min(5, notInstalledItems.length)) + .toList(); + + return WalletsList( + itemList: itemsToShow, + onTapWallet: (data) { + final url = Platform.isIOS + ? data.listing.appStore + : data.listing.playStore; + if ((url ?? '').isNotEmpty) { + uriService.instance.launchUrl( + Uri.parse(url!), + mode: LaunchMode.externalApplication, + ); + } + }, + bottomItems: [ + AllWalletsItem( + title: 'Explore all', + onTap: () => uriService.instance.launchUrl( + Uri.parse(UrlConstants.exploreWallets), + mode: LaunchMode.externalApplication, + ), + trailing: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: SvgPicture.asset( + 'lib/modal/assets/icons/arrow_top_right.svg', + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + themeColors.foreground200, + BlendMode.srcIn, + ), + width: 18.0, + height: 18.0, + ), + ), + ), + ], + ); + }, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_all_wallets_page.dart b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_all_wallets_page.dart new file mode 100644 index 0000000..4eada8a --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_all_wallets_page.dart @@ -0,0 +1,124 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/pages/connect_wallet_page.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/lists/wallets_grid.dart'; +import 'package:reown_appkit/modal/widgets/value_listenable_builders/explorer_service_items_listener.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/all_wallets_header.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; + +class AppKitModalAllWalletsPage extends StatefulWidget { + const AppKitModalAllWalletsPage() + : super(key: KeyConstants.walletListLongPageKey); + + @override + State createState() => + _AppKitModalAllWalletsPageState(); +} + +class _AppKitModalAllWalletsPageState extends State { + bool _paginating = false; + final _controller = ScrollController(); + + bool _processScrollNotification(ScrollNotification notification) { + if (notification is ScrollEndNotification) { + setState(() => _paginating = false); + } else { + if (notification is UserScrollNotification) { + return true; + } + final extent = _controller.position.maxScrollExtent * 0.9; + final outOfRange = _controller.position.outOfRange; + if (_controller.offset >= extent && !outOfRange) { + if (!_paginating) { + _paginate(); + } + } + } + return true; + } + + Future _paginate() { + setState(() => _paginating = explorerService.instance.canPaginate); + return explorerService.instance.paginate(); + } + + @override + Widget build(BuildContext context) { + final service = ModalProvider.of(context).service; + final totalListings = explorerService.instance.totalListings.value; + final rows = (totalListings / 4.0).ceil(); + final isSearchAvailable = totalListings >= kShortWalletListCount; + final maxHeight = (rows * kGridItemHeight) + + (kPadding16 * 4.0) + + (isSearchAvailable ? kSearchFieldHeight : 0.0) + + ResponsiveData.paddingBottomOf(context); + return ModalNavbar( + title: 'All wallets', + onTapTitle: () => _controller.animateTo( + 0, + duration: Duration(milliseconds: 200), + curve: Curves.linear, + ), + onBack: () { + FocusManager.instance.primaryFocus?.unfocus(); + explorerService.instance.search(query: null); + widgetStack.instance.pop(); + }, + safeAreaBottom: false, + safeAreaLeft: true, + safeAreaRight: true, + body: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: !isSearchAvailable + ? maxHeight + : min(maxHeight, ResponsiveData.maxHeightOf(context)), + ), + child: Column( + children: [ + isSearchAvailable ? const AllWalletsHeader() : SizedBox.shrink(), + Expanded( + child: NotificationListener( + onNotification: _processScrollNotification, + child: ExplorerServiceItemsListener( + listen: !_paginating, + builder: (context, initialised, items, searching) { + if (!initialised || searching) { + return WalletsGrid( + paddingTop: isSearchAvailable ? 0.0 : kPadding16, + showLoading: true, + loadingCount: + items.isNotEmpty ? min(16, items.length) : 16, + scrollController: _controller, + itemList: [], + ); + } + final isPortrait = ResponsiveData.isPortrait(context); + return WalletsGrid( + paddingTop: isSearchAvailable ? 0.0 : kPadding16, + showLoading: _paginating, + loadingCount: isPortrait ? 4 : 8, + scrollController: _controller, + onTapWallet: (data) async { + service.selectWallet(data); + widgetStack.instance.push(const ConnectWalletPage()); + }, + itemList: items, + ); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_main_wallets_page.dart b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_main_wallets_page.dart new file mode 100644 index 0000000..00332fb --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_main_wallets_page.dart @@ -0,0 +1,247 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/pages/about_wallets.dart'; +import 'package:reown_appkit/modal/pages/confirm_email_page.dart'; +import 'package:reown_appkit/modal/pages/connect_wallet_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/input_email.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/all_wallets_item.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/wallet_item_chip.dart'; +import 'package:reown_appkit/modal/widgets/lists/wallets_list.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar_action_button.dart'; +import 'package:reown_appkit/modal/widgets/value_listenable_builders/explorer_service_items_listener.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AppKitModalMainWalletsPage extends StatefulWidget { + const AppKitModalMainWalletsPage() + : super(key: KeyConstants.walletListShortPageKey); + + @override + State createState() => + _AppKitModalMainWalletsPageState(); +} + +class _AppKitModalMainWalletsPageState + extends State { + @override + void initState() { + super.initState(); + magicService.instance.isEnabled.addListener(_mailEnabledListener); + } + + void _mailEnabledListener() { + setState(() {}); + } + + @override + void dispose() { + magicService.instance.isEnabled.removeListener(_mailEnabledListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final service = ModalProvider.of(context).service; + final isPortrait = ResponsiveData.isPortrait(context); + double maxHeight = isPortrait + ? (kListItemHeight * 6) + : ResponsiveData.maxHeightOf(context); + return ModalNavbar( + title: 'Connect wallet', + leftAction: NavbarActionButton( + asset: 'lib/modal/assets/icons/help.svg', + action: () { + widgetStack.instance.push( + const AboutWallets(), + event: ClickWalletHelpEvent(), + ); + }, + ), + safeAreaLeft: true, + safeAreaRight: true, + body: ExplorerServiceItemsListener( + builder: (context, initialised, items, _) { + if (!initialised) { + return ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxHeight), + child: const WalletsList( + isLoading: true, + itemList: [], + ), + ); + } + final itemsCount = min(kShortWalletListCount, items.length); + if (itemsCount < kShortWalletListCount) { + maxHeight = kListItemHeight * (itemsCount + 1.5); + } + final emailEnabled = magicService.instance.isEnabled.value; + if (emailEnabled) { + maxHeight += (kSearchFieldHeight * 2); + } + final itemsToShow = items.getRange(0, itemsCount); + return ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxHeight), + child: WalletsList( + onTapWallet: (data) { + service.selectWallet(data); + widgetStack.instance.push(const ConnectWalletPage()); + }, + firstItem: _EmailLoginWidget(), + itemList: itemsToShow.toList(), + bottomItems: [ + AllWalletsItem( + trailing: (items.length <= kShortWalletListCount) + ? null + : ValueListenableBuilder( + valueListenable: + explorerService.instance.totalListings, + builder: (context, value, _) { + return WalletItemChip(value: value.lazyCount); + }, + ), + onTap: () { + if (items.length <= kShortWalletListCount) { + widgetStack.instance.push( + const AppKitModalQRCodePage(), + event: SelectWalletEvent( + name: 'WalletConnect', + platform: AnalyticsPlatform.qrcode, + ), + ); + } else { + widgetStack.instance.push( + const AppKitModalAllWalletsPage(), + event: ClickAllWalletsEvent(), + ); + } + }, + ), + ], + ), + ); + }, + ), + ); + } +} + +class _LoginDivider extends StatelessWidget { + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final themeData = AppKitModalTheme.getDataOf(context); + return Row( + children: [ + Expanded( + child: Divider(color: themeColors.grayGlass005, height: 0.0), + ), + Padding( + padding: const EdgeInsets.only( + left: kPadding12, + right: kPadding12, + ), + child: Text( + 'Or', + style: themeData.textStyles.small400.copyWith( + color: themeColors.foreground200, + ), + ), + ), + Expanded( + child: Divider(color: themeColors.grayGlass005, height: 0.0), + ), + ], + ); + } +} + +extension on int { + String get lazyCount { + if (this <= 10) return toString(); + return '${toString().substring(0, toString().length - 1)}0+'; + } +} + +class _EmailLoginWidget extends StatefulWidget { + @override + State<_EmailLoginWidget> createState() => __EmailLoginWidgetState(); +} + +class __EmailLoginWidgetState extends State<_EmailLoginWidget> { + bool _submitted = false; + @override + void initState() { + super.initState(); + magicService.instance.step.addListener(_stepListener); + } + + void _stepListener() { + debugPrint(magicService.instance.step.value.toString()); + if ((magicService.instance.step.value == EmailLoginStep.verifyDevice || + magicService.instance.step.value == EmailLoginStep.verifyOtp || + magicService.instance.step.value == EmailLoginStep.verifyOtp2) && + _submitted) { + widgetStack.instance.push(ConfirmEmailPage()); + _submitted = false; + } + } + + @override + void dispose() { + magicService.instance.step.removeListener(_stepListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: magicService.instance.isEnabled, + builder: (context, emailEnabled, _) { + if (!emailEnabled) { + return const SizedBox.shrink(); + } + return Column( + children: [ + InputEmailWidget( + onFocus: (value) { + if (value) { + analyticsService.instance.sendEvent( + EmailLoginSelected(), + ); + } + }, + onValueChange: (value) { + magicService.instance.setEmail(value); + }, + onSubmitted: (value) { + setState(() => _submitted = true); + final service = ModalProvider.of(context).service; + final chainId = service.selectedChain?.chainId; + analyticsService.instance.sendEvent(EmailSubmitted()); + magicService.instance.connectEmail( + value: value, + chainId: chainId, + ); + }, + ), + const SizedBox.square(dimension: 4.0), + _LoginDivider(), + const SizedBox.square(dimension: kListViewSeparatorHeight), + ], + ); + }, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_pages.dart b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_pages.dart new file mode 100644 index 0000000..8e80878 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_pages.dart @@ -0,0 +1,4 @@ +export 'appkit_modal_all_wallets_page.dart'; +export 'appkit_modal_main_wallets_page.dart'; +export 'appkit_modal_qrcode_page.dart'; +export 'appkit_modal_select_network_page.dart'; diff --git a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_qrcode_page.dart b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_qrcode_page.dart new file mode 100644 index 0000000..25d4b4e --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_qrcode_page.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:shimmer/shimmer.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; +import 'package:reown_appkit/modal/widgets/qr_code_view.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; + +class AppKitModalQRCodePage extends StatefulWidget { + const AppKitModalQRCodePage() : super(key: KeyConstants.qrCodePageKey); + + @override + State createState() => _AppKitModalQRCodePageState(); +} + +class _AppKitModalQRCodePageState extends State { + IAppKitModal? _service; + Widget? _qrQodeWidget; + // + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + _service = ModalProvider.of(context).service; + _service!.addListener(_buildWidget); + _service!.appKit!.core.pairing.onPairingExpire.subscribe( + _onPairingExpire, + ); + _service?.onModalError.subscribe(_onError); + await _service!.buildConnectionUri(); + }); + } + + void _buildWidget() => setState(() { + _qrQodeWidget = QRCodeView( + uri: _service!.wcUri!, + logoPath: 'lib/modal/assets/png/logo_wc.png', + ); + }); + + void _onPairingExpire(EventArgs? args) async { + await _service!.buildConnectionUri(); + setState(() {}); + } + + void _onError(ModalError? args) { + final event = args ?? ModalError('An error occurred'); + toastService.instance.show( + ToastMessage( + type: ToastType.error, + text: event.message, + ), + ); + } + + @override + void dispose() async { + _service?.onModalError.unsubscribe(_onError); + _service!.appKit!.core.pairing.onPairingExpire.unsubscribe( + _onPairingExpire, + ); + _service!.removeListener(_buildWidget); + _service!.expirePreviousInactivePairings(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + final isPortrait = ResponsiveData.isPortrait(context); + + return ModalNavbar( + title: 'WalletConnect', + body: SingleChildScrollView( + scrollDirection: isPortrait ? Axis.vertical : Axis.horizontal, + child: Flex( + direction: isPortrait ? Axis.vertical : Axis.horizontal, + children: [ + Padding( + padding: EdgeInsets.all(20.0), + child: _qrQodeWidget ?? + AspectRatio( + aspectRatio: 1.0, + child: Shimmer.fromColors( + baseColor: themeColors.grayGlass100, + highlightColor: themeColors.grayGlass025, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(radiuses.radiusL), + color: themeColors.grayGlass010, + ), + ), + ), + ), + ), + Container( + constraints: BoxConstraints( + maxWidth: isPortrait + ? ResponsiveData.maxWidthOf(context) + : (ResponsiveData.maxHeightOf(context) - + kNavbarHeight - + 32.0), + ), + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Scan this QR code with your phone', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: kPadding12), + child: SimpleIconButton( + onTap: () => _copyToClipboard(context), + leftIcon: 'lib/modal/assets/icons/copy_14.svg', + iconSize: 13.0, + title: 'Copy link', + fontSize: 14.0, + backgroundColor: Colors.transparent, + foregroundColor: themeColors.foreground200, + overlayColor: MaterialStateProperty.all( + themeColors.background200, + ), + withBorder: false, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Future _copyToClipboard(BuildContext context) async { + final service = ModalProvider.of(context).service; + await Clipboard.setData(ClipboardData(text: service.wcUri!)); + toastService.instance.show( + ToastMessage(type: ToastType.success, text: 'Link copied'), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_select_network_page.dart b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_select_network_page.dart new file mode 100644 index 0000000..961c47b --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_select_network_page.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/pages/about_networks.dart'; +import 'package:reown_appkit/modal/pages/connet_network_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; + +import 'package:reown_appkit/modal/widgets/lists/networks_grid.dart'; +import 'package:reown_appkit/modal/widgets/value_listenable_builders/network_service_items_listener.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AppKitModalSelectNetworkPage extends StatelessWidget { + const AppKitModalSelectNetworkPage({ + this.onTapNetwork, + }) : super(key: KeyConstants.selectNetworkPage); + + final Function(AppKitModalNetworkInfo)? onTapNetwork; + + void _onSelectNetwork( + BuildContext context, AppKitModalNetworkInfo chainInfo) async { + final service = ModalProvider.of(context).service; + if (service.isConnected) { + final approvedChains = service.session!.getApprovedChains() ?? []; + final caip2Chain = '${CoreConstants.namespace}:${chainInfo.chainId}'; + final isChainApproved = approvedChains.contains(caip2Chain); + if (chainInfo.chainId == service.selectedChain?.chainId) { + widgetStack.instance.pop(); + } else if (isChainApproved || service.session!.sessionService.isMagic) { + await service.selectChain(chainInfo, switchChain: true); + widgetStack.instance.pop(); + } else { + widgetStack.instance.push(ConnectNetworkPage(chainInfo: chainInfo)); + } + } else { + onTapNetwork?.call(chainInfo); + } + } + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final service = ModalProvider.of(context).service; + final isSwitch = service.selectedChain != null; + final isPortrait = ResponsiveData.isPortrait(context); + final maxHeight = + (ResponsiveData.gridItemSzieOf(context).height * 3) + (kPadding12 * 4); + + return ModalNavbar( + title: isSwitch ? 'Change network' : 'Select network', + safeAreaLeft: true, + safeAreaRight: true, + body: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + fit: isPortrait ? FlexFit.loose : FlexFit.tight, + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxHeight), + child: NetworkServiceItemsListener( + builder: (context, initialised, items) { + if (!initialised) { + return const ContentLoading(); + } + return NetworksGrid( + onTapNetwork: (chainInfo) => _onSelectNetwork( + context, + chainInfo, + ), + itemList: items, + ); + }, + ), + ), + ), + Divider(color: themeColors.grayGlass005, height: 0.0), + const SizedBox.square(dimension: 8.0), + Text( + 'Your connected wallet may not support some of the networks available for this dApp', + textAlign: TextAlign.center, + style: themeData.textStyles.small500.copyWith( + color: themeColors.foreground300, + ), + ), + SimpleIconButton( + onTap: () { + widgetStack.instance.push( + const AboutNetworks(), + event: ClickNetworkHelpEvent(), + ); + }, + size: BaseButtonSize.small, + leftIcon: 'lib/modal/assets/icons/help.svg', + title: 'What is a network?', + fontSize: 15.0, + backgroundColor: Colors.transparent, + foregroundColor: themeColors.accent100, + overlayColor: MaterialStateProperty.all( + themeColors.background200, + ), + withBorder: false, + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart b/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart new file mode 100644 index 0000000..d61c568 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; + +class UpgradeWalletPage extends StatelessWidget { + const UpgradeWalletPage() : super(key: KeyConstants.upgradeWalletPage); + + @override + Widget build(BuildContext context) { + final textStyles = AppKitModalTheme.getDataOf(context).textStyles; + final themeColors = AppKitModalTheme.colorsOf(context); + return ModalNavbar( + title: 'Upgrade your Wallet', + safeAreaBottom: true, + safeAreaLeft: true, + safeAreaRight: true, + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox.square(dimension: kPadding8), + const SizedBox.square(dimension: kPadding8), + Text( + 'Follow the instructions on', + style: textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: kPadding8), + SimpleIconButton( + leftIcon: 'lib/modal/assets/icons/wc.svg', + onTap: () { + analyticsService.instance.sendEvent(EmailUpgradeFromModal()); + launchUrlString( + UrlConstants.secureDashboard, + mode: LaunchMode.externalApplication, + ); + }, + rightIcon: 'lib/modal/assets/icons/arrow_top_right.svg', + title: Uri.parse(UrlConstants.secureDashboard).authority, + size: BaseButtonSize.small, + iconSize: 12.0, + fontSize: 14.0, + ), + const SizedBox.square(dimension: kPadding8), + Text( + 'You will have to reconnect for security reasons', + style: textStyles.small400.copyWith( + color: themeColors.foreground200, + ), + ), + const SizedBox.square(dimension: kPadding8), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart b/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart new file mode 100644 index 0000000..49ca02d --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart @@ -0,0 +1,98 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:reown_appkit/modal/utils/core_utils.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:uuid/uuid.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/services/analytics_service/i_analytics_service.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; + +class AnalyticsService implements IAnalyticsService { + static final _eventsController = StreamController.broadcast(); + static const _debugApiEndpoint = + 'https://analytics-api-cf-workers-staging.walletconnect-v1-bridge.workers.dev'; + static const _debugProjectId = 'e087b4b0503b860119be49d906717c12'; + // + bool _isEnabled = false; + late final String _bundleId; + late final String _endpoint; + late final Map _headers; + + @override + final Stream events = _eventsController.stream; + + @override + final bool? enableAnalytics; + + late final IReownCore _core; + + AnalyticsService({ + required IReownCore core, + this.enableAnalytics, + }) { + _core = core; + _endpoint = kDebugMode ? _debugApiEndpoint : UrlConstants.analyticsService; + _headers = kDebugMode + ? CoreUtils.getAPIHeaders(_debugProjectId) + : CoreUtils.getAPIHeaders(_core.projectId); + } + + @override + Future init() async { + try { + if (enableAnalytics == null) { + _isEnabled = await fetchAnalyticsConfig(); + } else { + _isEnabled = enableAnalytics!; + } + _bundleId = await ReownCoreUtils.getPackageName(); + _core.logger.d('[$runtimeType] enabled: $_isEnabled'); + } catch (e, _) { + _core.logger.d('[$runtimeType] init error', error: e); + } + } + + @override + Future fetchAnalyticsConfig() async { + try { + final response = await http.get( + Uri.parse('${UrlConstants.apiService}/getAnalyticsConfig'), + headers: _headers, + ); + final json = jsonDecode(response.body) as Map; + final enabled = json['isAnalyticsEnabled'] as bool?; + return enabled ?? false; + } catch (e, _) { + _core.logger.d('[$runtimeType] fetch error', error: e); + return false; + } + } + + @override + void sendEvent(AnalyticsEvent analyticsEvent) async { + if (!_isEnabled) return; + try { + final body = jsonEncode({ + 'eventId': Uuid().v4(), + 'bundleId': _bundleId, + 'timestamp': DateTime.now().toUtc().millisecondsSinceEpoch, + 'props': analyticsEvent.toMap(), + }); + + final response = await http.post( + Uri.parse('$_endpoint/e'), + headers: _headers, + body: body, + ); + final code = response.statusCode; + if (code == 200 || code == 202) { + _eventsController.sink.add(analyticsEvent.toMap()); + } + _core.logger.d('[$runtimeType] send event $code: $body'); + } catch (e, _) { + _core.logger.d('[$runtimeType] send event error', error: e); + } + } +} diff --git a/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service_singleton.dart b/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service_singleton.dart new file mode 100644 index 0000000..83ccb30 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service_singleton.dart @@ -0,0 +1,7 @@ +import 'package:reown_appkit/modal/services/analytics_service/i_analytics_service.dart'; + +class AnalyticsServiceSingleton { + late IAnalyticsService instance; +} + +final analyticsService = AnalyticsServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/analytics_service/i_analytics_service.dart b/packages/reown_appkit/lib/modal/services/analytics_service/i_analytics_service.dart new file mode 100644 index 0000000..1c643c8 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/analytics_service/i_analytics_service.dart @@ -0,0 +1,9 @@ +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; + +abstract class IAnalyticsService { + bool? get enableAnalytics; + Stream get events; + Future init(); + void sendEvent(AnalyticsEvent analyticsEvent); + Future fetchAnalyticsConfig(); +} diff --git a/packages/reown_appkit/lib/modal/services/analytics_service/models/analytics_event.dart b/packages/reown_appkit/lib/modal/services/analytics_service/models/analytics_event.dart new file mode 100644 index 0000000..2af5a77 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/analytics_service/models/analytics_event.dart @@ -0,0 +1,589 @@ +enum AnalyticsPlatform { + mobile, + web, + qrcode, + email, + unsupported, +} + +abstract class AnalyticsEvent { + abstract final String type; + abstract final String event; + abstract final Map? properties; + + Map toMap(); +} + +class ModalCreatedEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'MODAL_CREATED'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ModalLoadedEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'MODAL_LOADED'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ModalOpenEvent implements AnalyticsEvent { + final bool _connected; + ModalOpenEvent({ + required bool connected, + }) : _connected = connected; + + @override + String get type => 'track'; + + @override + String get event => 'MODAL_OPEN'; + + @override + Map? get properties => { + 'connected': _connected, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ModalCloseEvent implements AnalyticsEvent { + final bool _connected; + ModalCloseEvent({ + required bool connected, + }) : _connected = connected; + + @override + String get type => 'track'; + + @override + String get event => 'MODAL_CLOSE'; + + @override + Map? get properties => { + 'connected': _connected, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ClickAllWalletsEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'CLICK_ALL_WALLETS'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ClickNetworksEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'CLICK_NETWORKS'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class SwitchNetworkEvent implements AnalyticsEvent { + final String _network; + SwitchNetworkEvent({ + required String network, + }) : _network = network; + + @override + String get type => 'track'; + + @override + String get event => 'SWITCH_NETWORK'; + + @override + Map? get properties => { + 'network': _network, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class SelectWalletEvent implements AnalyticsEvent { + final String _name; + final String? _platform; + SelectWalletEvent({ + required String name, + AnalyticsPlatform? platform, + }) : _name = name, + _platform = platform?.name; + + @override + String get type => 'track'; + + @override + String get event => 'SELECT_WALLET'; + + @override + Map? get properties => { + 'name': _name, + if (_platform != null) 'platform': _platform, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ConnectSuccessEvent implements AnalyticsEvent { + final String _name; + final String? _method; + ConnectSuccessEvent({ + required String name, + AnalyticsPlatform? method, + }) : _name = name, + _method = method?.name; + + @override + String get type => 'track'; + + @override + String get event => 'CONNECT_SUCCESS'; + + @override + Map? get properties => { + 'name': _name, + if (_method != null) 'method': _method, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ConnectErrorEvent implements AnalyticsEvent { + final String _message; + ConnectErrorEvent({ + required String message, + }) : _message = message; + + @override + String get type => 'track'; + + @override + String get event => 'CONNECT_ERROR'; + + @override + Map? get properties => { + 'message': _message, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class DisconnectSuccessEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'DISCONNECT_SUCCESS'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class DisconnectErrorEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'DISCONNECT_ERROR'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ClickWalletHelpEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'CLICK_WALLET_HELP'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ClickNetworkHelpEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'CLICK_NETWORK_HELP'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ClickGetWalletEvent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'CLICK_GET_WALLET'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class EmailLoginSelected implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'EMAIL_LOGIN_SELECTED'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class EmailSubmitted implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'EMAIL_SUBMITTED'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class DeviceRegisteredForEmail implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'DEVICE_REGISTERED_FOR_EMAIL'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class EmailVerificationCodeSent implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'EMAIL_VERIFICATION_CODE_SENT'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class EmailVerificationCodePass implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'EMAIL_VERIFICATION_CODE_PASS'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class EmailVerificationCodeFail implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'EMAIL_VERIFICATION_CODE_FAIL'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class EmailEdit implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'EMAIL_EDIT'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class EmailEditComplete implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'EMAIL_EDIT_COMPLETE'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class EmailUpgradeFromModal implements AnalyticsEvent { + @override + String get type => 'track'; + + @override + String get event => 'EMAIL_UPGRADE_FROM_MODAL'; + + @override + Map? get properties => null; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ClickSignSiweMessage implements AnalyticsEvent { + final String _network; + ClickSignSiweMessage({required String network}) : _network = network; + + @override + String get type => 'track'; + + @override + String get event => 'CLICK_SIGN_SIWE_MESSAGE'; + + @override + Map? get properties => { + 'network': _network, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class ClickCancelSiwe implements AnalyticsEvent { + final String _network; + ClickCancelSiwe({required String network}) : _network = network; + + @override + String get type => 'track'; + + @override + String get event => 'CLICK_CANCEL_SIWE'; + + @override + Map? get properties => { + 'network': _network, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class SiweAuthSuccess implements AnalyticsEvent { + final String _network; + SiweAuthSuccess({required String network}) : _network = network; + + @override + String get type => 'track'; + + @override + String get event => 'SIWE_AUTH_SUCCESS'; + + @override + Map? get properties => { + 'network': _network, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class SiweAuthError implements AnalyticsEvent { + final String _network; + SiweAuthError({required String network}) : _network = network; + + @override + String get type => 'track'; + + @override + String get event => 'SIWE_AUTH_ERROR'; + + @override + Map? get properties => { + 'network': _network, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} diff --git a/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart b/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart new file mode 100644 index 0000000..b5d62b6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart @@ -0,0 +1,174 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/services/blockchain_service/models/blockchain_identity.dart'; +import 'package:reown_appkit/modal/services/blockchain_service/i_blockchain_service.dart'; + +class BlockChainService implements IBlockChainService { + late final IReownCore _core; + late final String _baseUrl; + String? _clientId; + + BlockChainService({required IReownCore core}) + : _core = core, + _baseUrl = '${UrlConstants.blockChainService}/v1'; + + Map get _requiredParams => { + 'projectId': _core.projectId, + 'clientId': _clientId, + }; + + Map get _requiredHeaders => { + 'x-sdk-type': CoreConstants.X_SDK_TYPE, + 'x-sdk-version': 'flutter-${CoreConstants.X_SDK_VERSION}', + }; + + @override + Future init() async { + _clientId = await _core.crypto.getClientId(); + } + + @override + Future getIdentity(String address) async { + try { + final uri = Uri.parse('$_baseUrl/identity/$address'); + final queryParams = {..._requiredParams}; + // if (queryParams['clientId'] == null) { + // queryParams['clientId'] = await _core.crypto.getClientId(); + // } + final response = await http.get( + uri.replace(queryParameters: queryParams), + headers: _requiredHeaders, + ); + if (response.statusCode == 200) { + return BlockchainIdentity.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Failed to load avatar'); + } + } catch (e) { + _core.logger.e('[$runtimeType] getIdentity: $e'); + rethrow; + } + } + + int _retries = 1; + @override + Future rpcRequest({ + // required String? topic, + required String chainId, + required SessionRequestParams request, + }) async { + final bool isChainId = NamespaceUtils.isValidChainId(chainId); + if (!isChainId) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: '[$runtimeType] chain should be CAIP-2 valid', + ); + } + final uri = Uri.parse(_baseUrl); + final queryParams = {..._requiredParams, 'chainId': chainId}; + final response = await http.post( + uri.replace(queryParameters: queryParams), + headers: { + ..._requiredHeaders, + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'id': 1, + 'jsonrpc': '2.0', + 'method': request.method, + 'params': request.params, + }), + ); + if (response.statusCode == 200 && response.body.isNotEmpty) { + _retries = 1; + try { + final result = _parseRpcResultAs(response.body); + final amount = EtherAmount.fromBigInt(EtherUnit.wei, hexToInt(result)); + return amount.getValueInUnit(EtherUnit.ether); + } catch (e) { + rethrow; + } + } else { + if (response.body.isEmpty && _retries > 0) { + _core.logger.i('[$runtimeType] Empty body'); + _retries -= 1; + await rpcRequest(chainId: chainId, request: request); + } else { + _core.logger.i( + '[$runtimeType] Failed to get request ${request.toJson()}. ' + 'Response: ${response.body}, Status code: ${response.statusCode}', + ); + } + } + } + + T _parseRpcResultAs(String body) { + try { + final result = Map.from({ + ...jsonDecode(body), + 'id': 1, + }); + final jsonResponse = JsonRpcResponse.fromJson(result); + if (jsonResponse.result != null) { + return jsonResponse.result; + } + throw jsonResponse.error ?? + // TODO AppKitModal change this error + ReownSignError( + code: 0, + message: 'Error parsing result', + ); + } catch (e) { + rethrow; + } + } + + // @override + // Future getBalance( + // String address, + // String currency, { + // String? chain, + // String? forceUpdate, + // }) async { + // final uri = Uri.parse('$_baseUrl/account/$address/balance'); + // final queryParams = { + // ..._requiredParams, + // 'currency': currency, + // if (chain != null) 'chainId': chain, + // if (forceUpdate != null) 'forceUpdate': forceUpdate, + // }; + // final response = await http.get( + // uri.replace(queryParameters: queryParams), + // headers: { + // ..._requiredHeaders, + // // 'chain': chainId, + // // 'forceUpdate': string + // // 'Content-Type': 'application/json', + // }, + // // body: jsonEncode({ + // // 'jsonrpc': '2.0', + // // 'method': 'eth_getBalance', + // // 'params': [address, 'latest'], + // // 'chainId': 1 + // // }), + // ); + // _core.logger.i('[$runtimeType] getBalance $address: ${response.body}'); + // if (response.statusCode == 200) { + // } else { + // throw Exception('Failed to load balance'); + // } + // } + + // @override + // Future fetchEnsName(String rpcUrl, String address) async { + // return ''; + // } + + // @override + // Future fetchEnsAvatar(String rpcUrl, String address) async { + // return ''; + // } +} diff --git a/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service_singleton.dart b/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service_singleton.dart new file mode 100644 index 0000000..3ea620c --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service_singleton.dart @@ -0,0 +1,7 @@ +import 'package:reown_appkit/modal/services/blockchain_service/i_blockchain_service.dart'; + +class BlockChainServiceSingleton { + late IBlockChainService instance; +} + +final blockchainService = BlockChainServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/blockchain_service/i_blockchain_service.dart b/packages/reown_appkit/lib/modal/services/blockchain_service/i_blockchain_service.dart new file mode 100644 index 0000000..cdc0e8a --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/blockchain_service/i_blockchain_service.dart @@ -0,0 +1,15 @@ +import 'package:reown_appkit/modal/services/blockchain_service/models/blockchain_identity.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +abstract class IBlockChainService { + Future init(); + + /// Gets the name and avatar of a provided address on the given chain + Future getIdentity(String address); + + Future rpcRequest({ + // required String? topic, + required String chainId, + required SessionRequestParams request, + }); +} diff --git a/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.dart b/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.dart new file mode 100644 index 0000000..b18d7dc --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'blockchain_identity.g.dart'; +part 'blockchain_identity.freezed.dart'; + +@freezed +class BlockchainIdentity with _$BlockchainIdentity { + const factory BlockchainIdentity({ + String? name, + String? avatar, + }) = _BlockchainIdentity; + + factory BlockchainIdentity.fromJson(Map json) => + _$BlockchainIdentityFromJson(json); +} diff --git a/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.freezed.dart b/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.freezed.dart new file mode 100644 index 0000000..1c372f9 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.freezed.dart @@ -0,0 +1,169 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'blockchain_identity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BlockchainIdentity _$BlockchainIdentityFromJson(Map json) { + return _BlockchainIdentity.fromJson(json); +} + +/// @nodoc +mixin _$BlockchainIdentity { + String? get name => throw _privateConstructorUsedError; + String? get avatar => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BlockchainIdentityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BlockchainIdentityCopyWith<$Res> { + factory $BlockchainIdentityCopyWith( + BlockchainIdentity value, $Res Function(BlockchainIdentity) then) = + _$BlockchainIdentityCopyWithImpl<$Res, BlockchainIdentity>; + @useResult + $Res call({String? name, String? avatar}); +} + +/// @nodoc +class _$BlockchainIdentityCopyWithImpl<$Res, $Val extends BlockchainIdentity> + implements $BlockchainIdentityCopyWith<$Res> { + _$BlockchainIdentityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? avatar = freezed, + }) { + return _then(_value.copyWith( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + avatar: freezed == avatar + ? _value.avatar + : avatar // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BlockchainIdentityImplCopyWith<$Res> + implements $BlockchainIdentityCopyWith<$Res> { + factory _$$BlockchainIdentityImplCopyWith(_$BlockchainIdentityImpl value, + $Res Function(_$BlockchainIdentityImpl) then) = + __$$BlockchainIdentityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? name, String? avatar}); +} + +/// @nodoc +class __$$BlockchainIdentityImplCopyWithImpl<$Res> + extends _$BlockchainIdentityCopyWithImpl<$Res, _$BlockchainIdentityImpl> + implements _$$BlockchainIdentityImplCopyWith<$Res> { + __$$BlockchainIdentityImplCopyWithImpl(_$BlockchainIdentityImpl _value, + $Res Function(_$BlockchainIdentityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? avatar = freezed, + }) { + return _then(_$BlockchainIdentityImpl( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + avatar: freezed == avatar + ? _value.avatar + : avatar // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BlockchainIdentityImpl implements _BlockchainIdentity { + const _$BlockchainIdentityImpl({this.name, this.avatar}); + + factory _$BlockchainIdentityImpl.fromJson(Map json) => + _$$BlockchainIdentityImplFromJson(json); + + @override + final String? name; + @override + final String? avatar; + + @override + String toString() { + return 'BlockchainIdentity(name: $name, avatar: $avatar)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BlockchainIdentityImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.avatar, avatar) || other.avatar == avatar)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, name, avatar); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BlockchainIdentityImplCopyWith<_$BlockchainIdentityImpl> get copyWith => + __$$BlockchainIdentityImplCopyWithImpl<_$BlockchainIdentityImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$BlockchainIdentityImplToJson( + this, + ); + } +} + +abstract class _BlockchainIdentity implements BlockchainIdentity { + const factory _BlockchainIdentity( + {final String? name, final String? avatar}) = _$BlockchainIdentityImpl; + + factory _BlockchainIdentity.fromJson(Map json) = + _$BlockchainIdentityImpl.fromJson; + + @override + String? get name; + @override + String? get avatar; + @override + @JsonKey(ignore: true) + _$$BlockchainIdentityImplCopyWith<_$BlockchainIdentityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.g.dart b/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.g.dart new file mode 100644 index 0000000..a931cd6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/blockchain_service/models/blockchain_identity.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'blockchain_identity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BlockchainIdentityImpl _$$BlockchainIdentityImplFromJson( + Map json) => + _$BlockchainIdentityImpl( + name: json['name'] as String?, + avatar: json['avatar'] as String?, + ); + +Map _$$BlockchainIdentityImplToJson( + _$BlockchainIdentityImpl instance) => + { + 'name': instance.name, + 'avatar': instance.avatar, + }; diff --git a/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service.dart b/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service.dart new file mode 100644 index 0000000..8e7bc6c --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service.dart @@ -0,0 +1,391 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; + +import 'package:reown_appkit/modal/services/coinbase_service/i_coinbase_service.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/models/coinbase_data.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/models/coinbase_events.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; + +import 'package:coinbase_wallet_sdk/currency.dart'; +import 'package:coinbase_wallet_sdk/action.dart'; +import 'package:coinbase_wallet_sdk/coinbase_wallet_sdk.dart'; +import 'package:coinbase_wallet_sdk/configuration.dart'; +import 'package:coinbase_wallet_sdk/eth_web3_rpc.dart'; +import 'package:coinbase_wallet_sdk/request.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class CoinbaseService implements ICoinbaseService { + static const coinbasePackageName = 'org.toshi'; + static const defaultWalletData = AppKitModalWalletInfo( + listing: Listing( + id: 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', + name: 'Coinbase Wallet', + homepage: 'https://www.coinbase.com/wallet/', + imageId: 'a5ebc364-8f91-4200-fcc6-be81310a0000', + order: 4110, + mobileLink: 'cbwallet://wsegue', + appStore: 'https://apps.apple.com/app/apple-store/id1278383455', + playStore: 'https://play.google.com/store/apps/details?id=org.toshi', + // rdns: 'com.coinbase.wallet', + ), + installed: false, + recent: false, + ); + + String _iconImage = ''; + + @override + ConnectionMetadata get metadata => ConnectionMetadata( + metadata: PairingMetadata( + name: _walletData.listing.name, + description: '', + url: _walletData.listing.homepage, + icons: [ + _iconImage, + ], + redirect: Redirect( + native: _walletData.listing.mobileLink, + universal: _walletData.listing.webappLink, + ), + ), + publicKey: '', + ); + + static const supportedMethods = [ + ...MethodsConstants.requiredMethods, + 'eth_requestAccounts', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + 'eth_signTransaction', + MethodsConstants.walletSwitchEthChain, + MethodsConstants.walletAddEthChain, + 'wallet_watchAsset', + ]; + + @override + Event onCoinbaseConnect = Event(); + + @override + Event onCoinbaseError = Event(); + + @override + Event onCoinbaseSessionUpdate = + Event(); + + @override + Event get onCoinbaseResponse => + Event(); + + late final PairingMetadata _metadata; + late bool _enabled; + late AppKitModalWalletInfo _walletData; + late final IReownCore _core; + + CoinbaseService({ + required PairingMetadata metadata, + required IReownCore core, + bool enabled = false, + }) : _metadata = metadata, + _enabled = enabled, + _core = core; + + @override + Future init() async { + if (!_enabled) return; + // Configure SDK for each platform + + _walletData = (await explorerService.instance.getCoinbaseWalletObject()) ?? + defaultWalletData; + final imageId = defaultWalletData.listing.imageId; + _iconImage = explorerService.instance.getWalletImageUrl(imageId); + + final walletLink = _walletData.listing.mobileLink ?? ''; + final redirect = _metadata.redirect; + final callback = redirect?.universal ?? redirect?.native ?? ''; + if (callback.isNotEmpty || walletLink.isNotEmpty) { + try { + final config = Configuration( + ios: IOSConfiguration( + host: Uri.parse(walletLink), + callback: Uri.parse(callback), + ), + android: AndroidConfiguration( + domain: Uri.parse(callback), + ), + ); + await CoinbaseWalletSDK.shared.configure(config); + } catch (_) { + // Silent error + } + } else { + _enabled = false; + throw CoinbaseServiceException('Initialization error'); + } + } + + @override + Future get ownPublicKey async { + try { + return await CoinbaseWalletSDK.shared.ownPublicKey(); + } catch (e) { + _core.logger.e('[$runtimeType] ownPublicKey $e'); + return ''; + } + } + + @override + Future get peerPublicKey async { + try { + return await CoinbaseWalletSDK.shared.peerPublicKey(); + } catch (e) { + _core.logger.e('[$runtimeType] peerPublicKey $e'); + return ''; + } + } + + @override + Future getAccount() async { + await _checkInstalled(); + try { + final results = await CoinbaseWalletSDK.shared.initiateHandshake([ + const RequestAccounts(), + ]); + final result = results.first; + if (result.error != null) { + final errorCode = result.error?.code; + final errorMessage = result.error!.message; + onCoinbaseError.broadcast(CoinbaseErrorEvent(errorMessage)); + throw CoinbaseServiceException('$errorMessage ($errorCode)'); + } + + final data = CoinbaseData.fromJson(result.account!.toJson()).copytWith( + peer: metadata.copyWith( + publicKey: await peerPublicKey, + ), + self: ConnectionMetadata( + metadata: _metadata, + publicKey: await ownPublicKey, + ), + ); + onCoinbaseConnect.broadcast(CoinbaseConnectEvent(data)); + return; + } on PlatformException catch (e, s) { + // Currently Coinbase SDK is not differentiate between User rejection or any other kind of error in iOS + final errorMessage = (e.message ?? '').toLowerCase(); + onCoinbaseError.broadcast(CoinbaseErrorEvent(errorMessage)); + throw CoinbaseServiceException(errorMessage, e, s); + } catch (e, s) { + onCoinbaseError.broadcast(CoinbaseErrorEvent('Initial handshake error')); + throw CoinbaseServiceException('Initial handshake error', e, s); + } + } + + @override + Future request({ + required String chainId, + required SessionRequestParams request, + }) async { + await _checkInstalled(); + final cid = chainId.contains(':') ? chainId.split(':').last : chainId; + try { + final req = Request(actions: [request.toCoinbaseRequest(cid)]); + final result = (await CoinbaseWalletSDK.shared.makeRequest(req)).first; + if (result.error != null) { + final errorCode = result.error?.code; + final errorMessage = result.error!.message; + onCoinbaseError.broadcast(CoinbaseErrorEvent(errorMessage)); + throw CoinbaseServiceException('$errorMessage ($errorCode)'); + } + final value = result.value?.replaceAll('"', ''); + switch (req.actions.first.method) { + case 'wallet_switchEthereumChain': + case 'wallet_addEthereumChain': + final event = CoinbaseSessionEvent(chainId: cid); + onCoinbaseSessionUpdate.broadcast(event); + break; + case 'eth_requestAccounts': + final json = jsonDecode(value!); + final data = CoinbaseData.fromJson(json).copytWith( + peer: metadata.copyWith( + publicKey: await peerPublicKey, + ), + self: ConnectionMetadata( + metadata: _metadata, + publicKey: await ownPublicKey, + ), + ); + onCoinbaseConnect.broadcast(CoinbaseConnectEvent(data)); + break; + default: + onCoinbaseResponse.broadcast(CoinbaseResponseEvent(data: value)); + break; + } + return value; + } on CoinbaseServiceException catch (e) { + onCoinbaseError.broadcast(CoinbaseErrorEvent(e.message)); + rethrow; + } on PlatformException catch (e, s) { + final message = 'Coinbase Wallet Error: (${e.code}) ${e.message}'; + onCoinbaseError.broadcast(CoinbaseErrorEvent(message)); + throw CoinbaseServiceException(message, e, s); + } + } + + @override + Future isInstalled() async { + try { + return await CoinbaseWalletSDK.shared.isAppInstalled(); + } catch (e, s) { + throw CoinbaseServiceException('Check is installed error', e, s); + } + } + + @override + Future isConnected() async { + try { + return await CoinbaseWalletSDK.shared.isConnected(); + } catch (e, s) { + throw CoinbaseServiceException('Check is connected error', e, s); + } + } + + @override + Future resetSession() async { + try { + return CoinbaseWalletSDK.shared.resetSession(); + } catch (e, s) { + throw CoinbaseServiceException('Reset session error', e, s); + } + } + + Future _checkInstalled() async { + final installed = await isInstalled(); + if (!installed) { + throw CoinbaseWalletNotInstalledException(); + } + return true; + } +} + +extension on SessionRequestParams { + Action toCoinbaseRequest(String? chainId) { + switch (method) { + case 'personal_sign': + final address = _getAddressFromParamsList(method, params); + final message = _getDataFromParamsList(method, params); + return PersonalSign(address: address, message: message); + case 'eth_signTypedData_v3': + final address = _getAddressFromParamsList(method, params); + final jsonData = _getDataFromParamsList(method, params); + return SignTypedDataV3(address: address, typedDataJson: jsonData); + case 'eth_signTypedData_v4': + final address = _getAddressFromParamsList(method, params); + final jsonData = _getDataFromParamsList(method, params); + return SignTypedDataV4(address: address, typedDataJson: jsonData); + case 'eth_requestAccounts': + return RequestAccounts(); + case 'eth_signTransaction': + case MethodsConstants.ethSendTransaction: + BigInt? weiValue; + final jsonData = _getTransactionFromParams(params); + if (jsonData.containsKey('value')) { + final hexValue = jsonData['value'].toString().replaceFirst('0x', ''); + final value = int.parse(hexValue, radix: 16); + weiValue = BigInt.from(value); + } + final data = jsonData['data']?.toString(); + if (method == 'eth_signTransaction') { + return SignTransaction( + fromAddress: jsonData['from'].toString(), + toAddress: jsonData['to'].toString(), + chainId: chainId!, + weiValue: weiValue, + data: data, + ); + } + return SendTransaction( + fromAddress: jsonData['from'].toString(), + toAddress: jsonData['to'].toString(), + chainId: chainId!, + weiValue: weiValue, + data: data, + ); + case MethodsConstants.walletSwitchEthChain: + case MethodsConstants.walletAddEthChain: + try { + final chainInfo = AppKitModalNetworks.getNetworkById( + CoreConstants.namespace, + chainId!, + )!; + return AddEthereumChain( + chainId: chainInfo.chainId, + rpcUrls: [chainInfo.rpcUrl], + chainName: chainInfo.name, + nativeCurrency: Currency( + name: chainInfo.currency, + symbol: chainInfo.currency, + decimals: 18, + ), + iconUrls: [chainInfo.chainIcon!], + blockExplorerUrls: [ + chainInfo.explorerUrl, + ], + ); + } catch (e, s) { + throw CoinbaseServiceException('Unrecognized chainId $chainId', e, s); + } + case 'wallet_watchAsset': + final address = _getAddressFromParamsList(method, params); + final symbol = _getDataFromParamsList(method, params); + return WatchAsset( + address: address, + symbol: symbol, + ); + default: + throw CoinbaseServiceException('Unsupported request method $method'); + } + } + + String _getAddressFromParamsList(String method, dynamic params) { + try { + final paramsList = List.from((params as List)); + if (method == 'personal_sign') { + // for `personal_sign` first value in params has to be always the message + paramsList.removeAt(0); + } + + return paramsList.firstWhere((p) { + try { + EthereumAddress.fromHex(p); + return true; + } catch (e) { + return false; + } + }); + } catch (e) { + rethrow; + } + } + + dynamic _getDataFromParamsList(String method, dynamic params) { + try { + final paramsList = List.from((params as List)); + if (method == 'personal_sign') { + return paramsList.first; + } + return paramsList.firstWhere((p) { + final address = _getAddressFromParamsList(method, params); + return p != address; + }); + } catch (e) { + rethrow; + } + } + + Map _getTransactionFromParams(dynamic params) { + final param = (params as List).first; + return param as Map; + } +} diff --git a/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service_singleton.dart b/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service_singleton.dart new file mode 100644 index 0000000..bc8a08b --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service_singleton.dart @@ -0,0 +1,7 @@ +import 'package:reown_appkit/modal/services/coinbase_service/i_coinbase_service.dart'; + +class CoinbaseServiceSingleton { + late ICoinbaseService instance; +} + +final coinbaseService = CoinbaseServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/coinbase_service/i_coinbase_service.dart b/packages/reown_appkit/lib/modal/services/coinbase_service/i_coinbase_service.dart new file mode 100644 index 0000000..7a93b8c --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/coinbase_service/i_coinbase_service.dart @@ -0,0 +1,43 @@ +import 'package:reown_appkit/modal/services/coinbase_service/models/coinbase_events.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class CoinbaseServiceException implements Exception { + final String message; + final dynamic error; + final dynamic stackTrace; + CoinbaseServiceException( + this.message, [ + this.error, + this.stackTrace, + ]) : super(); +} + +class CoinbaseWalletNotInstalledException extends CoinbaseServiceException { + CoinbaseWalletNotInstalledException() : super('App not installed'); +} + +class CoinbaseNotEnabledException extends CoinbaseServiceException { + CoinbaseNotEnabledException() : super('Coinbase is disabled'); +} + +abstract class ICoinbaseService { + Future init(); + Future isConnected(); + Future getAccount(); + Future request({ + required String chainId, + required SessionRequestParams request, + }); + Future resetSession(); + Future isInstalled(); + + Future get ownPublicKey; + Future get peerPublicKey; + + ConnectionMetadata get metadata; + + abstract final Event onCoinbaseConnect; + abstract final Event onCoinbaseError; + abstract final Event onCoinbaseSessionUpdate; + abstract final Event onCoinbaseResponse; +} diff --git a/packages/reown_appkit/lib/modal/services/coinbase_service/models/coinbase_data.dart b/packages/reown_appkit/lib/modal/services/coinbase_service/models/coinbase_data.dart new file mode 100644 index 0000000..849ca08 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/coinbase_service/models/coinbase_data.dart @@ -0,0 +1,60 @@ +import 'package:reown_appkit/reown_appkit.dart'; + +class CoinbaseData { + String address; + String chainName; + int chainId; + ConnectionMetadata? self; + ConnectionMetadata? peer; + + CoinbaseData({ + required this.address, + required this.chainName, + required this.chainId, + this.self, + this.peer, + }); + + factory CoinbaseData.fromJson(Map json) { + return CoinbaseData( + address: json['address'].toString(), + chainName: json['chain'].toString(), + chainId: int.parse(json['networkId'].toString()), + self: (json['self'] != null) + ? ConnectionMetadata.fromJson(json['self']) + : null, + peer: (json['peer'] != null) + ? ConnectionMetadata.fromJson(json['peer']) + : null, + ); + } + + Map toJson() { + return { + 'address': address, + 'chain': chainName, + 'networkId': chainId, + 'self': self?.toJson(), + 'peer': peer?.toJson(), + }; + } + + @override + String toString() => toJson().toString(); + + CoinbaseData copytWith({ + String? address, + String? chainName, + int? chainId, + ConnectionMetadata? self, + ConnectionMetadata? peer, + }) { + return CoinbaseData( + address: address ?? this.address, + chainName: chainName ?? this.chainName, + chainId: chainId ?? this.chainId, + self: self ?? this.self, + peer: peer ?? this.peer, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/services/coinbase_service/models/coinbase_events.dart b/packages/reown_appkit/lib/modal/services/coinbase_service/models/coinbase_events.dart new file mode 100644 index 0000000..25eb298 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/coinbase_service/models/coinbase_events.dart @@ -0,0 +1,50 @@ +import 'package:event/event.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/models/coinbase_data.dart'; + +class CoinbaseConnectEvent implements EventArgs { + final CoinbaseData? data; + CoinbaseConnectEvent(this.data); + + @override + String toString() => data?.toString() ?? ''; +} + +class CoinbaseErrorEvent implements EventArgs { + final String? error; + CoinbaseErrorEvent(this.error); +} + +class CoinbaseSessionEvent implements EventArgs { + String? address; + String? chainName; + String? chainId; + + CoinbaseSessionEvent({ + this.address, + this.chainName, + this.chainId, + }); + + Map toJson() { + Map params = {}; + if ((address ?? '').isNotEmpty) { + params['address'] = address; + } + if ((chainName ?? '').isNotEmpty) { + params['chainName'] = chainName; + } + if (chainId != null) { + params['chainId'] = chainId; + } + + return params; + } + + @override + String toString() => toJson().toString(); +} + +class CoinbaseResponseEvent implements EventArgs { + String? data; + CoinbaseResponseEvent({required this.data}); +} diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart b/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart new file mode 100644 index 0000000..39468ab --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart @@ -0,0 +1,550 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/coinbase_service.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/native_app_data.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/redirect.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/request_params.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/wc_sample_wallets.dart'; +import 'package:reown_appkit/modal/services/uri_service/url_utils_singleton.dart'; +import 'package:reown_appkit/modal/utils/core_utils.dart'; +import 'package:reown_appkit/modal/utils/debouncer.dart'; +import 'package:reown_appkit/modal/utils/platform_utils.dart'; +import 'package:reown_appkit/modal/services/explorer_service/i_explorer_service.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/api_response.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +const int _defaultEntriesCount = 48; + +class ExplorerService implements IExplorerService { + final http.Client _client; + final String _referer; + + late RequestParams _requestParams; + late final IReownCore _core; + + @override + String get projectId => _core.projectId; + + @override + ValueNotifier initialized = ValueNotifier(false); + + @override + ValueNotifier totalListings = ValueNotifier(0); + + List _listings = []; + @override + ValueNotifier> listings = ValueNotifier([]); + + final _debouncer = Debouncer(milliseconds: 300); + + String? _currentSearchValue; + @override + String get searchValue => _currentSearchValue ?? ''; + + @override + ValueNotifier isSearching = ValueNotifier(false); + + @override + Set? includedWalletIds; + String? get _includedWalletsParam { + final includedIds = (includedWalletIds ?? {}); + return includedIds.isNotEmpty ? includedIds.join(',') : null; + } + + @override + Set? excludedWalletIds; + String? get _excludedWalletsParam { + final excludedIds = (excludedWalletIds ?? {}) + ..addAll(_installedWalletIds) + ..addAll(featuredWalletIds ?? {}); + return excludedIds.isNotEmpty ? excludedIds.join(',') : null; + } + + @override + Set? featuredWalletIds; + String? get _featuredWalletsParam { + final featuredIds = Set.from(featuredWalletIds ?? {}); + featuredIds.removeWhere((e) => _installedWalletIds.contains(e)); + return featuredIds.isNotEmpty ? featuredIds.join(',') : null; + } + + Set _installedWalletIds = {}; + String? get _installedWalletsParam { + return _installedWalletIds.isNotEmpty + ? _installedWalletIds.join(',') + : null; + } + + int _currentWalletsCount = 0; + bool _canPaginate = true; + @override + bool get canPaginate => _canPaginate; + + ExplorerService({ + required IReownCore core, + required String referer, + this.featuredWalletIds, + this.includedWalletIds, + this.excludedWalletIds, + }) : _core = core, + _referer = referer, + _client = http.Client(); + + @override + Future init() async { + if (initialized.value) { + return; + } + + // TODO ideally we should call this at every opening to be able to detect newly installed wallets. + final nativeData = await _fetchNativeAppData(); + final installed = await nativeData.getInstalledApps(); + _installedWalletIds = Set.from(installed.map((e) => e.id)); + + await _fetchInitialWallets(); + + initialized.value = true; + } + + Future _fetchInitialWallets() async { + totalListings.value = 0; + final allListings = await Future.wait([ + _loadWCSampleWallets(), + _fetchInstalledListings(), + _fetchFeaturedListings(), + _fetchOtherListings(), + ]); + + _listings = [ + ...allListings[0], + ...allListings[1].sortByFeaturedIds(featuredWalletIds), + ...allListings[2].sortByFeaturedIds(featuredWalletIds), + ...allListings[3].sortByFeaturedIds(featuredWalletIds), + ]; + listings.value = _listings; + + if (_listings.length < _defaultEntriesCount) { + _canPaginate = false; + } + + await _getRecentWalletAndOrder(); + } + + Future> _loadWCSampleWallets() async { + // final platform = platformUtils.instance.getPlatformExact().name; + // final platformName = platform.toString().toLowerCase(); + List sampleWallets = []; + for (var sampleWallet in WCSampleWallets.getSampleWallets()) { + // final data = WCSampleWallets.nativeData[sampleWallet.listing.id]; + // final schema = (data?[platformName]! as NativeAppData).schema ?? ''; + final schema = sampleWallet.listing.mobileLink; + final installed = await uriService.instance.isInstalled(schema); + if (installed) { + sampleWallet = sampleWallet.copyWith(installed: true); + sampleWallets.add(sampleWallet); + } + } + _core.logger.d('[$runtimeType] sample wallets: ${sampleWallets.length}'); + return sampleWallets; + } + + Future _getRecentWalletAndOrder() async { + AppKitModalWalletInfo? walletInfo; + if (_core.storage.has(StorageConstants.connectedWalletData)) { + final walletData = _core.storage.get( + StorageConstants.connectedWalletData, + ); + if (walletData != null) { + walletInfo = AppKitModalWalletInfo.fromJson(walletData); + if (!walletInfo.installed) { + walletInfo = null; + } + } + } + + if (_core.storage.has(StorageConstants.recentWalletId)) { + final storedWalletId = _core.storage.get(StorageConstants.recentWalletId); + final walletId = storedWalletId?['walletId']; + await _updateRecentWalletId(walletInfo, walletId: walletId); + } + } + + @override + Future paginate() async { + if (!canPaginate) return; + _requestParams = _requestParams.nextPage(); + final newListings = await _fetchListings( + params: _requestParams, + updateCount: false, + ); + _listings = [..._listings, ...newListings]; + listings.value = _listings; + if (newListings.length < _currentWalletsCount) { + _canPaginate = false; + } else { + _currentWalletsCount = newListings.length; + } + } + + Future> _fetchNativeAppData() async { + final headers = CoreUtils.getAPIHeaders( + _core.projectId, + _referer, + ); + final uri = Platform.isIOS + ? Uri.parse('${UrlConstants.apiService}/getIosData') + : Uri.parse('${UrlConstants.apiService}/getAndroidData'); + try { + final response = await _client.get(uri, headers: headers); + if (response.statusCode == 200 || response.statusCode == 202) { + final apiResponse = ApiResponse.fromJson( + jsonDecode(response.body), + (json) => NativeAppData.fromJson(json), + ); + return apiResponse.data.toList(); + } else { + _core.logger.d( + '⛔ [$runtimeType] error fetching native data ${response.request?.url}', + error: response.statusCode, + ); + return []; + } + } catch (e) { + _core.logger.e( + '[$runtimeType] error fetching native data $uri', + error: e, + ); + return []; + } + } + + Future> _fetchInstalledListings() async { + final pType = PlatformUtils.getPlatformType(); + if (pType != PlatformType.mobile) { + return []; + } + if (_installedWalletIds.isEmpty) { + return []; + } + + // I query with include set as my installed wallets + final params = RequestParams( + page: 1, + entries: _installedWalletIds.length, + include: _installedWalletsParam, + platform: _getPlatformType(), + ); + // this query gives me a count of installedWalletsParam.length + final installedWallets = await _fetchListings(params: params); + _core.logger.d( + '[$runtimeType] installed wallets: ${installedWallets.length}', + ); + return installedWallets.setInstalledFlag(); + } + + Future> _fetchFeaturedListings() async { + if ((_featuredWalletsParam ?? '').isEmpty) { + return []; + } + final params = RequestParams( + page: 1, + entries: _featuredWalletsParam!.split(',').length, + include: _featuredWalletsParam, + platform: _getPlatformType(), + ); + return await _fetchListings(params: params); + } + + Future> _fetchOtherListings() async { + _requestParams = RequestParams( + page: 1, + entries: _defaultEntriesCount, + include: _includedWalletsParam, + exclude: _excludedWalletsParam, + platform: _getPlatformType(), + ); + return await _fetchListings(params: _requestParams); + } + + Future> _fetchListings({ + RequestParams? params, + bool updateCount = true, + }) async { + final queryParams = params?.toJson() ?? {}; + final headers = CoreUtils.getAPIHeaders( + _core.projectId, + _referer, + ); + final uri = Uri.parse('${UrlConstants.apiService}/getWallets').replace( + queryParameters: queryParams, + ); + _core.logger.d('[$runtimeType] fetching $uri'); + try { + final response = await _client.get(uri, headers: headers); + if (response.statusCode == 200 || response.statusCode == 202) { + final apiResponse = ApiResponse.fromJson( + jsonDecode(response.body), + (json) => Listing.fromJson(json), + ); + if (updateCount) { + totalListings.value += apiResponse.count; + } + return apiResponse.data.toList().toAppKitWalletInfo(); + } else { + _core.logger.d( + '⛔ [$runtimeType] error fetching listings (${response.statusCode}) ${response.request?.url}\n' + 'headers: $headers\n' + 'queryParams $queryParams', + ); + return []; + } + } catch (e) { + _core.logger.d( + '[$runtimeType] error fetching listings: $uri', + error: e, + ); + return []; + } + } + + @override + Future storeConnectedWallet(AppKitModalWalletInfo? walletInfo) async { + if (walletInfo == null) return; + await _core.storage.set( + StorageConstants.connectedWalletData, + walletInfo.toJson(), + ); + await _updateRecentWalletId(walletInfo, walletId: walletInfo.listing.id); + } + + @override + Future storeRecentWalletId(String? walletId) async { + if (walletId == null) return; + await _core.storage.set( + StorageConstants.recentWalletId, + {'walletId': walletId}, + ); + } + + @override + AppKitModalWalletInfo? getConnectedWallet() { + try { + if (_core.storage.has(StorageConstants.connectedWalletData)) { + final walletData = _core.storage.get( + StorageConstants.connectedWalletData, + ); + if (walletData != null) { + return AppKitModalWalletInfo.fromJson(walletData); + } + } + } catch (e, s) { + _core.logger.e( + '[$runtimeType] error getConnectedWallet:', + error: e, + stackTrace: s, + ); + } + return null; + } + + Future _updateRecentWalletId( + AppKitModalWalletInfo? walletInfo, { + String? walletId, + }) async { + try { + final recentId = walletInfo?.listing.id ?? walletId; + await storeRecentWalletId(recentId); + + final currentListings = List.from( + _listings.map((e) => e.copyWith(recent: false)).toList(), + ); + final recentWallet = currentListings.firstWhereOrNull( + (e) => e.listing.id == recentId, + ); + if (recentWallet != null) { + final rw = recentWallet.copyWith(recent: true); + currentListings.removeWhere((e) => e.listing.id == rw.listing.id); + currentListings.insert(0, rw); + } + _listings = currentListings; + listings.value = _listings; + } catch (e) { + _core.logger.e('[$runtimeType] updating recent wallet: $e'); + } + } + + @override + void search({String? query}) async { + if (query == null || query.isEmpty) { + _currentSearchValue = null; + listings.value = _listings; + return; + } + + final q = query.toLowerCase(); + await _searchListings(query: q); + } + + Future _searchListings({String? query}) async { + isSearching.value = true; + + final includedIds = (includedWalletIds ?? {}); + final include = includedIds.isNotEmpty ? includedIds.join(',') : null; + final excludedIds = (excludedWalletIds ?? {}); + final exclude = excludedIds.isNotEmpty ? excludedIds.join(',') : null; + + _core.logger.d('[$runtimeType] search $query'); + _currentSearchValue = query; + final newListins = await _fetchListings( + params: RequestParams( + page: 1, + entries: 100, + search: _currentSearchValue, + include: include, + exclude: exclude, + platform: _getPlatformType(), + ), + updateCount: false, + ); + + listings.value = newListins; + _debouncer.run(() => isSearching.value = false); + _core.logger.d('[$runtimeType] _searchListings $query'); + } + + @override + Future getCoinbaseWalletObject() async { + final results = await _fetchListings( + params: RequestParams( + page: 1, + entries: 1, + search: 'coinbase wallet', + // platform: _getPlatformType(), + ), + updateCount: false, + ); + + if (results.isNotEmpty) { + final wallet = AppKitModalWalletInfo.fromJson(results.first.toJson()); + final mobileLink = CoinbaseService.defaultWalletData.listing.mobileLink; + bool installed = await uriService.instance.isInstalled(mobileLink); + return wallet.copyWith( + listing: wallet.listing.copyWith(mobileLink: mobileLink), + installed: installed, + ); + } + return null; + } + + @override + String getWalletImageUrl(String imageId) { + if (imageId.isEmpty) { + return ''; + } + if (imageId.startsWith('http')) { + return imageId; + } + return '${UrlConstants.apiService}/getWalletImage/$imageId'; + } + + @override + String getAssetImageUrl(String imageId) { + if (imageId.isEmpty) { + return ''; + } + if (imageId.startsWith('http')) { + return imageId; + } + return '${UrlConstants.apiService}/public/getAssetImage/$imageId'; + } + + @override + WalletRedirect? getWalletRedirect(AppKitModalWalletInfo? walletInfo) { + if (walletInfo == null) return null; + if (walletInfo.listing.id == CoinbaseService.defaultWalletData.listing.id) { + return WalletRedirect( + mobile: CoinbaseService.defaultWalletData.listing.mobileLink, + desktop: null, + web: null, + ); + } + return WalletRedirect( + mobile: walletInfo.listing.mobileLink?.trim(), + desktop: walletInfo.listing.desktopLink, + web: walletInfo.listing.webappLink, + ); + } + + String _getPlatformType() { + final type = PlatformUtils.getPlatformType(); + final platform = type.toString().toLowerCase(); + switch (type) { + case PlatformType.mobile: + if (Platform.isIOS) { + return 'ios'; + } else if (Platform.isAndroid) { + return 'android'; + } else { + return 'mobile'; + } + default: + return platform; + } + } +} + +extension on List { + List toAppKitWalletInfo() { + return map( + (item) => AppKitModalWalletInfo( + listing: item, + installed: false, + recent: false, + ), + ).toList(); + } +} + +extension on List { + List sortByFeaturedIds( + Set? featuredWalletIds) { + Map sortedMap = {}; + final auxList = List.from(this); + + for (var id in featuredWalletIds ?? {}) { + final featured = auxList.firstWhereOrNull((e) => e.listing.id == id); + if (featured != null) { + auxList.removeWhere((e) => e.listing.id == id); + sortedMap[id] = featured; + } + } + + return [...sortedMap.values, ...auxList]; + } + + List setInstalledFlag() { + return map((e) => e.copyWith(installed: true)).toList(); + } +} + +extension on List { + Future> getInstalledApps() async { + final installedApps = []; + for (var appData in this) { + bool installed = await uriService.instance.isInstalled( + appData.schema, + id: appData.id, + ); + if (installed) { + installedApps.add(appData); + } + } + return installedApps; + } +} diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service_singleton.dart b/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service_singleton.dart new file mode 100644 index 0000000..cbb5c09 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service_singleton.dart @@ -0,0 +1,7 @@ +import 'package:reown_appkit/modal/services/explorer_service/i_explorer_service.dart'; + +class ExplorerServiceSingleton { + late IExplorerService instance; +} + +final explorerService = ExplorerServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/i_explorer_service.dart b/packages/reown_appkit/lib/modal/services/explorer_service/i_explorer_service.dart new file mode 100644 index 0000000..a777e63 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/explorer_service/i_explorer_service.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/redirect.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +abstract class IExplorerService { + /// The project ID used when querying the explorer API. + String get projectId; + + ValueNotifier initialized = ValueNotifier(false); + + ValueNotifier totalListings = ValueNotifier(0); + + ValueNotifier> listings = ValueNotifier([]); + + /// If featuredWalletIds is set wallets from this list are going to be prioritized in the results + Set? featuredWalletIds; + + /// If includedWalletIds is set only wallets from this list are going to be shown + Set? includedWalletIds; + + /// If excludedWalletIds is set wallets from this list are going to be excluded + Set? excludedWalletIds; + + /// Init service + Future init(); + + /// paginate subsequent wallets + Future paginate(); + + bool get canPaginate; + + /// search for a wallet + void search({String? query}); + + ValueNotifier isSearching = ValueNotifier(false); + + String get searchValue; + + /// update the recently used position to the top list + Future storeConnectedWallet(AppKitModalWalletInfo? walletInfo); + + Future storeRecentWalletId(String? walletId); + + /// Get connected wallet data from local storage + AppKitModalWalletInfo? getConnectedWallet(); + + /// Gets the WalletRedirect object from a wallet info data + WalletRedirect? getWalletRedirect(AppKitModalWalletInfo? walletInfo); + + /// Given an imageId it return the wallet app icon from our services + String getWalletImageUrl(String imageId); + + /// Given an imageId it return the chain icon from our services + String getAssetImageUrl(String imageId); + + Future getCoinbaseWalletObject(); +} diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/models/api_response.dart b/packages/reown_appkit/lib/modal/services/explorer_service/models/api_response.dart new file mode 100644 index 0000000..4e227a4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/explorer_service/models/api_response.dart @@ -0,0 +1,39 @@ +import 'package:reown_appkit/modal/models/public/appkit_wallet_info.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/native_app_data.dart'; + +class ApiResponse { + final int count; + final List data; + + ApiResponse({required this.count, required this.data}); + + ApiResponse copyWith({int? count, List? data}) => ApiResponse( + count: count ?? this.count, + data: data ?? this.data, + ); + + factory ApiResponse.fromJson( + final Map json, + T Function(Object? json) fromJsonT, + ) { + return ApiResponse( + count: json['count'], + data: (json['data'] as List).map(fromJsonT).toList(), + ); + } + + Map toJson() => { + 'count': count, + 'data': List.from(data.map( + (x) { + if (T is Listing) { + return (x as Listing).toJson(); + } else if (T is NativeAppData) { + return (x as NativeAppData).toJson(); + } else { + throw Exception('Invalid Type'); + } + }, + )), + }; +} diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/models/native_app_data.dart b/packages/reown_appkit/lib/modal/services/explorer_service/models/native_app_data.dart new file mode 100644 index 0000000..87ef9bc --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/explorer_service/models/native_app_data.dart @@ -0,0 +1,31 @@ +import 'dart:convert'; + +class NativeAppData { + final String id; + final String? schema; + + NativeAppData({required this.id, this.schema}); + + NativeAppData copyWith({String? id, String? schema}) => NativeAppData( + id: id ?? this.id, + schema: schema ?? this.schema, + ); + + factory NativeAppData.fromRawJson(String str) => + NativeAppData.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory NativeAppData.fromJson(Object? json) { + final j = json as Map? ?? {}; + return NativeAppData( + id: j['id'], + schema: (j['ios_schema'] ?? j['android_app_id'])?.toString().trim() ?? '', + ); + } + + Map toJson() => { + 'id': id, + 'schema': schema, + }; +} diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/models/redirect.dart b/packages/reown_appkit/lib/modal/services/explorer_service/models/redirect.dart new file mode 100644 index 0000000..b54c3c7 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/explorer_service/models/redirect.dart @@ -0,0 +1,35 @@ +class WalletRedirect { + String? mobile; + String? desktop; + String? web; + + WalletRedirect({ + this.mobile, + this.desktop, + this.web, + }); + + bool get mobileOnly => desktop == null && web == null; + bool get webOnly => desktop == null && mobile == null; + bool get desktopOnly => mobile == null && web == null; + + Uri? get mobileUri => mobile != null ? Uri.parse(mobile!) : null; + Uri? get webUri => web != null ? Uri.parse(web!) : null; + Uri? get desktopUri => desktop != null ? Uri.parse(desktop!) : null; + + WalletRedirect copyWith({ + String? mobile, + String? desktop, + String? web, + }) { + return WalletRedirect( + mobile: mobile ?? this.mobile, + desktop: desktop ?? this.desktop, + web: web ?? this.web, + ); + } + + @override + String toString() => + 'mobile: $mobile (mobileOnly: $mobileOnly), desktop: $desktop (desktopOnly: $desktopOnly), web: $web (webOnly: $webOnly)'; +} diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/models/request_params.dart b/packages/reown_appkit/lib/modal/services/explorer_service/models/request_params.dart new file mode 100644 index 0000000..a495855 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/explorer_service/models/request_params.dart @@ -0,0 +1,64 @@ +class RequestParams { + final int page; // eg. 1 + final int entries; // eg. 100 + final String? search; // eg. MetaMa... + final String? include; // eg. id1,id2,id3 + final String? exclude; // eg. id1,id2,id3 + final String? platform; // 'ios' | 'android' + + const RequestParams({ + required this.page, + required this.entries, + this.search, + this.include, + this.exclude, + this.platform, + }); + + Map toJson({bool short = false}) { + Map params = { + 'page': page.toString(), + 'entries': entries.toString(), + }; + if ((search ?? '').isNotEmpty) { + params['search'] = search; + } + if ((include ?? '').isNotEmpty && !short) { + params['include'] = include; + } + if ((exclude ?? '').isNotEmpty && !short) { + params['exclude'] = exclude; + } + if ((platform ?? '').isNotEmpty) { + params['platform'] = platform; + } + + return params; + } + + RequestParams copyWith({ + int? page, + int? entries, + String? search, + String? include, + String? exclude, + String? platform, + }) => + RequestParams( + page: page ?? this.page, + entries: entries ?? this.entries, + search: search ?? this.search, + include: include ?? this.include, + exclude: exclude ?? this.exclude, + platform: platform ?? this.platform, + ); + + RequestParams nextPage() => RequestParams( + page: page + 1, + entries: entries, + search: search, + include: include, + exclude: exclude, + platform: platform, + ); +} diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/models/wc_sample_wallets.dart b/packages/reown_appkit/lib/modal/services/explorer_service/models/wc_sample_wallets.dart new file mode 100644 index 0000000..8ebe403 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/explorer_service/models/wc_sample_wallets.dart @@ -0,0 +1,107 @@ +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:reown_appkit/modal/models/public/appkit_wallet_info.dart'; + +class WCSampleWallets { + static List> sampleWalletsInternal() => [ + { + 'name': 'Swift Wallet', + 'platform': ['ios'], + 'id': '123456789012345678901234567890', + 'schema': 'walletapp://', + 'bundleId': 'com.walletconnect.sample.wallet', + 'universal': 'https://lab.web3modal.com/wallet', + }, + { + 'name': 'Flutter Wallet (internal)', + 'platform': ['ios', 'android'], + 'id': '123456789012345678901234567895', + 'schema': 'wcflutterwallet-internal://', + 'bundleId': 'com.walletconnect.flutterwallet.internal', + 'universal': + 'https://dev.lab.web3modal.com/flutter_walletkit_internal', + }, + { + 'name': 'RN Wallet (internal)', + 'platform': ['ios', 'android'], + 'id': '1234567890123456789012345678922', + 'schema': 'rn-web3wallet://wc', + 'bundleId': 'com.walletconnect.web3wallet.rnsample.internal', + 'universal': 'https://lab.web3modal.com/rn_walletkit', + }, + { + 'name': 'Kotlin Wallet (Internal)', + 'platform': ['android'], + 'id': '123456789012345678901234567894', + 'schema': 'kotlin-web3wallet://wc', + 'bundleId': 'com.walletconnect.sample.wallet.internal', + 'universal': + 'https://web3modal-laboratory-git-chore-kotlin-assetlinks-walletconnect1.vercel.app/wallet_internal', + }, + ]; + + static List> sampleWalletsProduction() => [ + { + 'name': 'Swift Wallet', + 'platform': ['ios'], + 'id': '123456789012345678901234567890', + 'schema': 'walletapp://', + 'bundleId': 'com.walletconnect.sample.wallet', + 'universal': 'https://lab.web3modal.com/wallet', + }, + { + 'name': 'Flutter Wallet', + 'platform': ['ios', 'android'], + 'id': '123456789012345678901234567891', + 'schema': 'wcflutterwallet://', + 'bundleId': 'com.walletconnect.flutterwallet', + 'universal': 'https://lab.web3modal.com/flutter_walletkit', + }, + { + 'name': 'RN Wallet', + 'platform': ['ios', 'android'], + 'id': '123456789012345678901234567892', + 'schema': 'rn-web3wallet://wc', + 'bundleId': 'com.walletconnect.web3wallet.rnsample', + 'universal': 'https://lab.web3modal.com/rn_walletkit', + }, + { + 'name': 'Kotlin Wallet', + 'platform': ['android'], + 'id': '123456789012345678901234567893', + 'schema': 'kotlin-web3wallet://wc', + 'bundleId': 'com.walletconnect.sample.wallet', + 'universal': + 'https://web3modal-laboratory-git-chore-kotlin-assetlinks-walletconnect1.vercel.app/wallet_release', + }, + ]; + + static List getSampleWallets() { + final wallets = _getSampleWallets().mapIndexed((index, entry) { + return AppKitModalWalletInfo( + listing: Listing.fromJson({ + 'id': index, + 'name': entry['name'] as String?, + 'homepage': 'https://reown.com', + 'image_id': + 'https://thegraph.academy/wp-content/uploads/2021/04/WalletConnect-logo.png', + 'order': 10, + 'mobile_link': entry['schema'] as String?, + 'link_mode': entry['universal'] as String?, + }), + installed: false, + recent: false, + ); + }).toList(); + return wallets.whereType().toList(); + } + + static List> _getSampleWallets() { + return sampleWalletsInternal().where((e) { + return (e['platform'] as List).contains( + Platform.operatingSystem, + ); + }).toList(); + } +} diff --git a/packages/reown_appkit/lib/modal/services/magic_service/i_magic_service.dart b/packages/reown_appkit/lib/modal/services/magic_service/i_magic_service.dart new file mode 100644 index 0000000..71f9ea6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/magic_service/i_magic_service.dart @@ -0,0 +1,34 @@ +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +abstract class IMagicService { + ConnectionMetadata get metadata; + + Future init(); + + void setEmail(String value); + void setNewEmail(String value); + + // ****** W3mFrameProvider public methods ******* // + Future connectEmail({required String value}); + Future updateEmail({required String value}); + Future updateEmailPrimaryOtp({required String otp}); + Future updateEmailSecondaryOtp({required String otp}); + Future connectOtp({required String otp}); + Future getChainId(); + Future syncTheme(AppKitModalTheme? theme); + Future getUser({String? chainId}); + Future switchNetwork({required String chainId}); + Future request({ + String? chainId, + required SessionRequestParams request, + }); + Future disconnect(); + + abstract final Event onMagicLoginRequest; + abstract final Event onMagicLoginSuccess; + abstract final Event onMagicConnect; + abstract final Event onMagicUpdate; + abstract final Event onMagicError; + abstract final Event onMagicRpcRequest; +} diff --git a/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart b/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart new file mode 100644 index 0000000..6c0d024 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart @@ -0,0 +1,670 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_data.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/frame_message.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; + +class MagicService implements IMagicService { + static const _safeDomains = [ + 'auth.magic.link', + 'launchdarkly.com', + ]; + static const supportedMethods = [ + 'personal_sign', + 'eth_sign', + 'eth_sendTransaction', + 'eth_signTypedData_v4', + 'wallet_switchEthereumChain', + 'wallet_addEthereumChain', + ]; + static const defaultWalletData = AppKitModalWalletInfo( + listing: Listing( + id: '', + name: 'Email Wallet', + homepage: '', + imageId: '', + order: 10000, + ), + installed: false, + recent: false, + ); + + @override + ConnectionMetadata get metadata => ConnectionMetadata( + metadata: PairingMetadata( + name: defaultWalletData.listing.name, + description: '', + url: defaultWalletData.listing.homepage, + icons: [''], + ), + publicKey: '', + ); + + // + // final IReownAppKit _appKit; + Timer? _timeOutTimer; + String? _connectionChainId; + int _onLoadCount = 0; + String _packageName = ''; + + late final WebViewController _webViewController; + WebViewController get controller => _webViewController; + + late final WebViewWidget _webview; + WebViewWidget get webview => _webview; + + late Completer _initialized; + late Completer _connected; + late Completer _response; + late Completer _disconnect; + + @override + Event onMagicLoginRequest = Event(); + + @override + Event onMagicLoginSuccess = Event(); + + @override + Event onMagicConnect = Event(); + + @override + Event onMagicError = Event(); + + @override + Event onMagicUpdate = Event(); + + @override + Event onMagicRpcRequest = Event(); + + final isEnabled = ValueNotifier(false); + final isReady = ValueNotifier(false); + final isConnected = ValueNotifier(false); + final isTimeout = ValueNotifier(false); + + final email = ValueNotifier(''); + final newEmail = ValueNotifier(''); + final step = ValueNotifier(EmailLoginStep.idle); + + late final IReownCore _core; + late final PairingMetadata _metadata; + + MagicService({ + // required IReownAppKit appKit, + required IReownCore core, + required PairingMetadata metadata, + bool enabled = false, + }) : _core = core, + _metadata = metadata { + isEnabled.value = enabled; + if (isEnabled.value) { + _webViewController = WebViewController(); + _webview = WebViewWidget(controller: _webViewController); + isReady.addListener(_readyListener); + } + } + + final _awaitReadyness = Completer(); + void _readyListener() { + if (isReady.value && !_awaitReadyness.isCompleted) { + _awaitReadyness.complete(true); + } + } + + @override + Future init() async { + if (!isEnabled.value) { + _initialized = Completer(); + _initialized.complete(false); + _connected = Completer(); + _connected.complete(false); + return; + } + _packageName = await ReownCoreUtils.getPackageName(); + await _init(); + await _initialized.future; + await _isConnected(); + await _connected.future; + isReady.value = true; + _syncDappData(); + return; + } + + Future _init() async { + _initialized = Completer(); + + await _webViewController.setBackgroundColor(Colors.transparent); + await _webViewController.setJavaScriptMode(JavaScriptMode.unrestricted); + await _webViewController.addJavaScriptChannel( + 'w3mWebview', + onMessageReceived: _onFrameMessage, + ); + await _webViewController.setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (NavigationRequest request) { + if (_isAllowedDomain(request.url)) { + return NavigationDecision.navigate; + } + if (isReady.value) { + launchUrlString( + request.url, + mode: LaunchMode.externalApplication, + ); + } + return NavigationDecision.prevent; + }, + onWebResourceError: _onWebResourceError, + onPageFinished: (String url) async { + _onLoadCount++; + // If bundleId/packageName is whitelisted in cloud then for some reason it enters here twice + // Like as if secure-mobile.walletconnect.com is loaded twice + // If bundleId/packageName is NOT whitelisted in cloud then it enter just once. + // This is happening only on Android devices, on iOS only once execution is done no matter what. + if (_onLoadCount < 2 && Platform.isAndroid) return; + await _runJavascript(); + Future.delayed(Duration(milliseconds: 600)).then((value) { + if (_initialized.isCompleted) return; + _initialized.complete(true); + }); + }, + ), + ); + await _setDebugMode(); + await _loadRequest(); + } + + @override + void setEmail(String value) { + email.value = value; + } + + @override + void setNewEmail(String value) { + newEmail.value = value; + } + + // ****** W3mFrameProvider public methods ******* // + + @override + Future connectEmail({required String value, String? chainId}) async { + if (!isEnabled.value || !isReady.value) return; + _connectionChainId = chainId ?? _connectionChainId; + final message = ConnectEmail(email: value).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future updateEmail({required String value}) async { + if (!isEnabled.value || !isReady.value) return; + step.value = EmailLoginStep.loading; + final message = UpdateEmail(email: value).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future updateEmailPrimaryOtp({required String otp}) async { + if (!isEnabled.value || !isReady.value) return; + step.value = EmailLoginStep.loading; + final message = UpdateEmailPrimaryOtp(otp: otp).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future updateEmailSecondaryOtp({required String otp}) async { + if (!isEnabled.value || !isReady.value) return; + step.value = EmailLoginStep.loading; + final message = UpdateEmailSecondaryOtp(otp: otp).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future connectOtp({required String otp}) async { + if (!isEnabled.value || !isReady.value) return; + step.value = EmailLoginStep.loading; + final message = ConnectOtp(otp: otp).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future getChainId() async { + if (!isEnabled.value || !isReady.value) return; + final message = GetChainId().toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future syncTheme(AppKitModalTheme? theme) async { + if (!isEnabled.value || !isReady.value) return; + final message = SyncTheme(theme: theme).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + void _syncDappData() async { + if (!isEnabled.value || !isReady.value) return; + final message = SyncAppData( + metadata: _metadata, + projectId: _core.projectId, + sdkVersion: 'flutter-${CoreConstants.X_SDK_VERSION}', + ).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future getUser({String? chainId}) async { + if (!isEnabled.value || !isReady.value) return; + return await _getUser(chainId); + } + + Future _getUser(String? chainId) async { + final message = GetUser(chainId: chainId).toString(); + return await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future switchNetwork({required String chainId}) async { + if (!isEnabled.value || !isReady.value) return; + final message = SwitchNetwork(chainId: chainId).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future request({ + String? chainId, + required SessionRequestParams request, + }) async { + if (!isEnabled.value) return; + await _awaitReadyness.future; + await _rpcRequest(request.toJson()); + return await _response.future; + } + + Future _rpcRequest(Map parameters) async { + _response = Completer(); + if (!isConnected.value) { + onMagicLoginRequest.broadcast(MagicSessionEvent(email: email.value)); + _connected = Completer(); + await connectEmail(value: email.value); + final success = await _connected.future; + if (!success) return; + } + onMagicRpcRequest.broadcast(MagicRequestEvent(request: parameters)); + final method = parameters['method']; + final params = parameters['params'] as List; + final message = RpcRequest(method: method, params: params).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + @override + Future disconnect() async { + if (!isEnabled.value || !isReady.value) return false; + _disconnect = Completer(); + if (!isConnected.value) { + _resetTimeOut(); + _disconnect.complete(true); + return (await _disconnect.future); + } + final message = SignOut().toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return (await _disconnect.future); + } + + // ****** Private Methods ******* // + + Future _loadRequest() async { + try { + final headers = { + // secure-site's middleware requires a referer otherwise it throws `400: Missing projectId or referer` + 'referer': _metadata.url, + 'x-bundle-id': _packageName, + }; + final uri = Uri.parse(UrlConstants.secureService); + final queryParams = { + 'projectId': _core.projectId, + 'bundleId': _packageName, + }; + await _webViewController.loadRequest( + uri.replace(queryParameters: queryParams), + headers: headers, + ); + // in case connection message or even the request itself hangs there's no other way to continue the flow than timing it out. + _timeOutTimer ??= Timer.periodic(Duration(seconds: 1), _timeOut); + } catch (e) { + _initialized.complete(false); + } + } + + Future _isConnected() async { + _connected = Completer(); + final message = IsConnected().toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + + void _onFrameMessage(JavaScriptMessage jsMessage) async { + if (Platform.isAndroid) { + _core.logger.d('[$runtimeType] jsMessage ${jsMessage.message}'); + } + try { + final frameMessage = jsMessage.toFrameMessage(); + if (!frameMessage.isValidOrigin || !frameMessage.isValidData) { + return; + } + final messageData = frameMessage.data!; + if (messageData.syncDataSuccess) { + _resetTimeOut(); + } + // ****** IS_CONNECTED + if (messageData.isConnectSuccess) { + _resetTimeOut(); + isConnected.value = messageData.getPayloadMapKey('isConnected'); + if (!_connected.isCompleted) { + _connected.complete(isConnected.value); + } + onMagicConnect.broadcast(MagicConnectEvent(isConnected.value)); + if (isConnected.value) { + await _getUser(_connectionChainId); + } + } + // ****** CONNECT_EMAIL + if (messageData.connectEmailSuccess) { + if (step.value != EmailLoginStep.loading) { + final action = messageData.getPayloadMapKey('action'); + final value = action.toString().toUpperCase(); + final newStep = EmailLoginStep.fromAction(value); + if (newStep == EmailLoginStep.verifyOtp) { + if (step.value == EmailLoginStep.verifyDevice) { + analyticsService.instance.sendEvent(DeviceRegisteredForEmail()); + } + analyticsService.instance.sendEvent(EmailVerificationCodeSent()); + } + step.value = newStep; + } + } + // ****** CONNECT_OTP + if (messageData.connectOtpSuccess) { + analyticsService.instance.sendEvent(EmailVerificationCodePass()); + step.value = EmailLoginStep.idle; + await _getUser(_connectionChainId); + } + // ****** UPDAET_EMAIL + if (messageData.updateEmailSuccess) { + final action = messageData.getPayloadMapKey('action'); + if (action == 'VERIFY_SECONDARY_OTP') { + step.value = EmailLoginStep.verifyOtp2; + } else { + step.value = EmailLoginStep.verifyOtp; + } + analyticsService.instance.sendEvent(EmailEdit()); + } + // ****** UPDATE_EMAIL_PRIMARY_OTP + if (messageData.updateEmailPrimarySuccess) { + step.value = EmailLoginStep.verifyOtp2; + } + // ****** UPDATE_EMAIL_SECONDARY_OTP + if (messageData.updateEmailSecondarySuccess) { + analyticsService.instance.sendEvent(EmailEditComplete()); + step.value = EmailLoginStep.idle; + setEmail(newEmail.value); + setNewEmail(''); + await _getUser(_connectionChainId); + } + // ****** SWITCH_NETWORK + if (messageData.switchNetworkSuccess) { + final chainId = messageData.getPayloadMapKey('chainId'); + onMagicUpdate.broadcast(MagicSessionEvent(chainId: chainId)); + } + // ****** GET_CHAIN_ID + if (messageData.getChainIdSuccess) { + final chainId = messageData.getPayloadMapKey('chainId'); + onMagicUpdate.broadcast(MagicSessionEvent(chainId: chainId)); + _connectionChainId = chainId?.toString(); + } + // ****** RPC_REQUEST + if (messageData.rpcRequestSuccess) { + final hash = messageData.payload as String?; + _response.complete(hash); + onMagicRpcRequest.broadcast( + MagicRequestEvent( + request: null, + result: hash, + success: true, + ), + ); + } + // ****** GET_USER + if (messageData.getUserSuccess) { + isConnected.value = true; + final data = MagicData.fromJson(messageData.payload!); + if (!_connected.isCompleted) { + final event = MagicSessionEvent( + email: data.email, + address: data.address, + chainId: data.chainId, + ); + onMagicUpdate.broadcast(event); + _connected.complete(isConnected.value); + } else { + final session = data.copytWith( + peer: metadata, + self: ConnectionMetadata( + metadata: _metadata, + publicKey: '', + ), + ); + onMagicLoginSuccess.broadcast(MagicLoginEvent(session)); + } + } + // ****** SIGN_OUT + if (messageData.signOutSuccess) { + _resetTimeOut(); + _disconnect.complete(true); + } + // ****** SESSION_UPDATE + if (messageData.sessionUpdate) { + // onMagicUpdate.broadcast(MagicSessionEvent(...)); + } + if (messageData.isConnectError) { + _error(IsConnectedErrorEvent()); + } + if (messageData.connectEmailError) { + String? message = messageData.payload?['message']?.toString(); + if (message?.toLowerCase() == 'invalid params') { + message = 'Wrong email format'; + } + _error(ConnectEmailErrorEvent(message: message)); + } + if (messageData.updateEmailError) { + final message = messageData.payload?['message']?.toString(); + _error(UpdateEmailErrorEvent(message: message)); + } + if (messageData.updateEmailPrimaryOtpError) { + final message = messageData.payload?['message']?.toString(); + _error(UpdateEmailPrimaryOtpErrorEvent(message: message)); + } + if (messageData.updateEmailSecondaryOtpError) { + final message = messageData.payload?['message']?.toString(); + _error(UpdateEmailSecondaryOtpErrorEvent(message: message)); + } + if (messageData.connectOtpError) { + analyticsService.instance.sendEvent(EmailVerificationCodeFail()); + final message = messageData.payload?['message']?.toString(); + _error(ConnectOtpErrorEvent(message: message)); + } + if (messageData.getUserError) { + _error(GetUserErrorEvent()); + } + if (messageData.switchNetworkError) { + _error(SwitchNetworkErrorEvent()); + } + if (messageData.rpcRequestError) { + final message = messageData.getPayloadMapKey('message'); + _error(RpcRequestErrorEvent(message)); + } + if (messageData.signOutError) { + _error(SignOutErrorEvent()); + } + } catch (e, s) { + _core.logger.d('[$runtimeType] $jsMessage', stackTrace: s); + } + } + + void _error(MagicErrorEvent errorEvent) { + if (errorEvent is RpcRequestErrorEvent) { + _response.completeError(JsonRpcError(code: 0, message: errorEvent.error)); + onMagicRpcRequest.broadcast( + MagicRequestEvent( + request: null, + result: JsonRpcError(code: 0, message: errorEvent.error), + success: false, + ), + ); + return; + } + if (errorEvent is IsConnectedErrorEvent) { + isReady.value = false; + isConnected.value = false; + step.value = EmailLoginStep.idle; + } + if (errorEvent is ConnectEmailErrorEvent) { + isConnected.value = false; + step.value = EmailLoginStep.idle; + } + if (errorEvent is UpdateEmailErrorEvent) { + isConnected.value = false; + step.value = EmailLoginStep.verifyOtp; + } + if (errorEvent is UpdateEmailPrimaryOtpErrorEvent) { + step.value = EmailLoginStep.verifyOtp; + } + if (errorEvent is UpdateEmailSecondaryOtpErrorEvent) { + step.value = EmailLoginStep.verifyOtp2; + } + if (errorEvent is ConnectOtpErrorEvent) { + isConnected.value = false; + step.value = EmailLoginStep.verifyOtp; + } + if (errorEvent is SignOutErrorEvent) { + isConnected.value = true; + _disconnect.complete(false); + } + if (!_connected.isCompleted) { + _connected.complete(isConnected.value); + } + onMagicError.broadcast(errorEvent); + } + + Future _runJavascript() async { + return await _webViewController.runJavaScript(''' + const iframeFL = document.getElementById('frame-mobile-sdk') + + window.addEventListener('message', ({ data, origin }) => { + console.log('[MagicService] received <=== ' + JSON.stringify({data,origin})) + window.w3mWebview.postMessage(JSON.stringify({data,origin})) + }) + + const sendMessage = async (message) => { + console.log('[MagicService] posted =====> ' + JSON.stringify(message)) + iframeFL.contentWindow.postMessage(message, '*') + } + '''); + } + + // sendMessage({type:"@w3m-app/GET_SOCIAL_REDIRECT_URI",payload:{provider:"x"}}); + // sendMessage({type:"@w3m-app/CONNECT_SOCIAL",payload:{uri:"https://auth.magic.link/v1/oauth2/twitter/start?magic_api_key=pk_live_B080E9DC31E5875E&magic_challenge=9HbSG6KYL3r2b7LqzhD7-EcjoHLj-a7wt7npmSBR2fw&state=FfR0W7idPzp81HM2KE~zBPR7bbSQM97CL5zZZMHd_2_ZHZ~rLvnO63MO3fd6eB4LMymif9pQupdhVL11l4NsQk4D-zQDfPGB17PpiWjPobCemCZwP.HdkH4dQeSDgkiH&platform=web&redirect_uri=https%3A%2F%2Fsecure.walletconnect.com%2Fsdk%2Foauth%3FprojectId%3Dcad4956f31a5e40a00b62865b030c6f8"}}); + + void _onDebugConsoleReceived(JavaScriptConsoleMessage message) { + _core.logger.d('[$runtimeType] JS Console ${message.message}'); + } + + void _onWebResourceError(WebResourceError error) { + if (error.isForMainFrame == true) { + isReady.value = false; + isConnected.value = false; + step.value = EmailLoginStep.idle; + debugPrint(''' + [$runtimeType] Page resource error: + code: ${error.errorCode} + description: ${error.description} + errorType: ${error.errorType} + isForMainFrame: ${error.isForMainFrame} + url: ${error.url} + '''); + } + } + + bool _isAllowedDomain(String domain) { + final domains = [ + Uri.parse(UrlConstants.secureService).authority, + Uri.parse(UrlConstants.secureDashboard).authority, + ..._safeDomains, + ].join('|'); + return RegExp(r'' + domains).hasMatch(domain); + } + + void _timeOut(Timer time) { + if (time.tick > 30) { + _resetTimeOut(); + _error(IsConnectedErrorEvent()); + isTimeout.value = true; + _core.logger.e( + '[EmailLogin] initialization timed out. Please check if your ' + 'bundleId/packageName $_packageName is whitelisted in your cloud ' + 'configuration at ${UrlConstants.cloudService} for project id ${_core.projectId}', + ); + } + } + + Future _setDebugMode() async { + if (kDebugMode) { + try { + if (Platform.isIOS) { + await _webViewController.setOnConsoleMessage( + _onDebugConsoleReceived, + ); + final webkitCtrl = + _webViewController.platform as WebKitWebViewController; + webkitCtrl.setInspectable(true); + } + if (Platform.isAndroid) { + if (_webViewController.platform is AndroidWebViewController) { + AndroidWebViewController.enableDebugging(true); + (_webViewController.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); + + final cookieManager = + WebViewCookieManager().platform as AndroidWebViewCookieManager; + cookieManager.setAcceptThirdPartyCookies( + _webViewController.platform as AndroidWebViewController, + true, + ); + } + } + } catch (_) {} + } + } + + void _resetTimeOut() { + _timeOutTimer?.cancel(); + _timeOutTimer = null; + } +} + +extension JavaScriptMessageExtension on JavaScriptMessage { + FrameMessage toFrameMessage() { + final decodeMessage = jsonDecode(message) as Map; + return FrameMessage.fromJson(decodeMessage); + } +} diff --git a/packages/reown_appkit/lib/modal/services/magic_service/magic_service_singleton.dart b/packages/reown_appkit/lib/modal/services/magic_service/magic_service_singleton.dart new file mode 100644 index 0000000..d767c7d --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/magic_service/magic_service_singleton.dart @@ -0,0 +1,7 @@ +import 'package:reown_appkit/modal/services/magic_service/magic_service.dart'; + +class MagicServiceSingleton { + late MagicService instance; +} + +final magicService = MagicServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/magic_service/models/email_login_step.dart b/packages/reown_appkit/lib/modal/services/magic_service/models/email_login_step.dart new file mode 100644 index 0000000..7659b3d --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/magic_service/models/email_login_step.dart @@ -0,0 +1,14 @@ +enum EmailLoginStep { + verifyDevice('VERIFY_DEVICE'), + verifyOtp('VERIFY_OTP'), + verifyOtp2('VERIFY_OTP_2'), // not an actual action from service + loading('LOADING'), + idle(''); + + final String action; + const EmailLoginStep(this.action); + + factory EmailLoginStep.fromAction(String action) { + return values.firstWhere((e) => e.action == action); + } +} diff --git a/packages/reown_appkit/lib/modal/services/magic_service/models/frame_message.dart b/packages/reown_appkit/lib/modal/services/magic_service/models/frame_message.dart new file mode 100644 index 0000000..da6929e --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/magic_service/models/frame_message.dart @@ -0,0 +1,296 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/utils/render_utils.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class FrameMessage { + final MessageData? data; + final String? origin; + + FrameMessage({this.data, this.origin}); + + FrameMessage copyWith({MessageData? data, String? origin}) => FrameMessage( + data: data ?? this.data, + origin: origin ?? this.origin, + ); + + factory FrameMessage.fromRawJson(String str) { + return FrameMessage.fromJson(json.decode(str)); + } + + String toRawJson() => json.encode(toJson()); + + factory FrameMessage.fromJson(Map json) => FrameMessage( + data: MessageData.fromJson(json['data']), + origin: json['origin'], + ); + + Map toJson() => { + 'data': data?.toJson(), + 'origin': origin, + }; + + bool get isValidOrigin { + return Uri.parse(origin ?? '').authority == + Uri.parse(UrlConstants.secureDashboard).authority; + } + + bool get isValidData { + return data != null; + } +} + +class MessageData { + final String? type; + final dynamic payload; + + MessageData({this.type, this.payload}); + + MessageData copyWith({String? type, dynamic payload}) { + return MessageData( + type: type ?? this.type, + payload: payload ?? this.payload, + ); + } + + factory MessageData.fromRawJson(String str) { + return MessageData.fromJson(json.decode(str)); + } + + String toRawJson() => json.encode(toJson()); + + factory MessageData.fromJson(Map json) => MessageData( + type: json['type'], + payload: json['payload'], + ); + + Map toJson() => { + 'type': type, + 'payload': payload?.toJson(), + }; + + T getPayloadMapKey(String key) { + final p = payload as Map; + return p[key] as T; + } + + // @w3m-frame events + bool get syncThemeSuccess => type == '@w3m-frame/SYNC_THEME_SUCCESS'; + bool get syncDataSuccess => type == '@w3m-frame/SYNC_DAPP_DATA_SUCCESS'; + bool get connectEmailSuccess => type == '@w3m-frame/CONNECT_EMAIL_SUCCESS'; + bool get connectEmailError => type == '@w3m-frame/CONNECT_EMAIL_ERROR'; + bool get updateEmailSuccess => type == '@w3m-frame/UPDATE_EMAIL_SUCCESS'; + bool get updateEmailPrimarySuccess => + type == '@w3m-frame/UPDATE_EMAIL_PRIMARY_OTP_SUCCESS'; + bool get updateEmailPrimaryOtpError => + type == '@w3m-frame/UPDATE_EMAIL_PRIMARY_OTP_ERROR'; + bool get updateEmailSecondarySuccess => + type == '@w3m-frame/UPDATE_EMAIL_SECONDARY_OTP_SUCCESS'; + bool get updateEmailSecondaryOtpError => + type == '@w3m-frame/UPDATE_EMAIL_SECONDARY_OTP_ERROR'; + bool get updateEmailError => type == '@w3m-frame/UPDATE_EMAIL_ERROR'; + bool get isConnectSuccess => type == '@w3m-frame/IS_CONNECTED_SUCCESS'; + bool get isConnectError => type == '@w3m-frame/IS_CONNECTED_ERROR'; + bool get connectOtpSuccess => type == '@w3m-frame/CONNECT_OTP_SUCCESS'; + bool get connectOtpError => type == '@w3m-frame/CONNECT_OTP_ERROR'; + bool get getUserSuccess => type == '@w3m-frame/GET_USER_SUCCESS'; + bool get getUserError => type == '@w3m-frame/GET_USER_ERROR'; + bool get sessionUpdate => type == '@w3m-frame/SESSION_UPDATE'; + bool get switchNetworkSuccess => type == '@w3m-frame/SWITCH_NETWORK_SUCCESS'; + bool get switchNetworkError => type == '@w3m-frame/SWITCH_NETWORK_ERROR'; + bool get getChainIdSuccess => type == '@w3m-frame/GET_CHAIN_ID_SUCCESS'; + bool get getChainIdError => type == '@w3m-frame/GET_CHAIN_ID_ERROR'; + bool get rpcRequestSuccess => type == '@w3m-frame/RPC_REQUEST_SUCCESS'; + bool get rpcRequestError => type == '@w3m-frame/RPC_REQUEST_ERROR'; + bool get signOutSuccess => type == '@w3m-frame/SIGN_OUT_SUCCESS'; + bool get signOutError => type == '@w3m-frame/SIGN_OUT_ERROR'; +} + +// @w3m-app events +class IsConnected extends MessageData { + IsConnected() : super(type: '@w3m-app/IS_CONNECTED'); + + @override + String toString() => '{type: "${super.type}"}'; +} + +class SwitchNetwork extends MessageData { + final String chainId; + SwitchNetwork({ + required this.chainId, + }) : super(type: '@w3m-app/SWITCH_NETWORK'); + + @override + String toString() => '{type:\'${super.type}\',payload:{chainId:$chainId}}'; +} + +class ConnectEmail extends MessageData { + final String email; + ConnectEmail({required this.email}) : super(type: '@w3m-app/CONNECT_EMAIL'); + + @override + String toString() => '{type:\'${super.type}\',payload:{email:\'$email\'}}'; +} + +class UpdateEmail extends MessageData { + final String email; + UpdateEmail({required this.email}) : super(type: '@w3m-app/UPDATE_EMAIL'); + + @override + String toString() => '{type:\'${super.type}\',payload:{email:\'$email\'}}'; +} + +class UpdateEmailPrimaryOtp extends MessageData { + final String otp; + UpdateEmailPrimaryOtp({ + required this.otp, + }) : super(type: '@w3m-app/UPDATE_EMAIL_PRIMARY_OTP'); + + @override + String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; +} + +class UpdateEmailSecondaryOtp extends MessageData { + final String otp; + UpdateEmailSecondaryOtp({ + required this.otp, + }) : super(type: '@w3m-app/UPDATE_EMAIL_SECONDARY_OTP'); + + @override + String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; +} + +class ConnectOtp extends MessageData { + final String otp; + ConnectOtp({required this.otp}) : super(type: '@w3m-app/CONNECT_OTP'); + + @override + String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; +} + +class GetUser extends MessageData { + final String? chainId; + GetUser({this.chainId}) : super(type: '@w3m-app/GET_USER'); + + @override + String toString() { + if ((chainId ?? '').isNotEmpty) { + return '{type:\'${super.type}\',payload:{chainId:$chainId}}'; + } + return '{type:\'${super.type}\'}'; + } +} + +class SignOut extends MessageData { + SignOut() : super(type: '@w3m-app/SIGN_OUT'); + + @override + String toString() => '{type: "${super.type}"}'; +} + +class GetChainId extends MessageData { + GetChainId() : super(type: '@w3m-app/GET_CHAIN_ID'); + + @override + String toString() => '{type: "${super.type}"}'; +} + +class RpcRequest extends MessageData { + final String method; + final List params; + + RpcRequest({ + required this.method, + required this.params, + }) : super(type: '@w3m-app/RPC_REQUEST'); + + @override + String toString() { + debugPrint('[$runtimeType] method $method'); + final m = 'method:\'$method\''; + final t = 'type:\'${super.type}\''; + final p = params.map((i) => '$i').toList(); + + if (method == 'personal_sign') { + final data = p.first; + final address = p.last; + return '{$t,payload:{$m,params:[\'$data\',\'$address\']}}'; + } + if (method == 'eth_signTypedData_v4' || + method == 'eth_signTypedData_v3' || + method == 'eth_signTypedData') { + // final data = jsonEncode(jsonDecode(p.first) as Map); + final data = p.first; + final address = p.last; + return '{$t,payload:{$m,params:[\'$address\',\'$data\']}}'; + } + if (method == 'eth_sendTransaction' || method == 'eth_signTransaction') { + final jp = jsonEncode(params.first); + return '{$t,payload:{$m,params:[$jp]}}'; + } + + final ps = p.join(','); + return '{$t,payload:{$m,params:[$ps]}}'; + } +} + +class SyncTheme extends MessageData { + final AppKitModalTheme? theme; + SyncTheme({required this.theme}) : super(type: '@w3m-app/SYNC_THEME'); + + @override + String toString() { + final mode = theme?.isDarkMode == true ? 'dark' : 'light'; + final themeData = theme?.themeData ?? AppKitModalThemeData(); + late AppKitModalColors colors; + if (mode == 'dark') { + colors = themeData.darkColors; + } else { + colors = themeData.lightColors; + } + + final tm = 'themeMode:\'$mode\''; + + final mix = RenderUtils.colorToRGBA(colors.background125); + final tv1 = '\'--w3m-color-mix\':\'$mix\''; + // final tv2 = '\'--w3m-color-mix-strength\':\'0%\''; + final tv = 'themeVariables:{$tv1}'; + + final accent = RenderUtils.colorToRGBA(colors.accent100); + final wtv1 = '\'--w3m-accent\':\'$accent\''; + final background = RenderUtils.colorToRGBA(colors.background125); + final wtv2 = '\'--w3m-background\':\'$background\''; + final w3mtv = 'w3mThemeVariables:{$wtv1,$wtv2}'; + + return '{type:\'${super.type}\',payload:{$tm, $tv,$w3mtv}}'; + } +} + +class SyncAppData extends MessageData { + SyncAppData({ + required this.metadata, + required this.sdkVersion, + required this.projectId, + }) : super(type: '@w3m-app/SYNC_DAPP_DATA'); + + final PairingMetadata metadata; + final String sdkVersion; + final String projectId; + + @override + String toString() { + final v = 'verified: true'; + final p1 = 'projectId:\'$projectId\''; + final p2 = 'sdkVersion:\'$sdkVersion\''; + final m1 = 'name:\'${metadata.name}\''; + final m2 = 'description:\'${metadata.description}\''; + final m3 = 'url:\'${metadata.url}\''; + final m4 = 'icons:["${metadata.icons.first}"]'; + final p3 = 'metadata:{$m1,$m2,$m3,$m4}'; + final p = 'payload:{$v,$p1,$p2,$p3}'; + return '{type:\'${super.type}\',$p}'; + } +} diff --git a/packages/reown_appkit/lib/modal/services/magic_service/models/magic_data.dart b/packages/reown_appkit/lib/modal/services/magic_service/models/magic_data.dart new file mode 100644 index 0000000..3237809 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/magic_service/models/magic_data.dart @@ -0,0 +1,60 @@ +import 'package:reown_appkit/reown_appkit.dart'; + +class MagicData { + String email; + String address; + int chainId; + ConnectionMetadata? self; + ConnectionMetadata? peer; + + MagicData({ + required this.email, + required this.chainId, + required this.address, + this.self, + this.peer, + }); + + factory MagicData.fromJson(Map json) { + return MagicData( + email: json['email'].toString(), + address: json['address'].toString(), + chainId: int.parse(json['chainId'].toString()), + self: (json['self'] != null) + ? ConnectionMetadata.fromJson(json['self']) + : null, + peer: (json['peer'] != null) + ? ConnectionMetadata.fromJson(json['peer']) + : null, + ); + } + + Map toJson() { + return { + 'email': email, + 'address': address, + 'chainId': chainId, + 'self': self?.toJson(), + 'peer': peer?.toJson(), + }; + } + + @override + String toString() => toJson().toString(); + + MagicData copytWith({ + String? email, + String? address, + int? chainId, + ConnectionMetadata? self, + ConnectionMetadata? peer, + }) { + return MagicData( + email: email ?? this.email, + address: address ?? this.address, + chainId: chainId ?? this.chainId, + self: self ?? this.self, + peer: peer ?? this.peer, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/services/magic_service/models/magic_events.dart b/packages/reown_appkit/lib/modal/services/magic_service/models/magic_events.dart new file mode 100644 index 0000000..2e812f1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/magic_service/models/magic_events.dart @@ -0,0 +1,124 @@ +import 'package:reown_appkit/modal/services/magic_service/models/magic_data.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class MagicLoginEvent implements EventArgs { + final MagicData? data; + MagicLoginEvent(this.data); + + @override + String toString() => data?.toString() ?? ''; +} + +class MagicSessionEvent implements EventArgs { + String? email; + String? address; + int? chainId; + + MagicSessionEvent({ + this.email, + this.address, + this.chainId, + }); + + Map toJson() { + Map params = {}; + if ((email ?? '').isNotEmpty) { + params['email'] = email; + } + if ((address ?? '').isNotEmpty) { + params['address'] = address; + } + if (chainId != null) { + params['chainId'] = chainId; + } + + return params; + } + + @override + String toString() => toJson().toString(); +} + +class MagicRequestEvent implements EventArgs { + dynamic request; + dynamic result; + bool? success; + + MagicRequestEvent({ + required this.request, + this.result, + this.success, + }); + + @override + String toString() => 'request: $request, success: $success, result: $result'; +} + +class MagicConnectEvent implements EventArgs { + final bool connected; + MagicConnectEvent(this.connected); +} + +class MagicErrorEvent implements EventArgs { + final String? error; + MagicErrorEvent(this.error); +} + +class IsConnectedErrorEvent extends MagicErrorEvent { + IsConnectedErrorEvent() : super('Error checking connection'); +} + +class ConnectEmailErrorEvent extends MagicErrorEvent { + final String? message; + ConnectEmailErrorEvent({this.message}) + : super( + message ?? 'Error connecting email', + ); +} + +class UpdateEmailErrorEvent extends MagicErrorEvent { + final String? message; + UpdateEmailErrorEvent({this.message}) + : super(message ?? 'Error updating email'); +} + +class UpdateEmailPrimaryOtpErrorEvent extends MagicErrorEvent { + final String? message; + UpdateEmailPrimaryOtpErrorEvent({this.message}) + : super( + message ?? 'Error validating OTP code', + ); +} + +class UpdateEmailSecondaryOtpErrorEvent extends MagicErrorEvent { + final String? message; + UpdateEmailSecondaryOtpErrorEvent({this.message}) + : super( + message ?? 'Error validating OTP code', + ); +} + +class ConnectOtpErrorEvent extends MagicErrorEvent { + final String? message; + ConnectOtpErrorEvent({this.message}) + : super( + message ?? 'Error validating OTP code', + ); +} + +class GetUserErrorEvent extends MagicErrorEvent { + GetUserErrorEvent() : super('Error getting user'); +} + +class SwitchNetworkErrorEvent extends MagicErrorEvent { + SwitchNetworkErrorEvent() : super('Error switching network'); +} + +class SignOutErrorEvent extends MagicErrorEvent { + SignOutErrorEvent() : super('Error on Signing out'); +} + +class RpcRequestErrorEvent extends MagicErrorEvent { + RpcRequestErrorEvent(String? message) + : super(message ?? 'Error during request'); +} diff --git a/packages/reown_appkit/lib/modal/services/network_service/i_network_service.dart b/packages/reown_appkit/lib/modal/services/network_service/i_network_service.dart new file mode 100644 index 0000000..97a2c9f --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/network_service/i_network_service.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/models/grid_item.dart'; +import 'package:reown_appkit/modal/models/public/appkit_network_info.dart'; + +abstract class INetworkService { + abstract ValueNotifier>> itemList; + abstract ValueNotifier initialized; + + Future init(); + + void filterList({String? query}); +} diff --git a/packages/reown_appkit/lib/modal/services/network_service/network_service.dart b/packages/reown_appkit/lib/modal/services/network_service/network_service.dart new file mode 100644 index 0000000..30a9ade --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/network_service/network_service.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; + +import 'package:reown_appkit/modal/models/grid_item.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/services/network_service/i_network_service.dart'; +import 'package:reown_appkit/modal/utils/render_utils.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class NetworkService implements INetworkService { + @override + ValueNotifier initialized = ValueNotifier(false); + + List> itemListComplete = []; + + @override + ValueNotifier>> itemList = + ValueNotifier>>([]); + + String _getImageUrl(AppKitModalNetworkInfo chainInfo) { + if (chainInfo.chainIcon != null && chainInfo.chainIcon!.contains('http')) { + return chainInfo.chainIcon!; + } + final imageId = AppKitModalNetworks.getNetworkIconId(chainInfo.chainId); + return explorerService.instance.getAssetImageUrl(imageId); + } + + @override + Future init() async { + if (initialized.value) { + return; + } + + final networks = AppKitModalNetworks.getNetworks( + CoreConstants.namespace, + ); + for (var chain in networks) { + final imageUrl = _getImageUrl(chain); + itemListComplete.add( + GridItem( + image: imageUrl, + id: chain.chainId, + title: RenderUtils.shorten(chain.name), + data: chain, + ), + ); + } + + itemList.value = itemListComplete; + + initialized.value = true; + } + + @override + void filterList({String? query}) { + if (query == null || query.isEmpty) { + itemList.value = itemListComplete; + return; + } + + itemList.value = itemListComplete + .where( + (element) => element.title.toLowerCase().contains( + query.toLowerCase(), + ), + ) + .toList(); + } +} diff --git a/packages/reown_appkit/lib/modal/services/network_service/network_service_singleton.dart b/packages/reown_appkit/lib/modal/services/network_service/network_service_singleton.dart new file mode 100644 index 0000000..7bf9bf8 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/network_service/network_service_singleton.dart @@ -0,0 +1,10 @@ +import 'package:reown_appkit/modal/services/network_service/i_network_service.dart'; +import 'package:reown_appkit/modal/services/network_service/network_service.dart'; + +class NetworkServiceSingleton { + INetworkService instance; + + NetworkServiceSingleton() : instance = NetworkService(); +} + +final networkService = NetworkServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/siwe_service/i_siwe_service.dart b/packages/reown_appkit/lib/modal/services/siwe_service/i_siwe_service.dart new file mode 100644 index 0000000..ccd3715 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/siwe_service/i_siwe_service.dart @@ -0,0 +1,37 @@ +import 'package:reown_appkit/reown_appkit.dart'; + +abstract class ISiweService { + SIWEConfig? get config; + + bool get enabled; + bool get signOutOnDisconnect; + bool get signOutOnAccountChange; + bool get signOutOnNetworkChange; + int get nonceRefetchIntervalMs; + int get sessionRefetchIntervalMs; + + Future getNonce(); + + Future createMessage({ + required String chainId, + required String address, + }); + + Future signMessageRequest( + String message, { + required AppKitModalSession session, + }); + + Future verifyMessage({ + required String message, + required String signature, + Cacao? cacao, + String? clientId, + }); + + Future getSession(); + + Future signOut(); + + String formatMessage(SIWECreateMessageArgs params); +} diff --git a/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart b/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart new file mode 100644 index 0000000..1d70bce --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart @@ -0,0 +1,181 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/coinbase_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/siwe_service/i_siwe_service.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class SiweService implements ISiweService { + late final SIWEConfig? _siweConfig; + final IReownAppKit _appKit; + + SiweService({ + required IReownAppKit appKit, + required SIWEConfig? siweConfig, + }) : _appKit = appKit, + _siweConfig = siweConfig; + + @override + SIWEConfig? get config => _siweConfig; + + @override + bool get enabled => _siweConfig?.enabled == true; + + @override + int get nonceRefetchIntervalMs => + _siweConfig?.nonceRefetchIntervalMs ?? 300000; + + @override + int get sessionRefetchIntervalMs => + _siweConfig?.sessionRefetchIntervalMs ?? 300000; + + @override + bool get signOutOnAccountChange => + _siweConfig?.signOutOnAccountChange ?? true; + + @override + bool get signOutOnDisconnect => _siweConfig?.signOutOnDisconnect ?? true; + + @override + bool get signOutOnNetworkChange => + _siweConfig?.signOutOnNetworkChange ?? true; + + @override + Future getNonce() async { + if (!enabled) throw Exception('siweConfig not enabled'); + // + return await _siweConfig!.getNonce(); + } + + @override + Future createMessage({ + required String chainId, + required String address, + }) async { + if (!enabled) throw Exception('siweConfig not enabled'); + // + final nonce = await getNonce(); + final messageParams = await _siweConfig!.getMessageParams(); + // + final createMessageArgs = SIWECreateMessageArgs.fromSIWEMessageArgs( + messageParams, + address: '$chainId:$address', + chainId: chainId, + nonce: nonce, + type: messageParams.type ?? CacaoHeader(t: 'eip4361'), + ); + + return _siweConfig!.createMessage(createMessageArgs); + } + + @override + Future signMessageRequest( + String message, { + required AppKitModalSession session, + }) async { + if (!enabled) throw Exception('siweConfig not enabled'); + // + final chainId = AuthSignature.getChainIdFromMessage(message); + final chainInfo = AppKitModalNetworks.getNetworkById( + CoreConstants.namespace, + chainId, + )!; + final caip2Chain = '${CoreConstants.namespace}:${chainInfo.chainId}'; + final address = AuthSignature.getAddressFromMessage(message); + final bytes = utf8.encode(message); + final encoded = hex.encode(bytes); + // + if (session.sessionService.isMagic) { + return await magicService.instance.request( + chainId: caip2Chain, + request: SessionRequestParams( + method: 'personal_sign', + params: ['0x$encoded', address], + ), + ); + } + if (session.sessionService.isCoinbase) { + return await coinbaseService.instance.request( + chainId: caip2Chain, + request: SessionRequestParams( + method: 'personal_sign', + params: ['0x$encoded', address], + ), + ); + } + return await _appKit.request( + topic: session.topic!, + chainId: caip2Chain, + request: SessionRequestParams( + method: 'personal_sign', + params: ['0x$encoded', address], + ), + ); + } + + @override + Future verifyMessage({ + required String message, + required String signature, + Cacao? cacao, + String? clientId, + }) async { + if (!enabled) throw Exception('siweConfig not enabled'); + // + final verifyArgs = SIWEVerifyMessageArgs( + message: message, + signature: signature, + cacao: cacao, + clientId: clientId, + ); + final isValid = await _siweConfig!.verifyMessage(verifyArgs); + if (!isValid) { + throw AppKitModalException('Error verifying SIWE signature'); + } + return true; + } + + @override + Future getSession() async { + if (!enabled) throw Exception('siweConfig not enabled'); + // + try { + final siweSession = await _siweConfig!.getSession(); + if (siweSession == null) { + throw AppKitModalException('Error getting SIWE session'); + } + _siweConfig!.onSignIn?.call(siweSession); + + return siweSession; + } catch (e) { + rethrow; + } + } + + @override + Future signOut() async { + if (!enabled) throw Exception('siweConfig not enabled'); + + final success = await _siweConfig!.signOut(); + if (!success) { + throw AppKitModalException('signOut() from siweConfig failed'); + } + _siweConfig!.onSignOut?.call(); + } + + @override + String formatMessage(SIWECreateMessageArgs params) { + final authPayload = SessionAuthPayload.fromJson({ + ...params.toJson(), + 'chains': [params.chainId], + 'aud': params.uri, + 'type': params.type?.t, + }); + return _appKit.formatAuthMessage( + iss: 'did:pkh:${params.address}', + cacaoPayload: CacaoRequestPayload.fromSessionAuthPayload(authPayload), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service_singleton.dart b/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service_singleton.dart new file mode 100644 index 0000000..5a109ad --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service_singleton.dart @@ -0,0 +1,7 @@ +import 'package:reown_appkit/modal/services/siwe_service/i_siwe_service.dart'; + +class SiweServiceSingleton { + ISiweService? instance; +} + +final siweService = SiweServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/toast_service/i_toast_service.dart b/packages/reown_appkit/lib/modal/services/toast_service/i_toast_service.dart new file mode 100644 index 0000000..30d46d6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/toast_service/i_toast_service.dart @@ -0,0 +1,11 @@ +import 'dart:async'; + +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; + +abstract class IToastService { + Stream get toasts; + + void show(ToastMessage message); + + void clear(); +} diff --git a/packages/reown_appkit/lib/modal/services/toast_service/models/toast_message.dart b/packages/reown_appkit/lib/modal/services/toast_service/models/toast_message.dart new file mode 100644 index 0000000..4bdcced --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/toast_service/models/toast_message.dart @@ -0,0 +1,64 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; + +enum ToastType { success, info, error } + +class ToastMessage { + final ToastType type; + final String text; + final Duration duration; + final Completer completer = Completer(); + + ToastMessage({ + required this.type, + required this.text, + this.duration = const Duration(milliseconds: 2500), + }); + + RoundedIcon icon(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + switch (type) { + case ToastType.success: + return RoundedIcon( + assetPath: 'lib/modal/assets/icons/checkmark.svg', + assetColor: themeColors.success100, + circleColor: themeColors.success100.withOpacity(0.15), + borderColor: Colors.transparent, + padding: 5.0, + size: 24.0, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ); + case ToastType.error: + return RoundedIcon( + assetPath: 'lib/modal/assets/icons/close.svg', + assetColor: themeColors.error100, + circleColor: themeColors.error100.withOpacity(0.15), + borderColor: Colors.transparent, + padding: 5.0, + size: 24.0, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ); + default: + return RoundedIcon( + assetPath: 'lib/modal/assets/icons/info.svg', + assetColor: themeColors.accent100, + circleColor: themeColors.accent100.withOpacity(0.15), + borderColor: Colors.transparent, + padding: 5.0, + size: 24.0, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ); + } + } +} + +class ToastMessageCompleter { + final ToastMessage message; + final Completer completer = Completer(); + + ToastMessageCompleter(this.message); +} diff --git a/packages/reown_appkit/lib/modal/services/toast_service/toast_service.dart b/packages/reown_appkit/lib/modal/services/toast_service/toast_service.dart new file mode 100644 index 0000000..a56bd93 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/toast_service/toast_service.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; + +class ToastService extends IToastService { + final _toastController = StreamController.broadcast(); + + @override + Stream get toasts => _toastController.stream; + + @override + void show(ToastMessage? message) { + _toastController.add(message); + } + + @override + void clear() { + _toastController.add(null); + } +} diff --git a/packages/reown_appkit/lib/modal/services/toast_service/toast_service_singleton.dart b/packages/reown_appkit/lib/modal/services/toast_service/toast_service_singleton.dart new file mode 100644 index 0000000..64a6838 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/toast_service/toast_service_singleton.dart @@ -0,0 +1,10 @@ +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service.dart'; + +class ToastServiceSingleton { + IToastService instance; + + ToastServiceSingleton() : instance = ToastService(); +} + +final toastService = ToastServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/uri_service/i_url_utils.dart b/packages/reown_appkit/lib/modal/services/uri_service/i_url_utils.dart new file mode 100644 index 0000000..dc6486b --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/uri_service/i_url_utils.dart @@ -0,0 +1,17 @@ +import 'package:reown_appkit/modal/utils/platform_utils.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:reown_appkit/modal/services/explorer_service/models/redirect.dart'; + +abstract class IUriService { + const IUriService(); + + Future isInstalled(String? uri, {String? id}); + + Future launchUrl(Uri url, {LaunchMode? mode}); + + Future openRedirect( + WalletRedirect redirect, { + String? wcURI, + PlatformType? pType, + }); +} diff --git a/packages/reown_appkit/lib/modal/services/uri_service/launch_url_exception.dart b/packages/reown_appkit/lib/modal/services/uri_service/launch_url_exception.dart new file mode 100644 index 0000000..9aaa88b --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/uri_service/launch_url_exception.dart @@ -0,0 +1,12 @@ +class LaunchUrlException { + final String message; + LaunchUrlException(this.message); +} + +class CanNotLaunchUrl extends LaunchUrlException { + CanNotLaunchUrl() : super('App not installed'); +} + +class ErrorLaunchingUrl extends LaunchUrlException { + ErrorLaunchingUrl() : super('Error launching app'); +} diff --git a/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart b/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart new file mode 100644 index 0000000..6f64b0d --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart @@ -0,0 +1,120 @@ +import 'package:appcheck/appcheck.dart'; +import 'package:reown_appkit/modal/services/uri_service/i_url_utils.dart'; +import 'package:reown_appkit/modal/services/uri_service/launch_url_exception.dart'; +import 'package:reown_appkit/modal/utils/core_utils.dart'; +import 'package:reown_appkit/modal/utils/platform_utils.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:url_launcher/url_launcher.dart' as launcher; +import 'package:reown_appkit/modal/services/explorer_service/models/redirect.dart'; + +class UriService extends IUriService { + UriService({required IReownCore core}) : _core = core; + final IReownCore _core; + + @override + Future isInstalled(String? uri, {String? id}) async { + if (uri == null || uri.isEmpty) { + return false; + } + + // If the wallet is just a generic wc:// then it is not installed + if (uri.contains('wc://')) { + return false; + } + + if (PlatformUtils.canDetectInstalledApps()) { + final p = PlatformUtils.getPlatformExact(); + try { + if (p == PlatformExact.android) { + return await _androidAppCheck(uri); + } else if (p == PlatformExact.ios) { + return await launcher.canLaunchUrl(Uri.parse(uri)); + } + } on FormatException catch (e) { + if (id != null) { + _core.logger.d('[$runtimeType] $uri ($id): ${e.message}'); + } else { + _core.logger.d('[$runtimeType] $uri: ${e.message}'); + } + } catch (e) { + rethrow; + } + } + + return false; + } + + @override + Future launchUrl(Uri url, {launcher.LaunchMode? mode}) async { + return await _launchUrlFunc( + url, + mode: mode, + ); + } + + @override + Future openRedirect( + WalletRedirect redirect, { + String? wcURI, + PlatformType? pType, + }) async { + // + Uri? uriToOpen; + try { + final isMobile = (redirect.mobileOnly || pType == PlatformType.mobile); + if (isMobile && redirect.mobile != null) { + uriToOpen = CoreUtils.formatCustomSchemeUri( + redirect.mobile, + wcURI, + ); + } + // + final isWeb = (redirect.webOnly || pType == PlatformType.web); + if (isWeb && redirect.web != null) { + uriToOpen = CoreUtils.formatWebUrl( + redirect.web, + wcURI, + ); + } + // + final isDesktop = (redirect.desktopOnly || pType == PlatformType.desktop); + if (isDesktop && redirect.desktop != null) { + uriToOpen = CoreUtils.formatCustomSchemeUri( + redirect.desktop, + wcURI, + ); + } + } catch (e) { + _core.logger.e('Error opening redirect', error: e); + return false; + } + _core.logger.i('[$runtimeType] openRedirect $uriToOpen'); + return await _launchUrlFunc( + uriToOpen!, + mode: launcher.LaunchMode.externalApplication, + ); + } + + Future _launchUrlFunc(Uri url, {launcher.LaunchMode? mode}) async { + try { + final success = await launcher.launchUrl( + url, + mode: mode ?? launcher.LaunchMode.platformDefault, + ); + if (!success) { + throw CanNotLaunchUrl(); + } + return true; + } catch (e) { + rethrow; + } + } + + Future _androidAppCheck(String uri) async { + try { + return await AppCheck().isAppEnabled(uri); + } catch (e) { + return false; + } + } +} diff --git a/packages/reown_appkit/lib/modal/services/uri_service/url_utils_singleton.dart b/packages/reown_appkit/lib/modal/services/uri_service/url_utils_singleton.dart new file mode 100644 index 0000000..af7eaa8 --- /dev/null +++ b/packages/reown_appkit/lib/modal/services/uri_service/url_utils_singleton.dart @@ -0,0 +1,7 @@ +import 'package:reown_appkit/modal/services/uri_service/i_url_utils.dart'; + +class UriServiceSingleton { + late IUriService instance; +} + +final uriService = UriServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/theme/appkit_modal_colors.freezed.dart b/packages/reown_appkit/lib/modal/theme/appkit_modal_colors.freezed.dart new file mode 100644 index 0000000..cf007bc --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/appkit_modal_colors.freezed.dart @@ -0,0 +1,826 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_modal_colors.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalColors { + Color get accent100 => throw _privateConstructorUsedError; + Color get accent090 => throw _privateConstructorUsedError; + Color get accent080 => throw _privateConstructorUsedError; // + Color get grayGlass100 => throw _privateConstructorUsedError; // + Color get foreground100 => throw _privateConstructorUsedError; + Color get foreground125 => throw _privateConstructorUsedError; + Color get foreground150 => throw _privateConstructorUsedError; + Color get foreground175 => throw _privateConstructorUsedError; + Color get foreground200 => throw _privateConstructorUsedError; + Color get foreground225 => throw _privateConstructorUsedError; + Color get foreground250 => throw _privateConstructorUsedError; + Color get foreground275 => throw _privateConstructorUsedError; + Color get foreground300 => throw _privateConstructorUsedError; // + Color get background100 => throw _privateConstructorUsedError; + Color get background125 => throw _privateConstructorUsedError; + Color get background150 => throw _privateConstructorUsedError; + Color get background175 => throw _privateConstructorUsedError; + Color get background200 => throw _privateConstructorUsedError; + Color get background225 => throw _privateConstructorUsedError; + Color get background250 => throw _privateConstructorUsedError; + Color get background275 => throw _privateConstructorUsedError; + Color get background300 => throw _privateConstructorUsedError; // + Color get inverse100 => throw _privateConstructorUsedError; + Color get inverse000 => throw _privateConstructorUsedError; + Color get success100 => throw _privateConstructorUsedError; + Color get error100 => throw _privateConstructorUsedError; + Color get teal100 => throw _privateConstructorUsedError; + Color get magenta100 => throw _privateConstructorUsedError; + Color get indigo100 => throw _privateConstructorUsedError; + Color get orange100 => throw _privateConstructorUsedError; + Color get purple100 => throw _privateConstructorUsedError; + Color get yellow100 => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalColorsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalColorsCopyWith<$Res> { + factory $AppKitModalColorsCopyWith( + AppKitModalColors value, $Res Function(AppKitModalColors) then) = + _$AppKitModalColorsCopyWithImpl<$Res, AppKitModalColors>; + @useResult + $Res call( + {Color accent100, + Color accent090, + Color accent080, + Color grayGlass100, + Color foreground100, + Color foreground125, + Color foreground150, + Color foreground175, + Color foreground200, + Color foreground225, + Color foreground250, + Color foreground275, + Color foreground300, + Color background100, + Color background125, + Color background150, + Color background175, + Color background200, + Color background225, + Color background250, + Color background275, + Color background300, + Color inverse100, + Color inverse000, + Color success100, + Color error100, + Color teal100, + Color magenta100, + Color indigo100, + Color orange100, + Color purple100, + Color yellow100}); +} + +/// @nodoc +class _$AppKitModalColorsCopyWithImpl<$Res, $Val extends AppKitModalColors> + implements $AppKitModalColorsCopyWith<$Res> { + _$AppKitModalColorsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accent100 = null, + Object? accent090 = null, + Object? accent080 = null, + Object? grayGlass100 = null, + Object? foreground100 = null, + Object? foreground125 = null, + Object? foreground150 = null, + Object? foreground175 = null, + Object? foreground200 = null, + Object? foreground225 = null, + Object? foreground250 = null, + Object? foreground275 = null, + Object? foreground300 = null, + Object? background100 = null, + Object? background125 = null, + Object? background150 = null, + Object? background175 = null, + Object? background200 = null, + Object? background225 = null, + Object? background250 = null, + Object? background275 = null, + Object? background300 = null, + Object? inverse100 = null, + Object? inverse000 = null, + Object? success100 = null, + Object? error100 = null, + Object? teal100 = null, + Object? magenta100 = null, + Object? indigo100 = null, + Object? orange100 = null, + Object? purple100 = null, + Object? yellow100 = null, + }) { + return _then(_value.copyWith( + accent100: null == accent100 + ? _value.accent100 + : accent100 // ignore: cast_nullable_to_non_nullable + as Color, + accent090: null == accent090 + ? _value.accent090 + : accent090 // ignore: cast_nullable_to_non_nullable + as Color, + accent080: null == accent080 + ? _value.accent080 + : accent080 // ignore: cast_nullable_to_non_nullable + as Color, + grayGlass100: null == grayGlass100 + ? _value.grayGlass100 + : grayGlass100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground100: null == foreground100 + ? _value.foreground100 + : foreground100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground125: null == foreground125 + ? _value.foreground125 + : foreground125 // ignore: cast_nullable_to_non_nullable + as Color, + foreground150: null == foreground150 + ? _value.foreground150 + : foreground150 // ignore: cast_nullable_to_non_nullable + as Color, + foreground175: null == foreground175 + ? _value.foreground175 + : foreground175 // ignore: cast_nullable_to_non_nullable + as Color, + foreground200: null == foreground200 + ? _value.foreground200 + : foreground200 // ignore: cast_nullable_to_non_nullable + as Color, + foreground225: null == foreground225 + ? _value.foreground225 + : foreground225 // ignore: cast_nullable_to_non_nullable + as Color, + foreground250: null == foreground250 + ? _value.foreground250 + : foreground250 // ignore: cast_nullable_to_non_nullable + as Color, + foreground275: null == foreground275 + ? _value.foreground275 + : foreground275 // ignore: cast_nullable_to_non_nullable + as Color, + foreground300: null == foreground300 + ? _value.foreground300 + : foreground300 // ignore: cast_nullable_to_non_nullable + as Color, + background100: null == background100 + ? _value.background100 + : background100 // ignore: cast_nullable_to_non_nullable + as Color, + background125: null == background125 + ? _value.background125 + : background125 // ignore: cast_nullable_to_non_nullable + as Color, + background150: null == background150 + ? _value.background150 + : background150 // ignore: cast_nullable_to_non_nullable + as Color, + background175: null == background175 + ? _value.background175 + : background175 // ignore: cast_nullable_to_non_nullable + as Color, + background200: null == background200 + ? _value.background200 + : background200 // ignore: cast_nullable_to_non_nullable + as Color, + background225: null == background225 + ? _value.background225 + : background225 // ignore: cast_nullable_to_non_nullable + as Color, + background250: null == background250 + ? _value.background250 + : background250 // ignore: cast_nullable_to_non_nullable + as Color, + background275: null == background275 + ? _value.background275 + : background275 // ignore: cast_nullable_to_non_nullable + as Color, + background300: null == background300 + ? _value.background300 + : background300 // ignore: cast_nullable_to_non_nullable + as Color, + inverse100: null == inverse100 + ? _value.inverse100 + : inverse100 // ignore: cast_nullable_to_non_nullable + as Color, + inverse000: null == inverse000 + ? _value.inverse000 + : inverse000 // ignore: cast_nullable_to_non_nullable + as Color, + success100: null == success100 + ? _value.success100 + : success100 // ignore: cast_nullable_to_non_nullable + as Color, + error100: null == error100 + ? _value.error100 + : error100 // ignore: cast_nullable_to_non_nullable + as Color, + teal100: null == teal100 + ? _value.teal100 + : teal100 // ignore: cast_nullable_to_non_nullable + as Color, + magenta100: null == magenta100 + ? _value.magenta100 + : magenta100 // ignore: cast_nullable_to_non_nullable + as Color, + indigo100: null == indigo100 + ? _value.indigo100 + : indigo100 // ignore: cast_nullable_to_non_nullable + as Color, + orange100: null == orange100 + ? _value.orange100 + : orange100 // ignore: cast_nullable_to_non_nullable + as Color, + purple100: null == purple100 + ? _value.purple100 + : purple100 // ignore: cast_nullable_to_non_nullable + as Color, + yellow100: null == yellow100 + ? _value.yellow100 + : yellow100 // ignore: cast_nullable_to_non_nullable + as Color, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalColorsImplCopyWith<$Res> + implements $AppKitModalColorsCopyWith<$Res> { + factory _$$AppKitModalColorsImplCopyWith(_$AppKitModalColorsImpl value, + $Res Function(_$AppKitModalColorsImpl) then) = + __$$AppKitModalColorsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Color accent100, + Color accent090, + Color accent080, + Color grayGlass100, + Color foreground100, + Color foreground125, + Color foreground150, + Color foreground175, + Color foreground200, + Color foreground225, + Color foreground250, + Color foreground275, + Color foreground300, + Color background100, + Color background125, + Color background150, + Color background175, + Color background200, + Color background225, + Color background250, + Color background275, + Color background300, + Color inverse100, + Color inverse000, + Color success100, + Color error100, + Color teal100, + Color magenta100, + Color indigo100, + Color orange100, + Color purple100, + Color yellow100}); +} + +/// @nodoc +class __$$AppKitModalColorsImplCopyWithImpl<$Res> + extends _$AppKitModalColorsCopyWithImpl<$Res, _$AppKitModalColorsImpl> + implements _$$AppKitModalColorsImplCopyWith<$Res> { + __$$AppKitModalColorsImplCopyWithImpl(_$AppKitModalColorsImpl _value, + $Res Function(_$AppKitModalColorsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accent100 = null, + Object? accent090 = null, + Object? accent080 = null, + Object? grayGlass100 = null, + Object? foreground100 = null, + Object? foreground125 = null, + Object? foreground150 = null, + Object? foreground175 = null, + Object? foreground200 = null, + Object? foreground225 = null, + Object? foreground250 = null, + Object? foreground275 = null, + Object? foreground300 = null, + Object? background100 = null, + Object? background125 = null, + Object? background150 = null, + Object? background175 = null, + Object? background200 = null, + Object? background225 = null, + Object? background250 = null, + Object? background275 = null, + Object? background300 = null, + Object? inverse100 = null, + Object? inverse000 = null, + Object? success100 = null, + Object? error100 = null, + Object? teal100 = null, + Object? magenta100 = null, + Object? indigo100 = null, + Object? orange100 = null, + Object? purple100 = null, + Object? yellow100 = null, + }) { + return _then(_$AppKitModalColorsImpl( + accent100: null == accent100 + ? _value.accent100 + : accent100 // ignore: cast_nullable_to_non_nullable + as Color, + accent090: null == accent090 + ? _value.accent090 + : accent090 // ignore: cast_nullable_to_non_nullable + as Color, + accent080: null == accent080 + ? _value.accent080 + : accent080 // ignore: cast_nullable_to_non_nullable + as Color, + grayGlass100: null == grayGlass100 + ? _value.grayGlass100 + : grayGlass100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground100: null == foreground100 + ? _value.foreground100 + : foreground100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground125: null == foreground125 + ? _value.foreground125 + : foreground125 // ignore: cast_nullable_to_non_nullable + as Color, + foreground150: null == foreground150 + ? _value.foreground150 + : foreground150 // ignore: cast_nullable_to_non_nullable + as Color, + foreground175: null == foreground175 + ? _value.foreground175 + : foreground175 // ignore: cast_nullable_to_non_nullable + as Color, + foreground200: null == foreground200 + ? _value.foreground200 + : foreground200 // ignore: cast_nullable_to_non_nullable + as Color, + foreground225: null == foreground225 + ? _value.foreground225 + : foreground225 // ignore: cast_nullable_to_non_nullable + as Color, + foreground250: null == foreground250 + ? _value.foreground250 + : foreground250 // ignore: cast_nullable_to_non_nullable + as Color, + foreground275: null == foreground275 + ? _value.foreground275 + : foreground275 // ignore: cast_nullable_to_non_nullable + as Color, + foreground300: null == foreground300 + ? _value.foreground300 + : foreground300 // ignore: cast_nullable_to_non_nullable + as Color, + background100: null == background100 + ? _value.background100 + : background100 // ignore: cast_nullable_to_non_nullable + as Color, + background125: null == background125 + ? _value.background125 + : background125 // ignore: cast_nullable_to_non_nullable + as Color, + background150: null == background150 + ? _value.background150 + : background150 // ignore: cast_nullable_to_non_nullable + as Color, + background175: null == background175 + ? _value.background175 + : background175 // ignore: cast_nullable_to_non_nullable + as Color, + background200: null == background200 + ? _value.background200 + : background200 // ignore: cast_nullable_to_non_nullable + as Color, + background225: null == background225 + ? _value.background225 + : background225 // ignore: cast_nullable_to_non_nullable + as Color, + background250: null == background250 + ? _value.background250 + : background250 // ignore: cast_nullable_to_non_nullable + as Color, + background275: null == background275 + ? _value.background275 + : background275 // ignore: cast_nullable_to_non_nullable + as Color, + background300: null == background300 + ? _value.background300 + : background300 // ignore: cast_nullable_to_non_nullable + as Color, + inverse100: null == inverse100 + ? _value.inverse100 + : inverse100 // ignore: cast_nullable_to_non_nullable + as Color, + inverse000: null == inverse000 + ? _value.inverse000 + : inverse000 // ignore: cast_nullable_to_non_nullable + as Color, + success100: null == success100 + ? _value.success100 + : success100 // ignore: cast_nullable_to_non_nullable + as Color, + error100: null == error100 + ? _value.error100 + : error100 // ignore: cast_nullable_to_non_nullable + as Color, + teal100: null == teal100 + ? _value.teal100 + : teal100 // ignore: cast_nullable_to_non_nullable + as Color, + magenta100: null == magenta100 + ? _value.magenta100 + : magenta100 // ignore: cast_nullable_to_non_nullable + as Color, + indigo100: null == indigo100 + ? _value.indigo100 + : indigo100 // ignore: cast_nullable_to_non_nullable + as Color, + orange100: null == orange100 + ? _value.orange100 + : orange100 // ignore: cast_nullable_to_non_nullable + as Color, + purple100: null == purple100 + ? _value.purple100 + : purple100 // ignore: cast_nullable_to_non_nullable + as Color, + yellow100: null == yellow100 + ? _value.yellow100 + : yellow100 // ignore: cast_nullable_to_non_nullable + as Color, + )); + } +} + +/// @nodoc + +class _$AppKitModalColorsImpl implements _AppKitModalColors { + const _$AppKitModalColorsImpl( + {required this.accent100, + required this.accent090, + required this.accent080, + required this.grayGlass100, + required this.foreground100, + required this.foreground125, + required this.foreground150, + required this.foreground175, + required this.foreground200, + required this.foreground225, + required this.foreground250, + required this.foreground275, + required this.foreground300, + required this.background100, + required this.background125, + required this.background150, + required this.background175, + required this.background200, + required this.background225, + required this.background250, + required this.background275, + required this.background300, + required this.inverse100, + required this.inverse000, + required this.success100, + required this.error100, + required this.teal100, + required this.magenta100, + required this.indigo100, + required this.orange100, + required this.purple100, + required this.yellow100}); + + @override + final Color accent100; + @override + final Color accent090; + @override + final Color accent080; +// + @override + final Color grayGlass100; +// + @override + final Color foreground100; + @override + final Color foreground125; + @override + final Color foreground150; + @override + final Color foreground175; + @override + final Color foreground200; + @override + final Color foreground225; + @override + final Color foreground250; + @override + final Color foreground275; + @override + final Color foreground300; +// + @override + final Color background100; + @override + final Color background125; + @override + final Color background150; + @override + final Color background175; + @override + final Color background200; + @override + final Color background225; + @override + final Color background250; + @override + final Color background275; + @override + final Color background300; +// + @override + final Color inverse100; + @override + final Color inverse000; + @override + final Color success100; + @override + final Color error100; + @override + final Color teal100; + @override + final Color magenta100; + @override + final Color indigo100; + @override + final Color orange100; + @override + final Color purple100; + @override + final Color yellow100; + + @override + String toString() { + return 'AppKitModalColors(accent100: $accent100, accent090: $accent090, accent080: $accent080, grayGlass100: $grayGlass100, foreground100: $foreground100, foreground125: $foreground125, foreground150: $foreground150, foreground175: $foreground175, foreground200: $foreground200, foreground225: $foreground225, foreground250: $foreground250, foreground275: $foreground275, foreground300: $foreground300, background100: $background100, background125: $background125, background150: $background150, background175: $background175, background200: $background200, background225: $background225, background250: $background250, background275: $background275, background300: $background300, inverse100: $inverse100, inverse000: $inverse000, success100: $success100, error100: $error100, teal100: $teal100, magenta100: $magenta100, indigo100: $indigo100, orange100: $orange100, purple100: $purple100, yellow100: $yellow100)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalColorsImpl && + (identical(other.accent100, accent100) || + other.accent100 == accent100) && + (identical(other.accent090, accent090) || + other.accent090 == accent090) && + (identical(other.accent080, accent080) || + other.accent080 == accent080) && + (identical(other.grayGlass100, grayGlass100) || + other.grayGlass100 == grayGlass100) && + (identical(other.foreground100, foreground100) || + other.foreground100 == foreground100) && + (identical(other.foreground125, foreground125) || + other.foreground125 == foreground125) && + (identical(other.foreground150, foreground150) || + other.foreground150 == foreground150) && + (identical(other.foreground175, foreground175) || + other.foreground175 == foreground175) && + (identical(other.foreground200, foreground200) || + other.foreground200 == foreground200) && + (identical(other.foreground225, foreground225) || + other.foreground225 == foreground225) && + (identical(other.foreground250, foreground250) || + other.foreground250 == foreground250) && + (identical(other.foreground275, foreground275) || + other.foreground275 == foreground275) && + (identical(other.foreground300, foreground300) || + other.foreground300 == foreground300) && + (identical(other.background100, background100) || + other.background100 == background100) && + (identical(other.background125, background125) || + other.background125 == background125) && + (identical(other.background150, background150) || + other.background150 == background150) && + (identical(other.background175, background175) || + other.background175 == background175) && + (identical(other.background200, background200) || + other.background200 == background200) && + (identical(other.background225, background225) || + other.background225 == background225) && + (identical(other.background250, background250) || + other.background250 == background250) && + (identical(other.background275, background275) || + other.background275 == background275) && + (identical(other.background300, background300) || + other.background300 == background300) && + (identical(other.inverse100, inverse100) || + other.inverse100 == inverse100) && + (identical(other.inverse000, inverse000) || + other.inverse000 == inverse000) && + (identical(other.success100, success100) || + other.success100 == success100) && + (identical(other.error100, error100) || + other.error100 == error100) && + (identical(other.teal100, teal100) || other.teal100 == teal100) && + (identical(other.magenta100, magenta100) || + other.magenta100 == magenta100) && + (identical(other.indigo100, indigo100) || + other.indigo100 == indigo100) && + (identical(other.orange100, orange100) || + other.orange100 == orange100) && + (identical(other.purple100, purple100) || + other.purple100 == purple100) && + (identical(other.yellow100, yellow100) || + other.yellow100 == yellow100)); + } + + @override + int get hashCode => Object.hashAll([ + runtimeType, + accent100, + accent090, + accent080, + grayGlass100, + foreground100, + foreground125, + foreground150, + foreground175, + foreground200, + foreground225, + foreground250, + foreground275, + foreground300, + background100, + background125, + background150, + background175, + background200, + background225, + background250, + background275, + background300, + inverse100, + inverse000, + success100, + error100, + teal100, + magenta100, + indigo100, + orange100, + purple100, + yellow100 + ]); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalColorsImplCopyWith<_$AppKitModalColorsImpl> get copyWith => + __$$AppKitModalColorsImplCopyWithImpl<_$AppKitModalColorsImpl>( + this, _$identity); +} + +abstract class _AppKitModalColors implements AppKitModalColors { + const factory _AppKitModalColors( + {required final Color accent100, + required final Color accent090, + required final Color accent080, + required final Color grayGlass100, + required final Color foreground100, + required final Color foreground125, + required final Color foreground150, + required final Color foreground175, + required final Color foreground200, + required final Color foreground225, + required final Color foreground250, + required final Color foreground275, + required final Color foreground300, + required final Color background100, + required final Color background125, + required final Color background150, + required final Color background175, + required final Color background200, + required final Color background225, + required final Color background250, + required final Color background275, + required final Color background300, + required final Color inverse100, + required final Color inverse000, + required final Color success100, + required final Color error100, + required final Color teal100, + required final Color magenta100, + required final Color indigo100, + required final Color orange100, + required final Color purple100, + required final Color yellow100}) = _$AppKitModalColorsImpl; + + @override + Color get accent100; + @override + Color get accent090; + @override + Color get accent080; + @override // + Color get grayGlass100; + @override // + Color get foreground100; + @override + Color get foreground125; + @override + Color get foreground150; + @override + Color get foreground175; + @override + Color get foreground200; + @override + Color get foreground225; + @override + Color get foreground250; + @override + Color get foreground275; + @override + Color get foreground300; + @override // + Color get background100; + @override + Color get background125; + @override + Color get background150; + @override + Color get background175; + @override + Color get background200; + @override + Color get background225; + @override + Color get background250; + @override + Color get background275; + @override + Color get background300; + @override // + Color get inverse100; + @override + Color get inverse000; + @override + Color get success100; + @override + Color get error100; + @override + Color get teal100; + @override + Color get magenta100; + @override + Color get indigo100; + @override + Color get orange100; + @override + Color get purple100; + @override + Color get yellow100; + @override + @JsonKey(ignore: true) + _$$AppKitModalColorsImplCopyWith<_$AppKitModalColorsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/appkit_modal_radiuses.freezed.dart b/packages/reown_appkit/lib/modal/theme/appkit_modal_radiuses.freezed.dart new file mode 100644 index 0000000..a8620aa --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/appkit_modal_radiuses.freezed.dart @@ -0,0 +1,291 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_modal_radiuses.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalRadiuses { + double get radius4XS => throw _privateConstructorUsedError; + double get radius3XS => throw _privateConstructorUsedError; + double get radius2XS => throw _privateConstructorUsedError; + double get radiusXS => throw _privateConstructorUsedError; + double get radiusS => throw _privateConstructorUsedError; + double get radiusM => throw _privateConstructorUsedError; + double get radiusL => throw _privateConstructorUsedError; + double get radius3XL => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalRadiusesCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalRadiusesCopyWith<$Res> { + factory $AppKitModalRadiusesCopyWith( + AppKitModalRadiuses value, $Res Function(AppKitModalRadiuses) then) = + _$AppKitModalRadiusesCopyWithImpl<$Res, AppKitModalRadiuses>; + @useResult + $Res call( + {double radius4XS, + double radius3XS, + double radius2XS, + double radiusXS, + double radiusS, + double radiusM, + double radiusL, + double radius3XL}); +} + +/// @nodoc +class _$AppKitModalRadiusesCopyWithImpl<$Res, $Val extends AppKitModalRadiuses> + implements $AppKitModalRadiusesCopyWith<$Res> { + _$AppKitModalRadiusesCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? radius4XS = null, + Object? radius3XS = null, + Object? radius2XS = null, + Object? radiusXS = null, + Object? radiusS = null, + Object? radiusM = null, + Object? radiusL = null, + Object? radius3XL = null, + }) { + return _then(_value.copyWith( + radius4XS: null == radius4XS + ? _value.radius4XS + : radius4XS // ignore: cast_nullable_to_non_nullable + as double, + radius3XS: null == radius3XS + ? _value.radius3XS + : radius3XS // ignore: cast_nullable_to_non_nullable + as double, + radius2XS: null == radius2XS + ? _value.radius2XS + : radius2XS // ignore: cast_nullable_to_non_nullable + as double, + radiusXS: null == radiusXS + ? _value.radiusXS + : radiusXS // ignore: cast_nullable_to_non_nullable + as double, + radiusS: null == radiusS + ? _value.radiusS + : radiusS // ignore: cast_nullable_to_non_nullable + as double, + radiusM: null == radiusM + ? _value.radiusM + : radiusM // ignore: cast_nullable_to_non_nullable + as double, + radiusL: null == radiusL + ? _value.radiusL + : radiusL // ignore: cast_nullable_to_non_nullable + as double, + radius3XL: null == radius3XL + ? _value.radius3XL + : radius3XL // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalRadiusesImplCopyWith<$Res> + implements $AppKitModalRadiusesCopyWith<$Res> { + factory _$$AppKitModalRadiusesImplCopyWith(_$AppKitModalRadiusesImpl value, + $Res Function(_$AppKitModalRadiusesImpl) then) = + __$$AppKitModalRadiusesImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {double radius4XS, + double radius3XS, + double radius2XS, + double radiusXS, + double radiusS, + double radiusM, + double radiusL, + double radius3XL}); +} + +/// @nodoc +class __$$AppKitModalRadiusesImplCopyWithImpl<$Res> + extends _$AppKitModalRadiusesCopyWithImpl<$Res, _$AppKitModalRadiusesImpl> + implements _$$AppKitModalRadiusesImplCopyWith<$Res> { + __$$AppKitModalRadiusesImplCopyWithImpl(_$AppKitModalRadiusesImpl _value, + $Res Function(_$AppKitModalRadiusesImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? radius4XS = null, + Object? radius3XS = null, + Object? radius2XS = null, + Object? radiusXS = null, + Object? radiusS = null, + Object? radiusM = null, + Object? radiusL = null, + Object? radius3XL = null, + }) { + return _then(_$AppKitModalRadiusesImpl( + radius4XS: null == radius4XS + ? _value.radius4XS + : radius4XS // ignore: cast_nullable_to_non_nullable + as double, + radius3XS: null == radius3XS + ? _value.radius3XS + : radius3XS // ignore: cast_nullable_to_non_nullable + as double, + radius2XS: null == radius2XS + ? _value.radius2XS + : radius2XS // ignore: cast_nullable_to_non_nullable + as double, + radiusXS: null == radiusXS + ? _value.radiusXS + : radiusXS // ignore: cast_nullable_to_non_nullable + as double, + radiusS: null == radiusS + ? _value.radiusS + : radiusS // ignore: cast_nullable_to_non_nullable + as double, + radiusM: null == radiusM + ? _value.radiusM + : radiusM // ignore: cast_nullable_to_non_nullable + as double, + radiusL: null == radiusL + ? _value.radiusL + : radiusL // ignore: cast_nullable_to_non_nullable + as double, + radius3XL: null == radius3XL + ? _value.radius3XL + : radius3XL // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc + +class _$AppKitModalRadiusesImpl implements _AppKitModalRadiuses { + const _$AppKitModalRadiusesImpl( + {this.radius4XS = 6.0, + this.radius3XS = 8.0, + this.radius2XS = 12.0, + this.radiusXS = 16.0, + this.radiusS = 20.0, + this.radiusM = 28.0, + this.radiusL = 36.0, + this.radius3XL = 80.0}); + + @override + @JsonKey() + final double radius4XS; + @override + @JsonKey() + final double radius3XS; + @override + @JsonKey() + final double radius2XS; + @override + @JsonKey() + final double radiusXS; + @override + @JsonKey() + final double radiusS; + @override + @JsonKey() + final double radiusM; + @override + @JsonKey() + final double radiusL; + @override + @JsonKey() + final double radius3XL; + + @override + String toString() { + return 'AppKitModalRadiuses(radius4XS: $radius4XS, radius3XS: $radius3XS, radius2XS: $radius2XS, radiusXS: $radiusXS, radiusS: $radiusS, radiusM: $radiusM, radiusL: $radiusL, radius3XL: $radius3XL)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalRadiusesImpl && + (identical(other.radius4XS, radius4XS) || + other.radius4XS == radius4XS) && + (identical(other.radius3XS, radius3XS) || + other.radius3XS == radius3XS) && + (identical(other.radius2XS, radius2XS) || + other.radius2XS == radius2XS) && + (identical(other.radiusXS, radiusXS) || + other.radiusXS == radiusXS) && + (identical(other.radiusS, radiusS) || other.radiusS == radiusS) && + (identical(other.radiusM, radiusM) || other.radiusM == radiusM) && + (identical(other.radiusL, radiusL) || other.radiusL == radiusL) && + (identical(other.radius3XL, radius3XL) || + other.radius3XL == radius3XL)); + } + + @override + int get hashCode => Object.hash(runtimeType, radius4XS, radius3XS, radius2XS, + radiusXS, radiusS, radiusM, radiusL, radius3XL); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalRadiusesImplCopyWith<_$AppKitModalRadiusesImpl> get copyWith => + __$$AppKitModalRadiusesImplCopyWithImpl<_$AppKitModalRadiusesImpl>( + this, _$identity); +} + +abstract class _AppKitModalRadiuses implements AppKitModalRadiuses { + const factory _AppKitModalRadiuses( + {final double radius4XS, + final double radius3XS, + final double radius2XS, + final double radiusXS, + final double radiusS, + final double radiusM, + final double radiusL, + final double radius3XL}) = _$AppKitModalRadiusesImpl; + + @override + double get radius4XS; + @override + double get radius3XS; + @override + double get radius2XS; + @override + double get radiusXS; + @override + double get radiusS; + @override + double get radiusM; + @override + double get radiusL; + @override + double get radius3XL; + @override + @JsonKey(ignore: true) + _$$AppKitModalRadiusesImplCopyWith<_$AppKitModalRadiusesImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/appkit_modal_text_styles.freezed.dart b/packages/reown_appkit/lib/modal/theme/appkit_modal_text_styles.freezed.dart new file mode 100644 index 0000000..96f2a05 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/appkit_modal_text_styles.freezed.dart @@ -0,0 +1,514 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_modal_text_styles.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalTextStyles { + String get fontFamily => throw _privateConstructorUsedError; + TextStyle get title400 => throw _privateConstructorUsedError; + TextStyle get title500 => throw _privateConstructorUsedError; + TextStyle get title600 => throw _privateConstructorUsedError; + TextStyle get large400 => throw _privateConstructorUsedError; + TextStyle get large500 => throw _privateConstructorUsedError; + TextStyle get large600 => throw _privateConstructorUsedError; + TextStyle get paragraph400 => throw _privateConstructorUsedError; + TextStyle get paragraph500 => throw _privateConstructorUsedError; + TextStyle get paragraph600 => throw _privateConstructorUsedError; + TextStyle get small400 => throw _privateConstructorUsedError; + TextStyle get small500 => throw _privateConstructorUsedError; + TextStyle get small600 => throw _privateConstructorUsedError; + TextStyle get tiny400 => throw _privateConstructorUsedError; + TextStyle get tiny500 => throw _privateConstructorUsedError; + TextStyle get tiny600 => throw _privateConstructorUsedError; + TextStyle get micro600 => throw _privateConstructorUsedError; + TextStyle get micro700 => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalTextStylesCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalTextStylesCopyWith<$Res> { + factory $AppKitModalTextStylesCopyWith(AppKitModalTextStyles value, + $Res Function(AppKitModalTextStyles) then) = + _$AppKitModalTextStylesCopyWithImpl<$Res, AppKitModalTextStyles>; + @useResult + $Res call( + {String fontFamily, + TextStyle title400, + TextStyle title500, + TextStyle title600, + TextStyle large400, + TextStyle large500, + TextStyle large600, + TextStyle paragraph400, + TextStyle paragraph500, + TextStyle paragraph600, + TextStyle small400, + TextStyle small500, + TextStyle small600, + TextStyle tiny400, + TextStyle tiny500, + TextStyle tiny600, + TextStyle micro600, + TextStyle micro700}); +} + +/// @nodoc +class _$AppKitModalTextStylesCopyWithImpl<$Res, + $Val extends AppKitModalTextStyles> + implements $AppKitModalTextStylesCopyWith<$Res> { + _$AppKitModalTextStylesCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fontFamily = null, + Object? title400 = null, + Object? title500 = null, + Object? title600 = null, + Object? large400 = null, + Object? large500 = null, + Object? large600 = null, + Object? paragraph400 = null, + Object? paragraph500 = null, + Object? paragraph600 = null, + Object? small400 = null, + Object? small500 = null, + Object? small600 = null, + Object? tiny400 = null, + Object? tiny500 = null, + Object? tiny600 = null, + Object? micro600 = null, + Object? micro700 = null, + }) { + return _then(_value.copyWith( + fontFamily: null == fontFamily + ? _value.fontFamily + : fontFamily // ignore: cast_nullable_to_non_nullable + as String, + title400: null == title400 + ? _value.title400 + : title400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title500: null == title500 + ? _value.title500 + : title500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title600: null == title600 + ? _value.title600 + : title600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large400: null == large400 + ? _value.large400 + : large400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large500: null == large500 + ? _value.large500 + : large500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large600: null == large600 + ? _value.large600 + : large600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph400: null == paragraph400 + ? _value.paragraph400 + : paragraph400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph500: null == paragraph500 + ? _value.paragraph500 + : paragraph500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph600: null == paragraph600 + ? _value.paragraph600 + : paragraph600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small400: null == small400 + ? _value.small400 + : small400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small500: null == small500 + ? _value.small500 + : small500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small600: null == small600 + ? _value.small600 + : small600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny400: null == tiny400 + ? _value.tiny400 + : tiny400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny500: null == tiny500 + ? _value.tiny500 + : tiny500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny600: null == tiny600 + ? _value.tiny600 + : tiny600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro600: null == micro600 + ? _value.micro600 + : micro600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro700: null == micro700 + ? _value.micro700 + : micro700 // ignore: cast_nullable_to_non_nullable + as TextStyle, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalTextStylesImplCopyWith<$Res> + implements $AppKitModalTextStylesCopyWith<$Res> { + factory _$$AppKitModalTextStylesImplCopyWith( + _$AppKitModalTextStylesImpl value, + $Res Function(_$AppKitModalTextStylesImpl) then) = + __$$AppKitModalTextStylesImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String fontFamily, + TextStyle title400, + TextStyle title500, + TextStyle title600, + TextStyle large400, + TextStyle large500, + TextStyle large600, + TextStyle paragraph400, + TextStyle paragraph500, + TextStyle paragraph600, + TextStyle small400, + TextStyle small500, + TextStyle small600, + TextStyle tiny400, + TextStyle tiny500, + TextStyle tiny600, + TextStyle micro600, + TextStyle micro700}); +} + +/// @nodoc +class __$$AppKitModalTextStylesImplCopyWithImpl<$Res> + extends _$AppKitModalTextStylesCopyWithImpl<$Res, + _$AppKitModalTextStylesImpl> + implements _$$AppKitModalTextStylesImplCopyWith<$Res> { + __$$AppKitModalTextStylesImplCopyWithImpl(_$AppKitModalTextStylesImpl _value, + $Res Function(_$AppKitModalTextStylesImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fontFamily = null, + Object? title400 = null, + Object? title500 = null, + Object? title600 = null, + Object? large400 = null, + Object? large500 = null, + Object? large600 = null, + Object? paragraph400 = null, + Object? paragraph500 = null, + Object? paragraph600 = null, + Object? small400 = null, + Object? small500 = null, + Object? small600 = null, + Object? tiny400 = null, + Object? tiny500 = null, + Object? tiny600 = null, + Object? micro600 = null, + Object? micro700 = null, + }) { + return _then(_$AppKitModalTextStylesImpl( + fontFamily: null == fontFamily + ? _value.fontFamily + : fontFamily // ignore: cast_nullable_to_non_nullable + as String, + title400: null == title400 + ? _value.title400 + : title400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title500: null == title500 + ? _value.title500 + : title500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title600: null == title600 + ? _value.title600 + : title600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large400: null == large400 + ? _value.large400 + : large400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large500: null == large500 + ? _value.large500 + : large500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large600: null == large600 + ? _value.large600 + : large600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph400: null == paragraph400 + ? _value.paragraph400 + : paragraph400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph500: null == paragraph500 + ? _value.paragraph500 + : paragraph500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph600: null == paragraph600 + ? _value.paragraph600 + : paragraph600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small400: null == small400 + ? _value.small400 + : small400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small500: null == small500 + ? _value.small500 + : small500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small600: null == small600 + ? _value.small600 + : small600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny400: null == tiny400 + ? _value.tiny400 + : tiny400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny500: null == tiny500 + ? _value.tiny500 + : tiny500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny600: null == tiny600 + ? _value.tiny600 + : tiny600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro600: null == micro600 + ? _value.micro600 + : micro600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro700: null == micro700 + ? _value.micro700 + : micro700 // ignore: cast_nullable_to_non_nullable + as TextStyle, + )); + } +} + +/// @nodoc + +class _$AppKitModalTextStylesImpl implements _AppKitModalTextStyles { + const _$AppKitModalTextStylesImpl( + {required this.fontFamily, + required this.title400, + required this.title500, + required this.title600, + required this.large400, + required this.large500, + required this.large600, + required this.paragraph400, + required this.paragraph500, + required this.paragraph600, + required this.small400, + required this.small500, + required this.small600, + required this.tiny400, + required this.tiny500, + required this.tiny600, + required this.micro600, + required this.micro700}); + + @override + final String fontFamily; + @override + final TextStyle title400; + @override + final TextStyle title500; + @override + final TextStyle title600; + @override + final TextStyle large400; + @override + final TextStyle large500; + @override + final TextStyle large600; + @override + final TextStyle paragraph400; + @override + final TextStyle paragraph500; + @override + final TextStyle paragraph600; + @override + final TextStyle small400; + @override + final TextStyle small500; + @override + final TextStyle small600; + @override + final TextStyle tiny400; + @override + final TextStyle tiny500; + @override + final TextStyle tiny600; + @override + final TextStyle micro600; + @override + final TextStyle micro700; + + @override + String toString() { + return 'AppKitModalTextStyles(fontFamily: $fontFamily, title400: $title400, title500: $title500, title600: $title600, large400: $large400, large500: $large500, large600: $large600, paragraph400: $paragraph400, paragraph500: $paragraph500, paragraph600: $paragraph600, small400: $small400, small500: $small500, small600: $small600, tiny400: $tiny400, tiny500: $tiny500, tiny600: $tiny600, micro600: $micro600, micro700: $micro700)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalTextStylesImpl && + (identical(other.fontFamily, fontFamily) || + other.fontFamily == fontFamily) && + (identical(other.title400, title400) || + other.title400 == title400) && + (identical(other.title500, title500) || + other.title500 == title500) && + (identical(other.title600, title600) || + other.title600 == title600) && + (identical(other.large400, large400) || + other.large400 == large400) && + (identical(other.large500, large500) || + other.large500 == large500) && + (identical(other.large600, large600) || + other.large600 == large600) && + (identical(other.paragraph400, paragraph400) || + other.paragraph400 == paragraph400) && + (identical(other.paragraph500, paragraph500) || + other.paragraph500 == paragraph500) && + (identical(other.paragraph600, paragraph600) || + other.paragraph600 == paragraph600) && + (identical(other.small400, small400) || + other.small400 == small400) && + (identical(other.small500, small500) || + other.small500 == small500) && + (identical(other.small600, small600) || + other.small600 == small600) && + (identical(other.tiny400, tiny400) || other.tiny400 == tiny400) && + (identical(other.tiny500, tiny500) || other.tiny500 == tiny500) && + (identical(other.tiny600, tiny600) || other.tiny600 == tiny600) && + (identical(other.micro600, micro600) || + other.micro600 == micro600) && + (identical(other.micro700, micro700) || + other.micro700 == micro700)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + fontFamily, + title400, + title500, + title600, + large400, + large500, + large600, + paragraph400, + paragraph500, + paragraph600, + small400, + small500, + small600, + tiny400, + tiny500, + tiny600, + micro600, + micro700); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalTextStylesImplCopyWith<_$AppKitModalTextStylesImpl> + get copyWith => __$$AppKitModalTextStylesImplCopyWithImpl< + _$AppKitModalTextStylesImpl>(this, _$identity); +} + +abstract class _AppKitModalTextStyles implements AppKitModalTextStyles { + const factory _AppKitModalTextStyles( + {required final String fontFamily, + required final TextStyle title400, + required final TextStyle title500, + required final TextStyle title600, + required final TextStyle large400, + required final TextStyle large500, + required final TextStyle large600, + required final TextStyle paragraph400, + required final TextStyle paragraph500, + required final TextStyle paragraph600, + required final TextStyle small400, + required final TextStyle small500, + required final TextStyle small600, + required final TextStyle tiny400, + required final TextStyle tiny500, + required final TextStyle tiny600, + required final TextStyle micro600, + required final TextStyle micro700}) = _$AppKitModalTextStylesImpl; + + @override + String get fontFamily; + @override + TextStyle get title400; + @override + TextStyle get title500; + @override + TextStyle get title600; + @override + TextStyle get large400; + @override + TextStyle get large500; + @override + TextStyle get large600; + @override + TextStyle get paragraph400; + @override + TextStyle get paragraph500; + @override + TextStyle get paragraph600; + @override + TextStyle get small400; + @override + TextStyle get small500; + @override + TextStyle get small600; + @override + TextStyle get tiny400; + @override + TextStyle get tiny500; + @override + TextStyle get tiny600; + @override + TextStyle get micro600; + @override + TextStyle get micro700; + @override + @JsonKey(ignore: true) + _$$AppKitModalTextStylesImplCopyWith<_$AppKitModalTextStylesImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/appkit_modal_theme_data.freezed.dart b/packages/reown_appkit/lib/modal/theme/appkit_modal_theme_data.freezed.dart new file mode 100644 index 0000000..6c39a05 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/appkit_modal_theme_data.freezed.dart @@ -0,0 +1,254 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_modal_theme_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalThemeData { + AppKitModalColors get lightColors => throw _privateConstructorUsedError; + AppKitModalColors get darkColors => throw _privateConstructorUsedError; + AppKitModalTextStyles get textStyles => throw _privateConstructorUsedError; + AppKitModalRadiuses get radiuses => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalThemeDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalThemeDataCopyWith<$Res> { + factory $AppKitModalThemeDataCopyWith(AppKitModalThemeData value, + $Res Function(AppKitModalThemeData) then) = + _$AppKitModalThemeDataCopyWithImpl<$Res, AppKitModalThemeData>; + @useResult + $Res call( + {AppKitModalColors lightColors, + AppKitModalColors darkColors, + AppKitModalTextStyles textStyles, + AppKitModalRadiuses radiuses}); + + $AppKitModalColorsCopyWith<$Res> get lightColors; + $AppKitModalColorsCopyWith<$Res> get darkColors; + $AppKitModalTextStylesCopyWith<$Res> get textStyles; + $AppKitModalRadiusesCopyWith<$Res> get radiuses; +} + +/// @nodoc +class _$AppKitModalThemeDataCopyWithImpl<$Res, + $Val extends AppKitModalThemeData> + implements $AppKitModalThemeDataCopyWith<$Res> { + _$AppKitModalThemeDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lightColors = null, + Object? darkColors = null, + Object? textStyles = null, + Object? radiuses = null, + }) { + return _then(_value.copyWith( + lightColors: null == lightColors + ? _value.lightColors + : lightColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + darkColors: null == darkColors + ? _value.darkColors + : darkColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + textStyles: null == textStyles + ? _value.textStyles + : textStyles // ignore: cast_nullable_to_non_nullable + as AppKitModalTextStyles, + radiuses: null == radiuses + ? _value.radiuses + : radiuses // ignore: cast_nullable_to_non_nullable + as AppKitModalRadiuses, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalColorsCopyWith<$Res> get lightColors { + return $AppKitModalColorsCopyWith<$Res>(_value.lightColors, (value) { + return _then(_value.copyWith(lightColors: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalColorsCopyWith<$Res> get darkColors { + return $AppKitModalColorsCopyWith<$Res>(_value.darkColors, (value) { + return _then(_value.copyWith(darkColors: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalTextStylesCopyWith<$Res> get textStyles { + return $AppKitModalTextStylesCopyWith<$Res>(_value.textStyles, (value) { + return _then(_value.copyWith(textStyles: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalRadiusesCopyWith<$Res> get radiuses { + return $AppKitModalRadiusesCopyWith<$Res>(_value.radiuses, (value) { + return _then(_value.copyWith(radiuses: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$AppKitModalThemeDataImplCopyWith<$Res> + implements $AppKitModalThemeDataCopyWith<$Res> { + factory _$$AppKitModalThemeDataImplCopyWith(_$AppKitModalThemeDataImpl value, + $Res Function(_$AppKitModalThemeDataImpl) then) = + __$$AppKitModalThemeDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {AppKitModalColors lightColors, + AppKitModalColors darkColors, + AppKitModalTextStyles textStyles, + AppKitModalRadiuses radiuses}); + + @override + $AppKitModalColorsCopyWith<$Res> get lightColors; + @override + $AppKitModalColorsCopyWith<$Res> get darkColors; + @override + $AppKitModalTextStylesCopyWith<$Res> get textStyles; + @override + $AppKitModalRadiusesCopyWith<$Res> get radiuses; +} + +/// @nodoc +class __$$AppKitModalThemeDataImplCopyWithImpl<$Res> + extends _$AppKitModalThemeDataCopyWithImpl<$Res, _$AppKitModalThemeDataImpl> + implements _$$AppKitModalThemeDataImplCopyWith<$Res> { + __$$AppKitModalThemeDataImplCopyWithImpl(_$AppKitModalThemeDataImpl _value, + $Res Function(_$AppKitModalThemeDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lightColors = null, + Object? darkColors = null, + Object? textStyles = null, + Object? radiuses = null, + }) { + return _then(_$AppKitModalThemeDataImpl( + lightColors: null == lightColors + ? _value.lightColors + : lightColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + darkColors: null == darkColors + ? _value.darkColors + : darkColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + textStyles: null == textStyles + ? _value.textStyles + : textStyles // ignore: cast_nullable_to_non_nullable + as AppKitModalTextStyles, + radiuses: null == radiuses + ? _value.radiuses + : radiuses // ignore: cast_nullable_to_non_nullable + as AppKitModalRadiuses, + )); + } +} + +/// @nodoc + +class _$AppKitModalThemeDataImpl implements _AppKitModalThemeData { + const _$AppKitModalThemeDataImpl( + {this.lightColors = AppKitModalColors.lightMode, + this.darkColors = AppKitModalColors.darkMode, + this.textStyles = AppKitModalTextStyles.textStyle, + this.radiuses = const AppKitModalRadiuses()}); + + @override + @JsonKey() + final AppKitModalColors lightColors; + @override + @JsonKey() + final AppKitModalColors darkColors; + @override + @JsonKey() + final AppKitModalTextStyles textStyles; + @override + @JsonKey() + final AppKitModalRadiuses radiuses; + + @override + String toString() { + return 'AppKitModalThemeData(lightColors: $lightColors, darkColors: $darkColors, textStyles: $textStyles, radiuses: $radiuses)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalThemeDataImpl && + (identical(other.lightColors, lightColors) || + other.lightColors == lightColors) && + (identical(other.darkColors, darkColors) || + other.darkColors == darkColors) && + (identical(other.textStyles, textStyles) || + other.textStyles == textStyles) && + (identical(other.radiuses, radiuses) || + other.radiuses == radiuses)); + } + + @override + int get hashCode => + Object.hash(runtimeType, lightColors, darkColors, textStyles, radiuses); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalThemeDataImplCopyWith<_$AppKitModalThemeDataImpl> + get copyWith => + __$$AppKitModalThemeDataImplCopyWithImpl<_$AppKitModalThemeDataImpl>( + this, _$identity); +} + +abstract class _AppKitModalThemeData implements AppKitModalThemeData { + const factory _AppKitModalThemeData( + {final AppKitModalColors lightColors, + final AppKitModalColors darkColors, + final AppKitModalTextStyles textStyles, + final AppKitModalRadiuses radiuses}) = _$AppKitModalThemeDataImpl; + + @override + AppKitModalColors get lightColors; + @override + AppKitModalColors get darkColors; + @override + AppKitModalTextStyles get textStyles; + @override + AppKitModalRadiuses get radiuses; + @override + @JsonKey(ignore: true) + _$$AppKitModalThemeDataImplCopyWith<_$AppKitModalThemeDataImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_colors.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_colors.dart new file mode 100644 index 0000000..b31f668 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_colors.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'appkit_modal_colors.freezed.dart'; + +@freezed +class AppKitModalColors with _$AppKitModalColors { + const factory AppKitModalColors({ + required Color accent100, + required Color accent090, + required Color accent080, + // + required Color grayGlass100, + // + required Color foreground100, + required Color foreground125, + required Color foreground150, + required Color foreground175, + required Color foreground200, + required Color foreground225, + required Color foreground250, + required Color foreground275, + required Color foreground300, + // + required Color background100, + required Color background125, + required Color background150, + required Color background175, + required Color background200, + required Color background225, + required Color background250, + required Color background275, + required Color background300, + // + required Color inverse100, + required Color inverse000, + required Color success100, + required Color error100, + required Color teal100, + required Color magenta100, + required Color indigo100, + required Color orange100, + required Color purple100, + required Color yellow100, + }) = _AppKitModalColors; + + static const darkMode = AppKitModalColors( + accent100: Color(0xFF667DFF), + accent090: Color(0xFF7388FD), + accent080: Color(0xFF7F92FA), + // + grayGlass100: Color(0xFFFFFFFF), + // + foreground100: Color(0xFFE4E7E7), + foreground125: Color(0xFFD0D5D5), + foreground150: Color(0xFFA8B1B1), + foreground175: Color(0xFFA8B0B0), + foreground200: Color(0xFF949E9E), + foreground225: Color(0xFF868F8F), + foreground250: Color(0xFF788080), + foreground275: Color(0xFF788181), + foreground300: Color(0xFF6E7777), + // + background100: Color(0xFF121313), + background125: Color(0xFF191A1A), + background150: Color(0xFF1E1F1F), + background175: Color(0xFF222525), + background200: Color(0xFF272A2A), + background225: Color(0xFF2C3030), + background250: Color(0xFF313535), + background275: Color(0xFF363B3B), + background300: Color(0xFF3B4040), + // + inverse100: Color(0xFFFFFFFF), + inverse000: Color(0xFF000000), + success100: Color(0xFF26D962), + error100: Color(0xFFF25A67), + teal100: Color(0xFF36E2E2), + magenta100: Color(0xFFCB4D8C), + indigo100: Color(0xFF516DFB), + orange100: Color(0xFFFFA64C), + purple100: Color(0xFF906EF7), + yellow100: Color(0xFFFAFF00), + ); + + static const lightMode = AppKitModalColors( + accent100: Color(0xFF5570FF), + accent090: Color(0xFF4F67E7), + accent080: Color(0xFF485ED0), + // + grayGlass100: Color(0xFF000000), + // + foreground100: Color(0xFF141414), + foreground125: Color(0xFF2D3131), + foreground150: Color(0xFF474D4D), + foreground175: Color(0xFF636D6D), + foreground200: Color(0xFF798686), + foreground225: Color(0xFF828F8F), + foreground250: Color(0xFF8B9797), + foreground275: Color(0xFF95A0A0), + foreground300: Color(0xFF9EA9A9), + // + background100: Color(0xFFFFFFFF), + background125: Color(0xFFFFFFFF), + background150: Color(0xFFF5FAFA), + background175: Color(0xFFF3F8F8), + background200: Color(0xFFEAF1F1), + background225: Color(0xFFE5EDED), + background250: Color(0xFFE1E9E9), + background275: Color(0xFFDCE7E7), + background300: Color(0xFFD8E3E3), + // + inverse100: Color(0xFFFFFFFF), + inverse000: Color(0xFF000000), + success100: Color(0xFF26B562), + error100: Color(0xFFF05142), + teal100: Color(0xFF2BB6B6), + magenta100: Color(0xFFC65380), + indigo100: Color(0xFF3D5CF5), + orange100: Color(0xFFEA8C2E), + purple100: Color(0xFF794CFF), + yellow100: Color(0xFFEECC1C), + ); +} + +extension AppKitColorsExtension on AppKitModalColors { + Color get accenGlass090 => accent100.withOpacity(0.9); + Color get accenGlass080 => accent100.withOpacity(0.8); + Color get accenGlass020 => accent100.withOpacity(0.2); + Color get accenGlass015 => accent100.withOpacity(0.15); + Color get accenGlass010 => accent100.withOpacity(0.1); + Color get accenGlass005 => accent100.withOpacity(0.05); + Color get accenGlass002 => accent100.withOpacity(0.02); + // + Color get grayGlass001 => grayGlass100.withOpacity(0.01); + Color get grayGlass002 => grayGlass100.withOpacity(0.02); + Color get grayGlass005 => grayGlass100.withOpacity(0.05); + Color get grayGlass010 => grayGlass100.withOpacity(0.1); + Color get grayGlass015 => grayGlass100.withOpacity(0.15); + Color get grayGlass020 => grayGlass100.withOpacity(0.2); + Color get grayGlass025 => grayGlass100.withOpacity(0.25); + Color get grayGlass030 => grayGlass100.withOpacity(0.3); + Color get grayGlass060 => grayGlass100.withOpacity(0.6); + Color get grayGlass080 => grayGlass100.withOpacity(0.8); + Color get grayGlass090 => grayGlass100.withOpacity(0.9); +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_colors.freezed.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_colors.freezed.dart new file mode 100644 index 0000000..cf007bc --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_colors.freezed.dart @@ -0,0 +1,826 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_modal_colors.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalColors { + Color get accent100 => throw _privateConstructorUsedError; + Color get accent090 => throw _privateConstructorUsedError; + Color get accent080 => throw _privateConstructorUsedError; // + Color get grayGlass100 => throw _privateConstructorUsedError; // + Color get foreground100 => throw _privateConstructorUsedError; + Color get foreground125 => throw _privateConstructorUsedError; + Color get foreground150 => throw _privateConstructorUsedError; + Color get foreground175 => throw _privateConstructorUsedError; + Color get foreground200 => throw _privateConstructorUsedError; + Color get foreground225 => throw _privateConstructorUsedError; + Color get foreground250 => throw _privateConstructorUsedError; + Color get foreground275 => throw _privateConstructorUsedError; + Color get foreground300 => throw _privateConstructorUsedError; // + Color get background100 => throw _privateConstructorUsedError; + Color get background125 => throw _privateConstructorUsedError; + Color get background150 => throw _privateConstructorUsedError; + Color get background175 => throw _privateConstructorUsedError; + Color get background200 => throw _privateConstructorUsedError; + Color get background225 => throw _privateConstructorUsedError; + Color get background250 => throw _privateConstructorUsedError; + Color get background275 => throw _privateConstructorUsedError; + Color get background300 => throw _privateConstructorUsedError; // + Color get inverse100 => throw _privateConstructorUsedError; + Color get inverse000 => throw _privateConstructorUsedError; + Color get success100 => throw _privateConstructorUsedError; + Color get error100 => throw _privateConstructorUsedError; + Color get teal100 => throw _privateConstructorUsedError; + Color get magenta100 => throw _privateConstructorUsedError; + Color get indigo100 => throw _privateConstructorUsedError; + Color get orange100 => throw _privateConstructorUsedError; + Color get purple100 => throw _privateConstructorUsedError; + Color get yellow100 => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalColorsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalColorsCopyWith<$Res> { + factory $AppKitModalColorsCopyWith( + AppKitModalColors value, $Res Function(AppKitModalColors) then) = + _$AppKitModalColorsCopyWithImpl<$Res, AppKitModalColors>; + @useResult + $Res call( + {Color accent100, + Color accent090, + Color accent080, + Color grayGlass100, + Color foreground100, + Color foreground125, + Color foreground150, + Color foreground175, + Color foreground200, + Color foreground225, + Color foreground250, + Color foreground275, + Color foreground300, + Color background100, + Color background125, + Color background150, + Color background175, + Color background200, + Color background225, + Color background250, + Color background275, + Color background300, + Color inverse100, + Color inverse000, + Color success100, + Color error100, + Color teal100, + Color magenta100, + Color indigo100, + Color orange100, + Color purple100, + Color yellow100}); +} + +/// @nodoc +class _$AppKitModalColorsCopyWithImpl<$Res, $Val extends AppKitModalColors> + implements $AppKitModalColorsCopyWith<$Res> { + _$AppKitModalColorsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accent100 = null, + Object? accent090 = null, + Object? accent080 = null, + Object? grayGlass100 = null, + Object? foreground100 = null, + Object? foreground125 = null, + Object? foreground150 = null, + Object? foreground175 = null, + Object? foreground200 = null, + Object? foreground225 = null, + Object? foreground250 = null, + Object? foreground275 = null, + Object? foreground300 = null, + Object? background100 = null, + Object? background125 = null, + Object? background150 = null, + Object? background175 = null, + Object? background200 = null, + Object? background225 = null, + Object? background250 = null, + Object? background275 = null, + Object? background300 = null, + Object? inverse100 = null, + Object? inverse000 = null, + Object? success100 = null, + Object? error100 = null, + Object? teal100 = null, + Object? magenta100 = null, + Object? indigo100 = null, + Object? orange100 = null, + Object? purple100 = null, + Object? yellow100 = null, + }) { + return _then(_value.copyWith( + accent100: null == accent100 + ? _value.accent100 + : accent100 // ignore: cast_nullable_to_non_nullable + as Color, + accent090: null == accent090 + ? _value.accent090 + : accent090 // ignore: cast_nullable_to_non_nullable + as Color, + accent080: null == accent080 + ? _value.accent080 + : accent080 // ignore: cast_nullable_to_non_nullable + as Color, + grayGlass100: null == grayGlass100 + ? _value.grayGlass100 + : grayGlass100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground100: null == foreground100 + ? _value.foreground100 + : foreground100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground125: null == foreground125 + ? _value.foreground125 + : foreground125 // ignore: cast_nullable_to_non_nullable + as Color, + foreground150: null == foreground150 + ? _value.foreground150 + : foreground150 // ignore: cast_nullable_to_non_nullable + as Color, + foreground175: null == foreground175 + ? _value.foreground175 + : foreground175 // ignore: cast_nullable_to_non_nullable + as Color, + foreground200: null == foreground200 + ? _value.foreground200 + : foreground200 // ignore: cast_nullable_to_non_nullable + as Color, + foreground225: null == foreground225 + ? _value.foreground225 + : foreground225 // ignore: cast_nullable_to_non_nullable + as Color, + foreground250: null == foreground250 + ? _value.foreground250 + : foreground250 // ignore: cast_nullable_to_non_nullable + as Color, + foreground275: null == foreground275 + ? _value.foreground275 + : foreground275 // ignore: cast_nullable_to_non_nullable + as Color, + foreground300: null == foreground300 + ? _value.foreground300 + : foreground300 // ignore: cast_nullable_to_non_nullable + as Color, + background100: null == background100 + ? _value.background100 + : background100 // ignore: cast_nullable_to_non_nullable + as Color, + background125: null == background125 + ? _value.background125 + : background125 // ignore: cast_nullable_to_non_nullable + as Color, + background150: null == background150 + ? _value.background150 + : background150 // ignore: cast_nullable_to_non_nullable + as Color, + background175: null == background175 + ? _value.background175 + : background175 // ignore: cast_nullable_to_non_nullable + as Color, + background200: null == background200 + ? _value.background200 + : background200 // ignore: cast_nullable_to_non_nullable + as Color, + background225: null == background225 + ? _value.background225 + : background225 // ignore: cast_nullable_to_non_nullable + as Color, + background250: null == background250 + ? _value.background250 + : background250 // ignore: cast_nullable_to_non_nullable + as Color, + background275: null == background275 + ? _value.background275 + : background275 // ignore: cast_nullable_to_non_nullable + as Color, + background300: null == background300 + ? _value.background300 + : background300 // ignore: cast_nullable_to_non_nullable + as Color, + inverse100: null == inverse100 + ? _value.inverse100 + : inverse100 // ignore: cast_nullable_to_non_nullable + as Color, + inverse000: null == inverse000 + ? _value.inverse000 + : inverse000 // ignore: cast_nullable_to_non_nullable + as Color, + success100: null == success100 + ? _value.success100 + : success100 // ignore: cast_nullable_to_non_nullable + as Color, + error100: null == error100 + ? _value.error100 + : error100 // ignore: cast_nullable_to_non_nullable + as Color, + teal100: null == teal100 + ? _value.teal100 + : teal100 // ignore: cast_nullable_to_non_nullable + as Color, + magenta100: null == magenta100 + ? _value.magenta100 + : magenta100 // ignore: cast_nullable_to_non_nullable + as Color, + indigo100: null == indigo100 + ? _value.indigo100 + : indigo100 // ignore: cast_nullable_to_non_nullable + as Color, + orange100: null == orange100 + ? _value.orange100 + : orange100 // ignore: cast_nullable_to_non_nullable + as Color, + purple100: null == purple100 + ? _value.purple100 + : purple100 // ignore: cast_nullable_to_non_nullable + as Color, + yellow100: null == yellow100 + ? _value.yellow100 + : yellow100 // ignore: cast_nullable_to_non_nullable + as Color, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalColorsImplCopyWith<$Res> + implements $AppKitModalColorsCopyWith<$Res> { + factory _$$AppKitModalColorsImplCopyWith(_$AppKitModalColorsImpl value, + $Res Function(_$AppKitModalColorsImpl) then) = + __$$AppKitModalColorsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Color accent100, + Color accent090, + Color accent080, + Color grayGlass100, + Color foreground100, + Color foreground125, + Color foreground150, + Color foreground175, + Color foreground200, + Color foreground225, + Color foreground250, + Color foreground275, + Color foreground300, + Color background100, + Color background125, + Color background150, + Color background175, + Color background200, + Color background225, + Color background250, + Color background275, + Color background300, + Color inverse100, + Color inverse000, + Color success100, + Color error100, + Color teal100, + Color magenta100, + Color indigo100, + Color orange100, + Color purple100, + Color yellow100}); +} + +/// @nodoc +class __$$AppKitModalColorsImplCopyWithImpl<$Res> + extends _$AppKitModalColorsCopyWithImpl<$Res, _$AppKitModalColorsImpl> + implements _$$AppKitModalColorsImplCopyWith<$Res> { + __$$AppKitModalColorsImplCopyWithImpl(_$AppKitModalColorsImpl _value, + $Res Function(_$AppKitModalColorsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accent100 = null, + Object? accent090 = null, + Object? accent080 = null, + Object? grayGlass100 = null, + Object? foreground100 = null, + Object? foreground125 = null, + Object? foreground150 = null, + Object? foreground175 = null, + Object? foreground200 = null, + Object? foreground225 = null, + Object? foreground250 = null, + Object? foreground275 = null, + Object? foreground300 = null, + Object? background100 = null, + Object? background125 = null, + Object? background150 = null, + Object? background175 = null, + Object? background200 = null, + Object? background225 = null, + Object? background250 = null, + Object? background275 = null, + Object? background300 = null, + Object? inverse100 = null, + Object? inverse000 = null, + Object? success100 = null, + Object? error100 = null, + Object? teal100 = null, + Object? magenta100 = null, + Object? indigo100 = null, + Object? orange100 = null, + Object? purple100 = null, + Object? yellow100 = null, + }) { + return _then(_$AppKitModalColorsImpl( + accent100: null == accent100 + ? _value.accent100 + : accent100 // ignore: cast_nullable_to_non_nullable + as Color, + accent090: null == accent090 + ? _value.accent090 + : accent090 // ignore: cast_nullable_to_non_nullable + as Color, + accent080: null == accent080 + ? _value.accent080 + : accent080 // ignore: cast_nullable_to_non_nullable + as Color, + grayGlass100: null == grayGlass100 + ? _value.grayGlass100 + : grayGlass100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground100: null == foreground100 + ? _value.foreground100 + : foreground100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground125: null == foreground125 + ? _value.foreground125 + : foreground125 // ignore: cast_nullable_to_non_nullable + as Color, + foreground150: null == foreground150 + ? _value.foreground150 + : foreground150 // ignore: cast_nullable_to_non_nullable + as Color, + foreground175: null == foreground175 + ? _value.foreground175 + : foreground175 // ignore: cast_nullable_to_non_nullable + as Color, + foreground200: null == foreground200 + ? _value.foreground200 + : foreground200 // ignore: cast_nullable_to_non_nullable + as Color, + foreground225: null == foreground225 + ? _value.foreground225 + : foreground225 // ignore: cast_nullable_to_non_nullable + as Color, + foreground250: null == foreground250 + ? _value.foreground250 + : foreground250 // ignore: cast_nullable_to_non_nullable + as Color, + foreground275: null == foreground275 + ? _value.foreground275 + : foreground275 // ignore: cast_nullable_to_non_nullable + as Color, + foreground300: null == foreground300 + ? _value.foreground300 + : foreground300 // ignore: cast_nullable_to_non_nullable + as Color, + background100: null == background100 + ? _value.background100 + : background100 // ignore: cast_nullable_to_non_nullable + as Color, + background125: null == background125 + ? _value.background125 + : background125 // ignore: cast_nullable_to_non_nullable + as Color, + background150: null == background150 + ? _value.background150 + : background150 // ignore: cast_nullable_to_non_nullable + as Color, + background175: null == background175 + ? _value.background175 + : background175 // ignore: cast_nullable_to_non_nullable + as Color, + background200: null == background200 + ? _value.background200 + : background200 // ignore: cast_nullable_to_non_nullable + as Color, + background225: null == background225 + ? _value.background225 + : background225 // ignore: cast_nullable_to_non_nullable + as Color, + background250: null == background250 + ? _value.background250 + : background250 // ignore: cast_nullable_to_non_nullable + as Color, + background275: null == background275 + ? _value.background275 + : background275 // ignore: cast_nullable_to_non_nullable + as Color, + background300: null == background300 + ? _value.background300 + : background300 // ignore: cast_nullable_to_non_nullable + as Color, + inverse100: null == inverse100 + ? _value.inverse100 + : inverse100 // ignore: cast_nullable_to_non_nullable + as Color, + inverse000: null == inverse000 + ? _value.inverse000 + : inverse000 // ignore: cast_nullable_to_non_nullable + as Color, + success100: null == success100 + ? _value.success100 + : success100 // ignore: cast_nullable_to_non_nullable + as Color, + error100: null == error100 + ? _value.error100 + : error100 // ignore: cast_nullable_to_non_nullable + as Color, + teal100: null == teal100 + ? _value.teal100 + : teal100 // ignore: cast_nullable_to_non_nullable + as Color, + magenta100: null == magenta100 + ? _value.magenta100 + : magenta100 // ignore: cast_nullable_to_non_nullable + as Color, + indigo100: null == indigo100 + ? _value.indigo100 + : indigo100 // ignore: cast_nullable_to_non_nullable + as Color, + orange100: null == orange100 + ? _value.orange100 + : orange100 // ignore: cast_nullable_to_non_nullable + as Color, + purple100: null == purple100 + ? _value.purple100 + : purple100 // ignore: cast_nullable_to_non_nullable + as Color, + yellow100: null == yellow100 + ? _value.yellow100 + : yellow100 // ignore: cast_nullable_to_non_nullable + as Color, + )); + } +} + +/// @nodoc + +class _$AppKitModalColorsImpl implements _AppKitModalColors { + const _$AppKitModalColorsImpl( + {required this.accent100, + required this.accent090, + required this.accent080, + required this.grayGlass100, + required this.foreground100, + required this.foreground125, + required this.foreground150, + required this.foreground175, + required this.foreground200, + required this.foreground225, + required this.foreground250, + required this.foreground275, + required this.foreground300, + required this.background100, + required this.background125, + required this.background150, + required this.background175, + required this.background200, + required this.background225, + required this.background250, + required this.background275, + required this.background300, + required this.inverse100, + required this.inverse000, + required this.success100, + required this.error100, + required this.teal100, + required this.magenta100, + required this.indigo100, + required this.orange100, + required this.purple100, + required this.yellow100}); + + @override + final Color accent100; + @override + final Color accent090; + @override + final Color accent080; +// + @override + final Color grayGlass100; +// + @override + final Color foreground100; + @override + final Color foreground125; + @override + final Color foreground150; + @override + final Color foreground175; + @override + final Color foreground200; + @override + final Color foreground225; + @override + final Color foreground250; + @override + final Color foreground275; + @override + final Color foreground300; +// + @override + final Color background100; + @override + final Color background125; + @override + final Color background150; + @override + final Color background175; + @override + final Color background200; + @override + final Color background225; + @override + final Color background250; + @override + final Color background275; + @override + final Color background300; +// + @override + final Color inverse100; + @override + final Color inverse000; + @override + final Color success100; + @override + final Color error100; + @override + final Color teal100; + @override + final Color magenta100; + @override + final Color indigo100; + @override + final Color orange100; + @override + final Color purple100; + @override + final Color yellow100; + + @override + String toString() { + return 'AppKitModalColors(accent100: $accent100, accent090: $accent090, accent080: $accent080, grayGlass100: $grayGlass100, foreground100: $foreground100, foreground125: $foreground125, foreground150: $foreground150, foreground175: $foreground175, foreground200: $foreground200, foreground225: $foreground225, foreground250: $foreground250, foreground275: $foreground275, foreground300: $foreground300, background100: $background100, background125: $background125, background150: $background150, background175: $background175, background200: $background200, background225: $background225, background250: $background250, background275: $background275, background300: $background300, inverse100: $inverse100, inverse000: $inverse000, success100: $success100, error100: $error100, teal100: $teal100, magenta100: $magenta100, indigo100: $indigo100, orange100: $orange100, purple100: $purple100, yellow100: $yellow100)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalColorsImpl && + (identical(other.accent100, accent100) || + other.accent100 == accent100) && + (identical(other.accent090, accent090) || + other.accent090 == accent090) && + (identical(other.accent080, accent080) || + other.accent080 == accent080) && + (identical(other.grayGlass100, grayGlass100) || + other.grayGlass100 == grayGlass100) && + (identical(other.foreground100, foreground100) || + other.foreground100 == foreground100) && + (identical(other.foreground125, foreground125) || + other.foreground125 == foreground125) && + (identical(other.foreground150, foreground150) || + other.foreground150 == foreground150) && + (identical(other.foreground175, foreground175) || + other.foreground175 == foreground175) && + (identical(other.foreground200, foreground200) || + other.foreground200 == foreground200) && + (identical(other.foreground225, foreground225) || + other.foreground225 == foreground225) && + (identical(other.foreground250, foreground250) || + other.foreground250 == foreground250) && + (identical(other.foreground275, foreground275) || + other.foreground275 == foreground275) && + (identical(other.foreground300, foreground300) || + other.foreground300 == foreground300) && + (identical(other.background100, background100) || + other.background100 == background100) && + (identical(other.background125, background125) || + other.background125 == background125) && + (identical(other.background150, background150) || + other.background150 == background150) && + (identical(other.background175, background175) || + other.background175 == background175) && + (identical(other.background200, background200) || + other.background200 == background200) && + (identical(other.background225, background225) || + other.background225 == background225) && + (identical(other.background250, background250) || + other.background250 == background250) && + (identical(other.background275, background275) || + other.background275 == background275) && + (identical(other.background300, background300) || + other.background300 == background300) && + (identical(other.inverse100, inverse100) || + other.inverse100 == inverse100) && + (identical(other.inverse000, inverse000) || + other.inverse000 == inverse000) && + (identical(other.success100, success100) || + other.success100 == success100) && + (identical(other.error100, error100) || + other.error100 == error100) && + (identical(other.teal100, teal100) || other.teal100 == teal100) && + (identical(other.magenta100, magenta100) || + other.magenta100 == magenta100) && + (identical(other.indigo100, indigo100) || + other.indigo100 == indigo100) && + (identical(other.orange100, orange100) || + other.orange100 == orange100) && + (identical(other.purple100, purple100) || + other.purple100 == purple100) && + (identical(other.yellow100, yellow100) || + other.yellow100 == yellow100)); + } + + @override + int get hashCode => Object.hashAll([ + runtimeType, + accent100, + accent090, + accent080, + grayGlass100, + foreground100, + foreground125, + foreground150, + foreground175, + foreground200, + foreground225, + foreground250, + foreground275, + foreground300, + background100, + background125, + background150, + background175, + background200, + background225, + background250, + background275, + background300, + inverse100, + inverse000, + success100, + error100, + teal100, + magenta100, + indigo100, + orange100, + purple100, + yellow100 + ]); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalColorsImplCopyWith<_$AppKitModalColorsImpl> get copyWith => + __$$AppKitModalColorsImplCopyWithImpl<_$AppKitModalColorsImpl>( + this, _$identity); +} + +abstract class _AppKitModalColors implements AppKitModalColors { + const factory _AppKitModalColors( + {required final Color accent100, + required final Color accent090, + required final Color accent080, + required final Color grayGlass100, + required final Color foreground100, + required final Color foreground125, + required final Color foreground150, + required final Color foreground175, + required final Color foreground200, + required final Color foreground225, + required final Color foreground250, + required final Color foreground275, + required final Color foreground300, + required final Color background100, + required final Color background125, + required final Color background150, + required final Color background175, + required final Color background200, + required final Color background225, + required final Color background250, + required final Color background275, + required final Color background300, + required final Color inverse100, + required final Color inverse000, + required final Color success100, + required final Color error100, + required final Color teal100, + required final Color magenta100, + required final Color indigo100, + required final Color orange100, + required final Color purple100, + required final Color yellow100}) = _$AppKitModalColorsImpl; + + @override + Color get accent100; + @override + Color get accent090; + @override + Color get accent080; + @override // + Color get grayGlass100; + @override // + Color get foreground100; + @override + Color get foreground125; + @override + Color get foreground150; + @override + Color get foreground175; + @override + Color get foreground200; + @override + Color get foreground225; + @override + Color get foreground250; + @override + Color get foreground275; + @override + Color get foreground300; + @override // + Color get background100; + @override + Color get background125; + @override + Color get background150; + @override + Color get background175; + @override + Color get background200; + @override + Color get background225; + @override + Color get background250; + @override + Color get background275; + @override + Color get background300; + @override // + Color get inverse100; + @override + Color get inverse000; + @override + Color get success100; + @override + Color get error100; + @override + Color get teal100; + @override + Color get magenta100; + @override + Color get indigo100; + @override + Color get orange100; + @override + Color get purple100; + @override + Color get yellow100; + @override + @JsonKey(ignore: true) + _$$AppKitModalColorsImplCopyWith<_$AppKitModalColorsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_radiuses.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_radiuses.dart new file mode 100644 index 0000000..8d999ab --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_radiuses.dart @@ -0,0 +1,63 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'appkit_modal_radiuses.freezed.dart'; + +@freezed +class AppKitModalRadiuses with _$AppKitModalRadiuses { + const factory AppKitModalRadiuses({ + @Default(6.0) double radius4XS, + @Default(8.0) double radius3XS, + @Default(12.0) double radius2XS, + @Default(16.0) double radiusXS, + @Default(20.0) double radiusS, + @Default(28.0) double radiusM, + @Default(36.0) double radiusL, + @Default(80.0) double radius3XL, + }) = _AppKitModalRadiuses; + + static const square = AppKitModalRadiuses( + radius4XS: 0.0, + radius3XS: 0.0, + radius2XS: 0.0, + radiusXS: 0.0, + radiusS: 0.0, + radiusM: 0.0, + radiusL: 0.0, + radius3XL: 0.0, + ); + + static const circular = AppKitModalRadiuses( + radius4XS: 100.0, + radius3XS: 100.0, + radius2XS: 100.0, + radiusXS: 100.0, + radiusS: 100.0, + radiusM: 100.0, + radiusL: 100.0, + radius3XL: 100.0, + ); +} + +extension AppKitModalRadiusesExtension on AppKitModalRadiuses { + bool isSquare() { + return radius4XS == 0.0 && + radius3XS == 0.0 && + radius2XS == 0.0 && + radiusXS == 0.0 && + radiusS == 0.0 && + radiusM == 0.0 && + radiusL == 0.0 && + radius3XL == 0.0; + } + + bool isCircular() { + return radius4XS == 100.0 && + radius3XS == 100.0 && + radius2XS == 100.0 && + radiusXS == 100.0 && + radiusS == 100.0 && + radiusM == 100.0 && + radiusL == 100.0 && + radius3XL == 100.0; + } +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_radiuses.freezed.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_radiuses.freezed.dart new file mode 100644 index 0000000..a8620aa --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_radiuses.freezed.dart @@ -0,0 +1,291 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_modal_radiuses.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalRadiuses { + double get radius4XS => throw _privateConstructorUsedError; + double get radius3XS => throw _privateConstructorUsedError; + double get radius2XS => throw _privateConstructorUsedError; + double get radiusXS => throw _privateConstructorUsedError; + double get radiusS => throw _privateConstructorUsedError; + double get radiusM => throw _privateConstructorUsedError; + double get radiusL => throw _privateConstructorUsedError; + double get radius3XL => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalRadiusesCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalRadiusesCopyWith<$Res> { + factory $AppKitModalRadiusesCopyWith( + AppKitModalRadiuses value, $Res Function(AppKitModalRadiuses) then) = + _$AppKitModalRadiusesCopyWithImpl<$Res, AppKitModalRadiuses>; + @useResult + $Res call( + {double radius4XS, + double radius3XS, + double radius2XS, + double radiusXS, + double radiusS, + double radiusM, + double radiusL, + double radius3XL}); +} + +/// @nodoc +class _$AppKitModalRadiusesCopyWithImpl<$Res, $Val extends AppKitModalRadiuses> + implements $AppKitModalRadiusesCopyWith<$Res> { + _$AppKitModalRadiusesCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? radius4XS = null, + Object? radius3XS = null, + Object? radius2XS = null, + Object? radiusXS = null, + Object? radiusS = null, + Object? radiusM = null, + Object? radiusL = null, + Object? radius3XL = null, + }) { + return _then(_value.copyWith( + radius4XS: null == radius4XS + ? _value.radius4XS + : radius4XS // ignore: cast_nullable_to_non_nullable + as double, + radius3XS: null == radius3XS + ? _value.radius3XS + : radius3XS // ignore: cast_nullable_to_non_nullable + as double, + radius2XS: null == radius2XS + ? _value.radius2XS + : radius2XS // ignore: cast_nullable_to_non_nullable + as double, + radiusXS: null == radiusXS + ? _value.radiusXS + : radiusXS // ignore: cast_nullable_to_non_nullable + as double, + radiusS: null == radiusS + ? _value.radiusS + : radiusS // ignore: cast_nullable_to_non_nullable + as double, + radiusM: null == radiusM + ? _value.radiusM + : radiusM // ignore: cast_nullable_to_non_nullable + as double, + radiusL: null == radiusL + ? _value.radiusL + : radiusL // ignore: cast_nullable_to_non_nullable + as double, + radius3XL: null == radius3XL + ? _value.radius3XL + : radius3XL // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalRadiusesImplCopyWith<$Res> + implements $AppKitModalRadiusesCopyWith<$Res> { + factory _$$AppKitModalRadiusesImplCopyWith(_$AppKitModalRadiusesImpl value, + $Res Function(_$AppKitModalRadiusesImpl) then) = + __$$AppKitModalRadiusesImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {double radius4XS, + double radius3XS, + double radius2XS, + double radiusXS, + double radiusS, + double radiusM, + double radiusL, + double radius3XL}); +} + +/// @nodoc +class __$$AppKitModalRadiusesImplCopyWithImpl<$Res> + extends _$AppKitModalRadiusesCopyWithImpl<$Res, _$AppKitModalRadiusesImpl> + implements _$$AppKitModalRadiusesImplCopyWith<$Res> { + __$$AppKitModalRadiusesImplCopyWithImpl(_$AppKitModalRadiusesImpl _value, + $Res Function(_$AppKitModalRadiusesImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? radius4XS = null, + Object? radius3XS = null, + Object? radius2XS = null, + Object? radiusXS = null, + Object? radiusS = null, + Object? radiusM = null, + Object? radiusL = null, + Object? radius3XL = null, + }) { + return _then(_$AppKitModalRadiusesImpl( + radius4XS: null == radius4XS + ? _value.radius4XS + : radius4XS // ignore: cast_nullable_to_non_nullable + as double, + radius3XS: null == radius3XS + ? _value.radius3XS + : radius3XS // ignore: cast_nullable_to_non_nullable + as double, + radius2XS: null == radius2XS + ? _value.radius2XS + : radius2XS // ignore: cast_nullable_to_non_nullable + as double, + radiusXS: null == radiusXS + ? _value.radiusXS + : radiusXS // ignore: cast_nullable_to_non_nullable + as double, + radiusS: null == radiusS + ? _value.radiusS + : radiusS // ignore: cast_nullable_to_non_nullable + as double, + radiusM: null == radiusM + ? _value.radiusM + : radiusM // ignore: cast_nullable_to_non_nullable + as double, + radiusL: null == radiusL + ? _value.radiusL + : radiusL // ignore: cast_nullable_to_non_nullable + as double, + radius3XL: null == radius3XL + ? _value.radius3XL + : radius3XL // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc + +class _$AppKitModalRadiusesImpl implements _AppKitModalRadiuses { + const _$AppKitModalRadiusesImpl( + {this.radius4XS = 6.0, + this.radius3XS = 8.0, + this.radius2XS = 12.0, + this.radiusXS = 16.0, + this.radiusS = 20.0, + this.radiusM = 28.0, + this.radiusL = 36.0, + this.radius3XL = 80.0}); + + @override + @JsonKey() + final double radius4XS; + @override + @JsonKey() + final double radius3XS; + @override + @JsonKey() + final double radius2XS; + @override + @JsonKey() + final double radiusXS; + @override + @JsonKey() + final double radiusS; + @override + @JsonKey() + final double radiusM; + @override + @JsonKey() + final double radiusL; + @override + @JsonKey() + final double radius3XL; + + @override + String toString() { + return 'AppKitModalRadiuses(radius4XS: $radius4XS, radius3XS: $radius3XS, radius2XS: $radius2XS, radiusXS: $radiusXS, radiusS: $radiusS, radiusM: $radiusM, radiusL: $radiusL, radius3XL: $radius3XL)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalRadiusesImpl && + (identical(other.radius4XS, radius4XS) || + other.radius4XS == radius4XS) && + (identical(other.radius3XS, radius3XS) || + other.radius3XS == radius3XS) && + (identical(other.radius2XS, radius2XS) || + other.radius2XS == radius2XS) && + (identical(other.radiusXS, radiusXS) || + other.radiusXS == radiusXS) && + (identical(other.radiusS, radiusS) || other.radiusS == radiusS) && + (identical(other.radiusM, radiusM) || other.radiusM == radiusM) && + (identical(other.radiusL, radiusL) || other.radiusL == radiusL) && + (identical(other.radius3XL, radius3XL) || + other.radius3XL == radius3XL)); + } + + @override + int get hashCode => Object.hash(runtimeType, radius4XS, radius3XS, radius2XS, + radiusXS, radiusS, radiusM, radiusL, radius3XL); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalRadiusesImplCopyWith<_$AppKitModalRadiusesImpl> get copyWith => + __$$AppKitModalRadiusesImplCopyWithImpl<_$AppKitModalRadiusesImpl>( + this, _$identity); +} + +abstract class _AppKitModalRadiuses implements AppKitModalRadiuses { + const factory _AppKitModalRadiuses( + {final double radius4XS, + final double radius3XS, + final double radius2XS, + final double radiusXS, + final double radiusS, + final double radiusM, + final double radiusL, + final double radius3XL}) = _$AppKitModalRadiusesImpl; + + @override + double get radius4XS; + @override + double get radius3XS; + @override + double get radius2XS; + @override + double get radiusXS; + @override + double get radiusS; + @override + double get radiusM; + @override + double get radiusL; + @override + double get radius3XL; + @override + @JsonKey(ignore: true) + _$$AppKitModalRadiusesImplCopyWith<_$AppKitModalRadiusesImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_text_styles.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_text_styles.dart new file mode 100644 index 0000000..d678df1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_text_styles.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'appkit_modal_text_styles.freezed.dart'; + +@freezed +class AppKitModalTextStyles with _$AppKitModalTextStyles { + const factory AppKitModalTextStyles({ + required String fontFamily, + required TextStyle title400, + required TextStyle title500, + required TextStyle title600, + required TextStyle large400, + required TextStyle large500, + required TextStyle large600, + required TextStyle paragraph400, + required TextStyle paragraph500, + required TextStyle paragraph600, + required TextStyle small400, + required TextStyle small500, + required TextStyle small600, + required TextStyle tiny400, + required TextStyle tiny500, + required TextStyle tiny600, + required TextStyle micro600, + required TextStyle micro700, + }) = _AppKitModalTextStyles; + + static const _ff = '.SF Pro Text'; + + static const textStyle = AppKitModalTextStyles( + fontFamily: _ff, + title400: TextStyle( + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w400, + fontSize: 24.0, + ), + title500: TextStyle( + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w500, + fontSize: 24.0, + ), + title600: TextStyle( + fontWeight: FontWeight.w600, + fontFamily: _ff, + letterSpacing: -0.04, + fontSize: 24.0, + ), + large400: TextStyle( + fontSize: 20.0, + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w400, + ), + large500: TextStyle( + fontSize: 20.0, + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w500, + ), + large600: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.w600, + fontFamily: _ff, + letterSpacing: -0.04, + ), + paragraph400: TextStyle( + fontSize: 16.0, + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w400, + ), + paragraph500: TextStyle( + fontSize: 16.0, + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w500, + ), + paragraph600: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + fontFamily: _ff, + letterSpacing: -0.04, + ), + small400: TextStyle( + fontSize: 14.0, + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w400, + ), + small500: TextStyle( + fontSize: 14.0, + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w500, + ), + small600: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w600, + fontFamily: _ff, + letterSpacing: -0.04, + ), + tiny400: TextStyle( + fontSize: 12.0, + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w400, + ), + tiny500: TextStyle( + fontSize: 12.0, + fontFamily: _ff, + letterSpacing: -0.04, + fontWeight: FontWeight.w500, + ), + tiny600: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, + fontFamily: _ff, + letterSpacing: -0.04, + ), + micro600: TextStyle( + fontSize: 10.0, + fontWeight: FontWeight.w600, + letterSpacing: 0.02, + fontFamily: _ff, + ), + micro700: TextStyle( + fontSize: 10.0, + fontWeight: FontWeight.w700, + letterSpacing: 0.02, + fontFamily: _ff, + ), + ); +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_text_styles.freezed.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_text_styles.freezed.dart new file mode 100644 index 0000000..96f2a05 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_text_styles.freezed.dart @@ -0,0 +1,514 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_modal_text_styles.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalTextStyles { + String get fontFamily => throw _privateConstructorUsedError; + TextStyle get title400 => throw _privateConstructorUsedError; + TextStyle get title500 => throw _privateConstructorUsedError; + TextStyle get title600 => throw _privateConstructorUsedError; + TextStyle get large400 => throw _privateConstructorUsedError; + TextStyle get large500 => throw _privateConstructorUsedError; + TextStyle get large600 => throw _privateConstructorUsedError; + TextStyle get paragraph400 => throw _privateConstructorUsedError; + TextStyle get paragraph500 => throw _privateConstructorUsedError; + TextStyle get paragraph600 => throw _privateConstructorUsedError; + TextStyle get small400 => throw _privateConstructorUsedError; + TextStyle get small500 => throw _privateConstructorUsedError; + TextStyle get small600 => throw _privateConstructorUsedError; + TextStyle get tiny400 => throw _privateConstructorUsedError; + TextStyle get tiny500 => throw _privateConstructorUsedError; + TextStyle get tiny600 => throw _privateConstructorUsedError; + TextStyle get micro600 => throw _privateConstructorUsedError; + TextStyle get micro700 => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalTextStylesCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalTextStylesCopyWith<$Res> { + factory $AppKitModalTextStylesCopyWith(AppKitModalTextStyles value, + $Res Function(AppKitModalTextStyles) then) = + _$AppKitModalTextStylesCopyWithImpl<$Res, AppKitModalTextStyles>; + @useResult + $Res call( + {String fontFamily, + TextStyle title400, + TextStyle title500, + TextStyle title600, + TextStyle large400, + TextStyle large500, + TextStyle large600, + TextStyle paragraph400, + TextStyle paragraph500, + TextStyle paragraph600, + TextStyle small400, + TextStyle small500, + TextStyle small600, + TextStyle tiny400, + TextStyle tiny500, + TextStyle tiny600, + TextStyle micro600, + TextStyle micro700}); +} + +/// @nodoc +class _$AppKitModalTextStylesCopyWithImpl<$Res, + $Val extends AppKitModalTextStyles> + implements $AppKitModalTextStylesCopyWith<$Res> { + _$AppKitModalTextStylesCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fontFamily = null, + Object? title400 = null, + Object? title500 = null, + Object? title600 = null, + Object? large400 = null, + Object? large500 = null, + Object? large600 = null, + Object? paragraph400 = null, + Object? paragraph500 = null, + Object? paragraph600 = null, + Object? small400 = null, + Object? small500 = null, + Object? small600 = null, + Object? tiny400 = null, + Object? tiny500 = null, + Object? tiny600 = null, + Object? micro600 = null, + Object? micro700 = null, + }) { + return _then(_value.copyWith( + fontFamily: null == fontFamily + ? _value.fontFamily + : fontFamily // ignore: cast_nullable_to_non_nullable + as String, + title400: null == title400 + ? _value.title400 + : title400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title500: null == title500 + ? _value.title500 + : title500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title600: null == title600 + ? _value.title600 + : title600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large400: null == large400 + ? _value.large400 + : large400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large500: null == large500 + ? _value.large500 + : large500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large600: null == large600 + ? _value.large600 + : large600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph400: null == paragraph400 + ? _value.paragraph400 + : paragraph400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph500: null == paragraph500 + ? _value.paragraph500 + : paragraph500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph600: null == paragraph600 + ? _value.paragraph600 + : paragraph600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small400: null == small400 + ? _value.small400 + : small400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small500: null == small500 + ? _value.small500 + : small500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small600: null == small600 + ? _value.small600 + : small600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny400: null == tiny400 + ? _value.tiny400 + : tiny400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny500: null == tiny500 + ? _value.tiny500 + : tiny500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny600: null == tiny600 + ? _value.tiny600 + : tiny600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro600: null == micro600 + ? _value.micro600 + : micro600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro700: null == micro700 + ? _value.micro700 + : micro700 // ignore: cast_nullable_to_non_nullable + as TextStyle, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalTextStylesImplCopyWith<$Res> + implements $AppKitModalTextStylesCopyWith<$Res> { + factory _$$AppKitModalTextStylesImplCopyWith( + _$AppKitModalTextStylesImpl value, + $Res Function(_$AppKitModalTextStylesImpl) then) = + __$$AppKitModalTextStylesImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String fontFamily, + TextStyle title400, + TextStyle title500, + TextStyle title600, + TextStyle large400, + TextStyle large500, + TextStyle large600, + TextStyle paragraph400, + TextStyle paragraph500, + TextStyle paragraph600, + TextStyle small400, + TextStyle small500, + TextStyle small600, + TextStyle tiny400, + TextStyle tiny500, + TextStyle tiny600, + TextStyle micro600, + TextStyle micro700}); +} + +/// @nodoc +class __$$AppKitModalTextStylesImplCopyWithImpl<$Res> + extends _$AppKitModalTextStylesCopyWithImpl<$Res, + _$AppKitModalTextStylesImpl> + implements _$$AppKitModalTextStylesImplCopyWith<$Res> { + __$$AppKitModalTextStylesImplCopyWithImpl(_$AppKitModalTextStylesImpl _value, + $Res Function(_$AppKitModalTextStylesImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fontFamily = null, + Object? title400 = null, + Object? title500 = null, + Object? title600 = null, + Object? large400 = null, + Object? large500 = null, + Object? large600 = null, + Object? paragraph400 = null, + Object? paragraph500 = null, + Object? paragraph600 = null, + Object? small400 = null, + Object? small500 = null, + Object? small600 = null, + Object? tiny400 = null, + Object? tiny500 = null, + Object? tiny600 = null, + Object? micro600 = null, + Object? micro700 = null, + }) { + return _then(_$AppKitModalTextStylesImpl( + fontFamily: null == fontFamily + ? _value.fontFamily + : fontFamily // ignore: cast_nullable_to_non_nullable + as String, + title400: null == title400 + ? _value.title400 + : title400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title500: null == title500 + ? _value.title500 + : title500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title600: null == title600 + ? _value.title600 + : title600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large400: null == large400 + ? _value.large400 + : large400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large500: null == large500 + ? _value.large500 + : large500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large600: null == large600 + ? _value.large600 + : large600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph400: null == paragraph400 + ? _value.paragraph400 + : paragraph400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph500: null == paragraph500 + ? _value.paragraph500 + : paragraph500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph600: null == paragraph600 + ? _value.paragraph600 + : paragraph600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small400: null == small400 + ? _value.small400 + : small400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small500: null == small500 + ? _value.small500 + : small500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small600: null == small600 + ? _value.small600 + : small600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny400: null == tiny400 + ? _value.tiny400 + : tiny400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny500: null == tiny500 + ? _value.tiny500 + : tiny500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny600: null == tiny600 + ? _value.tiny600 + : tiny600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro600: null == micro600 + ? _value.micro600 + : micro600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro700: null == micro700 + ? _value.micro700 + : micro700 // ignore: cast_nullable_to_non_nullable + as TextStyle, + )); + } +} + +/// @nodoc + +class _$AppKitModalTextStylesImpl implements _AppKitModalTextStyles { + const _$AppKitModalTextStylesImpl( + {required this.fontFamily, + required this.title400, + required this.title500, + required this.title600, + required this.large400, + required this.large500, + required this.large600, + required this.paragraph400, + required this.paragraph500, + required this.paragraph600, + required this.small400, + required this.small500, + required this.small600, + required this.tiny400, + required this.tiny500, + required this.tiny600, + required this.micro600, + required this.micro700}); + + @override + final String fontFamily; + @override + final TextStyle title400; + @override + final TextStyle title500; + @override + final TextStyle title600; + @override + final TextStyle large400; + @override + final TextStyle large500; + @override + final TextStyle large600; + @override + final TextStyle paragraph400; + @override + final TextStyle paragraph500; + @override + final TextStyle paragraph600; + @override + final TextStyle small400; + @override + final TextStyle small500; + @override + final TextStyle small600; + @override + final TextStyle tiny400; + @override + final TextStyle tiny500; + @override + final TextStyle tiny600; + @override + final TextStyle micro600; + @override + final TextStyle micro700; + + @override + String toString() { + return 'AppKitModalTextStyles(fontFamily: $fontFamily, title400: $title400, title500: $title500, title600: $title600, large400: $large400, large500: $large500, large600: $large600, paragraph400: $paragraph400, paragraph500: $paragraph500, paragraph600: $paragraph600, small400: $small400, small500: $small500, small600: $small600, tiny400: $tiny400, tiny500: $tiny500, tiny600: $tiny600, micro600: $micro600, micro700: $micro700)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalTextStylesImpl && + (identical(other.fontFamily, fontFamily) || + other.fontFamily == fontFamily) && + (identical(other.title400, title400) || + other.title400 == title400) && + (identical(other.title500, title500) || + other.title500 == title500) && + (identical(other.title600, title600) || + other.title600 == title600) && + (identical(other.large400, large400) || + other.large400 == large400) && + (identical(other.large500, large500) || + other.large500 == large500) && + (identical(other.large600, large600) || + other.large600 == large600) && + (identical(other.paragraph400, paragraph400) || + other.paragraph400 == paragraph400) && + (identical(other.paragraph500, paragraph500) || + other.paragraph500 == paragraph500) && + (identical(other.paragraph600, paragraph600) || + other.paragraph600 == paragraph600) && + (identical(other.small400, small400) || + other.small400 == small400) && + (identical(other.small500, small500) || + other.small500 == small500) && + (identical(other.small600, small600) || + other.small600 == small600) && + (identical(other.tiny400, tiny400) || other.tiny400 == tiny400) && + (identical(other.tiny500, tiny500) || other.tiny500 == tiny500) && + (identical(other.tiny600, tiny600) || other.tiny600 == tiny600) && + (identical(other.micro600, micro600) || + other.micro600 == micro600) && + (identical(other.micro700, micro700) || + other.micro700 == micro700)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + fontFamily, + title400, + title500, + title600, + large400, + large500, + large600, + paragraph400, + paragraph500, + paragraph600, + small400, + small500, + small600, + tiny400, + tiny500, + tiny600, + micro600, + micro700); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalTextStylesImplCopyWith<_$AppKitModalTextStylesImpl> + get copyWith => __$$AppKitModalTextStylesImplCopyWithImpl< + _$AppKitModalTextStylesImpl>(this, _$identity); +} + +abstract class _AppKitModalTextStyles implements AppKitModalTextStyles { + const factory _AppKitModalTextStyles( + {required final String fontFamily, + required final TextStyle title400, + required final TextStyle title500, + required final TextStyle title600, + required final TextStyle large400, + required final TextStyle large500, + required final TextStyle large600, + required final TextStyle paragraph400, + required final TextStyle paragraph500, + required final TextStyle paragraph600, + required final TextStyle small400, + required final TextStyle small500, + required final TextStyle small600, + required final TextStyle tiny400, + required final TextStyle tiny500, + required final TextStyle tiny600, + required final TextStyle micro600, + required final TextStyle micro700}) = _$AppKitModalTextStylesImpl; + + @override + String get fontFamily; + @override + TextStyle get title400; + @override + TextStyle get title500; + @override + TextStyle get title600; + @override + TextStyle get large400; + @override + TextStyle get large500; + @override + TextStyle get large600; + @override + TextStyle get paragraph400; + @override + TextStyle get paragraph500; + @override + TextStyle get paragraph600; + @override + TextStyle get small400; + @override + TextStyle get small500; + @override + TextStyle get small600; + @override + TextStyle get tiny400; + @override + TextStyle get tiny500; + @override + TextStyle get tiny600; + @override + TextStyle get micro600; + @override + TextStyle get micro700; + @override + @JsonKey(ignore: true) + _$$AppKitModalTextStylesImplCopyWith<_$AppKitModalTextStylesImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme.dart new file mode 100644 index 0000000..3da205d --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme.dart @@ -0,0 +1,5 @@ +export 'appkit_modal_colors.dart'; +export 'appkit_modal_radiuses.dart'; +export 'appkit_modal_text_styles.dart'; +export 'appkit_modal_theme_data.dart'; +export 'appkit_modal_theme_widget.dart'; diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_data.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_data.dart new file mode 100644 index 0000000..aece6b9 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_data.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_colors.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_radiuses.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_text_styles.dart'; + +part 'appkit_modal_theme_data.freezed.dart'; + +@freezed +class AppKitModalThemeData with _$AppKitModalThemeData { + const factory AppKitModalThemeData({ + @Default(AppKitModalColors.lightMode) AppKitModalColors lightColors, + @Default(AppKitModalColors.darkMode) AppKitModalColors darkColors, + @Default(AppKitModalTextStyles.textStyle) AppKitModalTextStyles textStyles, + @Default(AppKitModalRadiuses()) AppKitModalRadiuses radiuses, + }) = _AppKitModalThemeData; +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_data.freezed.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_data.freezed.dart new file mode 100644 index 0000000..6c39a05 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_data.freezed.dart @@ -0,0 +1,254 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'appkit_modal_theme_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalThemeData { + AppKitModalColors get lightColors => throw _privateConstructorUsedError; + AppKitModalColors get darkColors => throw _privateConstructorUsedError; + AppKitModalTextStyles get textStyles => throw _privateConstructorUsedError; + AppKitModalRadiuses get radiuses => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalThemeDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalThemeDataCopyWith<$Res> { + factory $AppKitModalThemeDataCopyWith(AppKitModalThemeData value, + $Res Function(AppKitModalThemeData) then) = + _$AppKitModalThemeDataCopyWithImpl<$Res, AppKitModalThemeData>; + @useResult + $Res call( + {AppKitModalColors lightColors, + AppKitModalColors darkColors, + AppKitModalTextStyles textStyles, + AppKitModalRadiuses radiuses}); + + $AppKitModalColorsCopyWith<$Res> get lightColors; + $AppKitModalColorsCopyWith<$Res> get darkColors; + $AppKitModalTextStylesCopyWith<$Res> get textStyles; + $AppKitModalRadiusesCopyWith<$Res> get radiuses; +} + +/// @nodoc +class _$AppKitModalThemeDataCopyWithImpl<$Res, + $Val extends AppKitModalThemeData> + implements $AppKitModalThemeDataCopyWith<$Res> { + _$AppKitModalThemeDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lightColors = null, + Object? darkColors = null, + Object? textStyles = null, + Object? radiuses = null, + }) { + return _then(_value.copyWith( + lightColors: null == lightColors + ? _value.lightColors + : lightColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + darkColors: null == darkColors + ? _value.darkColors + : darkColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + textStyles: null == textStyles + ? _value.textStyles + : textStyles // ignore: cast_nullable_to_non_nullable + as AppKitModalTextStyles, + radiuses: null == radiuses + ? _value.radiuses + : radiuses // ignore: cast_nullable_to_non_nullable + as AppKitModalRadiuses, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalColorsCopyWith<$Res> get lightColors { + return $AppKitModalColorsCopyWith<$Res>(_value.lightColors, (value) { + return _then(_value.copyWith(lightColors: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalColorsCopyWith<$Res> get darkColors { + return $AppKitModalColorsCopyWith<$Res>(_value.darkColors, (value) { + return _then(_value.copyWith(darkColors: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalTextStylesCopyWith<$Res> get textStyles { + return $AppKitModalTextStylesCopyWith<$Res>(_value.textStyles, (value) { + return _then(_value.copyWith(textStyles: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalRadiusesCopyWith<$Res> get radiuses { + return $AppKitModalRadiusesCopyWith<$Res>(_value.radiuses, (value) { + return _then(_value.copyWith(radiuses: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$AppKitModalThemeDataImplCopyWith<$Res> + implements $AppKitModalThemeDataCopyWith<$Res> { + factory _$$AppKitModalThemeDataImplCopyWith(_$AppKitModalThemeDataImpl value, + $Res Function(_$AppKitModalThemeDataImpl) then) = + __$$AppKitModalThemeDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {AppKitModalColors lightColors, + AppKitModalColors darkColors, + AppKitModalTextStyles textStyles, + AppKitModalRadiuses radiuses}); + + @override + $AppKitModalColorsCopyWith<$Res> get lightColors; + @override + $AppKitModalColorsCopyWith<$Res> get darkColors; + @override + $AppKitModalTextStylesCopyWith<$Res> get textStyles; + @override + $AppKitModalRadiusesCopyWith<$Res> get radiuses; +} + +/// @nodoc +class __$$AppKitModalThemeDataImplCopyWithImpl<$Res> + extends _$AppKitModalThemeDataCopyWithImpl<$Res, _$AppKitModalThemeDataImpl> + implements _$$AppKitModalThemeDataImplCopyWith<$Res> { + __$$AppKitModalThemeDataImplCopyWithImpl(_$AppKitModalThemeDataImpl _value, + $Res Function(_$AppKitModalThemeDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lightColors = null, + Object? darkColors = null, + Object? textStyles = null, + Object? radiuses = null, + }) { + return _then(_$AppKitModalThemeDataImpl( + lightColors: null == lightColors + ? _value.lightColors + : lightColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + darkColors: null == darkColors + ? _value.darkColors + : darkColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + textStyles: null == textStyles + ? _value.textStyles + : textStyles // ignore: cast_nullable_to_non_nullable + as AppKitModalTextStyles, + radiuses: null == radiuses + ? _value.radiuses + : radiuses // ignore: cast_nullable_to_non_nullable + as AppKitModalRadiuses, + )); + } +} + +/// @nodoc + +class _$AppKitModalThemeDataImpl implements _AppKitModalThemeData { + const _$AppKitModalThemeDataImpl( + {this.lightColors = AppKitModalColors.lightMode, + this.darkColors = AppKitModalColors.darkMode, + this.textStyles = AppKitModalTextStyles.textStyle, + this.radiuses = const AppKitModalRadiuses()}); + + @override + @JsonKey() + final AppKitModalColors lightColors; + @override + @JsonKey() + final AppKitModalColors darkColors; + @override + @JsonKey() + final AppKitModalTextStyles textStyles; + @override + @JsonKey() + final AppKitModalRadiuses radiuses; + + @override + String toString() { + return 'AppKitModalThemeData(lightColors: $lightColors, darkColors: $darkColors, textStyles: $textStyles, radiuses: $radiuses)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalThemeDataImpl && + (identical(other.lightColors, lightColors) || + other.lightColors == lightColors) && + (identical(other.darkColors, darkColors) || + other.darkColors == darkColors) && + (identical(other.textStyles, textStyles) || + other.textStyles == textStyles) && + (identical(other.radiuses, radiuses) || + other.radiuses == radiuses)); + } + + @override + int get hashCode => + Object.hash(runtimeType, lightColors, darkColors, textStyles, radiuses); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalThemeDataImplCopyWith<_$AppKitModalThemeDataImpl> + get copyWith => + __$$AppKitModalThemeDataImplCopyWithImpl<_$AppKitModalThemeDataImpl>( + this, _$identity); +} + +abstract class _AppKitModalThemeData implements AppKitModalThemeData { + const factory _AppKitModalThemeData( + {final AppKitModalColors lightColors, + final AppKitModalColors darkColors, + final AppKitModalTextStyles textStyles, + final AppKitModalRadiuses radiuses}) = _$AppKitModalThemeDataImpl; + + @override + AppKitModalColors get lightColors; + @override + AppKitModalColors get darkColors; + @override + AppKitModalTextStyles get textStyles; + @override + AppKitModalRadiuses get radiuses; + @override + @JsonKey(ignore: true) + _$$AppKitModalThemeDataImplCopyWith<_$AppKitModalThemeDataImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_widget.dart b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_widget.dart new file mode 100644 index 0000000..ae01844 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/public/appkit_modal_theme_widget.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_colors.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_radiuses.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme_data.dart'; + +class AppKitModalTheme extends InheritedWidget { + const AppKitModalTheme({ + super.key, + required super.child, + this.themeData, + this.isDarkMode = false, + }); + + final AppKitModalThemeData? themeData; + final bool isDarkMode; + + static AppKitModalTheme of(BuildContext context) { + final AppKitModalTheme? result = maybeOf(context); + assert(result != null, 'No AppKitModalTheme found in context'); + return result!; + } + + static bool isCustomTheme(BuildContext context) { + final AppKitModalTheme? theme = maybeOf(context); + return theme?.themeData != null; + } + + static AppKitModalTheme? maybeOf(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } + + static AppKitModalThemeData getDataOf(BuildContext context) { + final AppKitModalTheme? theme = maybeOf(context); + return theme?.themeData ?? const AppKitModalThemeData(); + } + + static AppKitModalColors colorsOf(BuildContext context) { + final AppKitModalTheme? theme = maybeOf(context); + if (theme?.isDarkMode == true) { + return theme?.themeData?.darkColors ?? AppKitModalColors.darkMode; + } + return theme?.themeData?.lightColors ?? AppKitModalColors.lightMode; + } + + static AppKitModalRadiuses radiusesOf(BuildContext context) { + final AppKitModalTheme? theme = maybeOf(context); + return theme?.themeData?.radiuses ?? const AppKitModalRadiuses(); + } + + @override + bool updateShouldNotify(covariant InheritedWidget oldWidget) { + return true; + } +} diff --git a/packages/reown_appkit/lib/modal/theme/w3m_colors.freezed.dart b/packages/reown_appkit/lib/modal/theme/w3m_colors.freezed.dart new file mode 100644 index 0000000..b16372f --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/w3m_colors.freezed.dart @@ -0,0 +1,826 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'w3m_colors.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalColors { + Color get accent100 => throw _privateConstructorUsedError; + Color get accent090 => throw _privateConstructorUsedError; + Color get accent080 => throw _privateConstructorUsedError; // + Color get grayGlass100 => throw _privateConstructorUsedError; // + Color get foreground100 => throw _privateConstructorUsedError; + Color get foreground125 => throw _privateConstructorUsedError; + Color get foreground150 => throw _privateConstructorUsedError; + Color get foreground175 => throw _privateConstructorUsedError; + Color get foreground200 => throw _privateConstructorUsedError; + Color get foreground225 => throw _privateConstructorUsedError; + Color get foreground250 => throw _privateConstructorUsedError; + Color get foreground275 => throw _privateConstructorUsedError; + Color get foreground300 => throw _privateConstructorUsedError; // + Color get background100 => throw _privateConstructorUsedError; + Color get background125 => throw _privateConstructorUsedError; + Color get background150 => throw _privateConstructorUsedError; + Color get background175 => throw _privateConstructorUsedError; + Color get background200 => throw _privateConstructorUsedError; + Color get background225 => throw _privateConstructorUsedError; + Color get background250 => throw _privateConstructorUsedError; + Color get background275 => throw _privateConstructorUsedError; + Color get background300 => throw _privateConstructorUsedError; // + Color get inverse100 => throw _privateConstructorUsedError; + Color get inverse000 => throw _privateConstructorUsedError; + Color get success100 => throw _privateConstructorUsedError; + Color get error100 => throw _privateConstructorUsedError; + Color get teal100 => throw _privateConstructorUsedError; + Color get magenta100 => throw _privateConstructorUsedError; + Color get indigo100 => throw _privateConstructorUsedError; + Color get orange100 => throw _privateConstructorUsedError; + Color get purple100 => throw _privateConstructorUsedError; + Color get yellow100 => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalColorsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalColorsCopyWith<$Res> { + factory $AppKitModalColorsCopyWith( + AppKitModalColors value, $Res Function(AppKitModalColors) then) = + _$AppKitModalColorsCopyWithImpl<$Res, AppKitModalColors>; + @useResult + $Res call( + {Color accent100, + Color accent090, + Color accent080, + Color grayGlass100, + Color foreground100, + Color foreground125, + Color foreground150, + Color foreground175, + Color foreground200, + Color foreground225, + Color foreground250, + Color foreground275, + Color foreground300, + Color background100, + Color background125, + Color background150, + Color background175, + Color background200, + Color background225, + Color background250, + Color background275, + Color background300, + Color inverse100, + Color inverse000, + Color success100, + Color error100, + Color teal100, + Color magenta100, + Color indigo100, + Color orange100, + Color purple100, + Color yellow100}); +} + +/// @nodoc +class _$AppKitModalColorsCopyWithImpl<$Res, $Val extends AppKitModalColors> + implements $AppKitModalColorsCopyWith<$Res> { + _$AppKitModalColorsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accent100 = null, + Object? accent090 = null, + Object? accent080 = null, + Object? grayGlass100 = null, + Object? foreground100 = null, + Object? foreground125 = null, + Object? foreground150 = null, + Object? foreground175 = null, + Object? foreground200 = null, + Object? foreground225 = null, + Object? foreground250 = null, + Object? foreground275 = null, + Object? foreground300 = null, + Object? background100 = null, + Object? background125 = null, + Object? background150 = null, + Object? background175 = null, + Object? background200 = null, + Object? background225 = null, + Object? background250 = null, + Object? background275 = null, + Object? background300 = null, + Object? inverse100 = null, + Object? inverse000 = null, + Object? success100 = null, + Object? error100 = null, + Object? teal100 = null, + Object? magenta100 = null, + Object? indigo100 = null, + Object? orange100 = null, + Object? purple100 = null, + Object? yellow100 = null, + }) { + return _then(_value.copyWith( + accent100: null == accent100 + ? _value.accent100 + : accent100 // ignore: cast_nullable_to_non_nullable + as Color, + accent090: null == accent090 + ? _value.accent090 + : accent090 // ignore: cast_nullable_to_non_nullable + as Color, + accent080: null == accent080 + ? _value.accent080 + : accent080 // ignore: cast_nullable_to_non_nullable + as Color, + grayGlass100: null == grayGlass100 + ? _value.grayGlass100 + : grayGlass100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground100: null == foreground100 + ? _value.foreground100 + : foreground100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground125: null == foreground125 + ? _value.foreground125 + : foreground125 // ignore: cast_nullable_to_non_nullable + as Color, + foreground150: null == foreground150 + ? _value.foreground150 + : foreground150 // ignore: cast_nullable_to_non_nullable + as Color, + foreground175: null == foreground175 + ? _value.foreground175 + : foreground175 // ignore: cast_nullable_to_non_nullable + as Color, + foreground200: null == foreground200 + ? _value.foreground200 + : foreground200 // ignore: cast_nullable_to_non_nullable + as Color, + foreground225: null == foreground225 + ? _value.foreground225 + : foreground225 // ignore: cast_nullable_to_non_nullable + as Color, + foreground250: null == foreground250 + ? _value.foreground250 + : foreground250 // ignore: cast_nullable_to_non_nullable + as Color, + foreground275: null == foreground275 + ? _value.foreground275 + : foreground275 // ignore: cast_nullable_to_non_nullable + as Color, + foreground300: null == foreground300 + ? _value.foreground300 + : foreground300 // ignore: cast_nullable_to_non_nullable + as Color, + background100: null == background100 + ? _value.background100 + : background100 // ignore: cast_nullable_to_non_nullable + as Color, + background125: null == background125 + ? _value.background125 + : background125 // ignore: cast_nullable_to_non_nullable + as Color, + background150: null == background150 + ? _value.background150 + : background150 // ignore: cast_nullable_to_non_nullable + as Color, + background175: null == background175 + ? _value.background175 + : background175 // ignore: cast_nullable_to_non_nullable + as Color, + background200: null == background200 + ? _value.background200 + : background200 // ignore: cast_nullable_to_non_nullable + as Color, + background225: null == background225 + ? _value.background225 + : background225 // ignore: cast_nullable_to_non_nullable + as Color, + background250: null == background250 + ? _value.background250 + : background250 // ignore: cast_nullable_to_non_nullable + as Color, + background275: null == background275 + ? _value.background275 + : background275 // ignore: cast_nullable_to_non_nullable + as Color, + background300: null == background300 + ? _value.background300 + : background300 // ignore: cast_nullable_to_non_nullable + as Color, + inverse100: null == inverse100 + ? _value.inverse100 + : inverse100 // ignore: cast_nullable_to_non_nullable + as Color, + inverse000: null == inverse000 + ? _value.inverse000 + : inverse000 // ignore: cast_nullable_to_non_nullable + as Color, + success100: null == success100 + ? _value.success100 + : success100 // ignore: cast_nullable_to_non_nullable + as Color, + error100: null == error100 + ? _value.error100 + : error100 // ignore: cast_nullable_to_non_nullable + as Color, + teal100: null == teal100 + ? _value.teal100 + : teal100 // ignore: cast_nullable_to_non_nullable + as Color, + magenta100: null == magenta100 + ? _value.magenta100 + : magenta100 // ignore: cast_nullable_to_non_nullable + as Color, + indigo100: null == indigo100 + ? _value.indigo100 + : indigo100 // ignore: cast_nullable_to_non_nullable + as Color, + orange100: null == orange100 + ? _value.orange100 + : orange100 // ignore: cast_nullable_to_non_nullable + as Color, + purple100: null == purple100 + ? _value.purple100 + : purple100 // ignore: cast_nullable_to_non_nullable + as Color, + yellow100: null == yellow100 + ? _value.yellow100 + : yellow100 // ignore: cast_nullable_to_non_nullable + as Color, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalColorsImplCopyWith<$Res> + implements $AppKitModalColorsCopyWith<$Res> { + factory _$$AppKitModalColorsImplCopyWith(_$AppKitModalColorsImpl value, + $Res Function(_$AppKitModalColorsImpl) then) = + __$$AppKitModalColorsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Color accent100, + Color accent090, + Color accent080, + Color grayGlass100, + Color foreground100, + Color foreground125, + Color foreground150, + Color foreground175, + Color foreground200, + Color foreground225, + Color foreground250, + Color foreground275, + Color foreground300, + Color background100, + Color background125, + Color background150, + Color background175, + Color background200, + Color background225, + Color background250, + Color background275, + Color background300, + Color inverse100, + Color inverse000, + Color success100, + Color error100, + Color teal100, + Color magenta100, + Color indigo100, + Color orange100, + Color purple100, + Color yellow100}); +} + +/// @nodoc +class __$$AppKitModalColorsImplCopyWithImpl<$Res> + extends _$AppKitModalColorsCopyWithImpl<$Res, _$AppKitModalColorsImpl> + implements _$$AppKitModalColorsImplCopyWith<$Res> { + __$$AppKitModalColorsImplCopyWithImpl(_$AppKitModalColorsImpl _value, + $Res Function(_$AppKitModalColorsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accent100 = null, + Object? accent090 = null, + Object? accent080 = null, + Object? grayGlass100 = null, + Object? foreground100 = null, + Object? foreground125 = null, + Object? foreground150 = null, + Object? foreground175 = null, + Object? foreground200 = null, + Object? foreground225 = null, + Object? foreground250 = null, + Object? foreground275 = null, + Object? foreground300 = null, + Object? background100 = null, + Object? background125 = null, + Object? background150 = null, + Object? background175 = null, + Object? background200 = null, + Object? background225 = null, + Object? background250 = null, + Object? background275 = null, + Object? background300 = null, + Object? inverse100 = null, + Object? inverse000 = null, + Object? success100 = null, + Object? error100 = null, + Object? teal100 = null, + Object? magenta100 = null, + Object? indigo100 = null, + Object? orange100 = null, + Object? purple100 = null, + Object? yellow100 = null, + }) { + return _then(_$AppKitModalColorsImpl( + accent100: null == accent100 + ? _value.accent100 + : accent100 // ignore: cast_nullable_to_non_nullable + as Color, + accent090: null == accent090 + ? _value.accent090 + : accent090 // ignore: cast_nullable_to_non_nullable + as Color, + accent080: null == accent080 + ? _value.accent080 + : accent080 // ignore: cast_nullable_to_non_nullable + as Color, + grayGlass100: null == grayGlass100 + ? _value.grayGlass100 + : grayGlass100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground100: null == foreground100 + ? _value.foreground100 + : foreground100 // ignore: cast_nullable_to_non_nullable + as Color, + foreground125: null == foreground125 + ? _value.foreground125 + : foreground125 // ignore: cast_nullable_to_non_nullable + as Color, + foreground150: null == foreground150 + ? _value.foreground150 + : foreground150 // ignore: cast_nullable_to_non_nullable + as Color, + foreground175: null == foreground175 + ? _value.foreground175 + : foreground175 // ignore: cast_nullable_to_non_nullable + as Color, + foreground200: null == foreground200 + ? _value.foreground200 + : foreground200 // ignore: cast_nullable_to_non_nullable + as Color, + foreground225: null == foreground225 + ? _value.foreground225 + : foreground225 // ignore: cast_nullable_to_non_nullable + as Color, + foreground250: null == foreground250 + ? _value.foreground250 + : foreground250 // ignore: cast_nullable_to_non_nullable + as Color, + foreground275: null == foreground275 + ? _value.foreground275 + : foreground275 // ignore: cast_nullable_to_non_nullable + as Color, + foreground300: null == foreground300 + ? _value.foreground300 + : foreground300 // ignore: cast_nullable_to_non_nullable + as Color, + background100: null == background100 + ? _value.background100 + : background100 // ignore: cast_nullable_to_non_nullable + as Color, + background125: null == background125 + ? _value.background125 + : background125 // ignore: cast_nullable_to_non_nullable + as Color, + background150: null == background150 + ? _value.background150 + : background150 // ignore: cast_nullable_to_non_nullable + as Color, + background175: null == background175 + ? _value.background175 + : background175 // ignore: cast_nullable_to_non_nullable + as Color, + background200: null == background200 + ? _value.background200 + : background200 // ignore: cast_nullable_to_non_nullable + as Color, + background225: null == background225 + ? _value.background225 + : background225 // ignore: cast_nullable_to_non_nullable + as Color, + background250: null == background250 + ? _value.background250 + : background250 // ignore: cast_nullable_to_non_nullable + as Color, + background275: null == background275 + ? _value.background275 + : background275 // ignore: cast_nullable_to_non_nullable + as Color, + background300: null == background300 + ? _value.background300 + : background300 // ignore: cast_nullable_to_non_nullable + as Color, + inverse100: null == inverse100 + ? _value.inverse100 + : inverse100 // ignore: cast_nullable_to_non_nullable + as Color, + inverse000: null == inverse000 + ? _value.inverse000 + : inverse000 // ignore: cast_nullable_to_non_nullable + as Color, + success100: null == success100 + ? _value.success100 + : success100 // ignore: cast_nullable_to_non_nullable + as Color, + error100: null == error100 + ? _value.error100 + : error100 // ignore: cast_nullable_to_non_nullable + as Color, + teal100: null == teal100 + ? _value.teal100 + : teal100 // ignore: cast_nullable_to_non_nullable + as Color, + magenta100: null == magenta100 + ? _value.magenta100 + : magenta100 // ignore: cast_nullable_to_non_nullable + as Color, + indigo100: null == indigo100 + ? _value.indigo100 + : indigo100 // ignore: cast_nullable_to_non_nullable + as Color, + orange100: null == orange100 + ? _value.orange100 + : orange100 // ignore: cast_nullable_to_non_nullable + as Color, + purple100: null == purple100 + ? _value.purple100 + : purple100 // ignore: cast_nullable_to_non_nullable + as Color, + yellow100: null == yellow100 + ? _value.yellow100 + : yellow100 // ignore: cast_nullable_to_non_nullable + as Color, + )); + } +} + +/// @nodoc + +class _$AppKitModalColorsImpl implements _AppKitModalColors { + const _$AppKitModalColorsImpl( + {required this.accent100, + required this.accent090, + required this.accent080, + required this.grayGlass100, + required this.foreground100, + required this.foreground125, + required this.foreground150, + required this.foreground175, + required this.foreground200, + required this.foreground225, + required this.foreground250, + required this.foreground275, + required this.foreground300, + required this.background100, + required this.background125, + required this.background150, + required this.background175, + required this.background200, + required this.background225, + required this.background250, + required this.background275, + required this.background300, + required this.inverse100, + required this.inverse000, + required this.success100, + required this.error100, + required this.teal100, + required this.magenta100, + required this.indigo100, + required this.orange100, + required this.purple100, + required this.yellow100}); + + @override + final Color accent100; + @override + final Color accent090; + @override + final Color accent080; +// + @override + final Color grayGlass100; +// + @override + final Color foreground100; + @override + final Color foreground125; + @override + final Color foreground150; + @override + final Color foreground175; + @override + final Color foreground200; + @override + final Color foreground225; + @override + final Color foreground250; + @override + final Color foreground275; + @override + final Color foreground300; +// + @override + final Color background100; + @override + final Color background125; + @override + final Color background150; + @override + final Color background175; + @override + final Color background200; + @override + final Color background225; + @override + final Color background250; + @override + final Color background275; + @override + final Color background300; +// + @override + final Color inverse100; + @override + final Color inverse000; + @override + final Color success100; + @override + final Color error100; + @override + final Color teal100; + @override + final Color magenta100; + @override + final Color indigo100; + @override + final Color orange100; + @override + final Color purple100; + @override + final Color yellow100; + + @override + String toString() { + return 'AppKitModalColors(accent100: $accent100, accent090: $accent090, accent080: $accent080, grayGlass100: $grayGlass100, foreground100: $foreground100, foreground125: $foreground125, foreground150: $foreground150, foreground175: $foreground175, foreground200: $foreground200, foreground225: $foreground225, foreground250: $foreground250, foreground275: $foreground275, foreground300: $foreground300, background100: $background100, background125: $background125, background150: $background150, background175: $background175, background200: $background200, background225: $background225, background250: $background250, background275: $background275, background300: $background300, inverse100: $inverse100, inverse000: $inverse000, success100: $success100, error100: $error100, teal100: $teal100, magenta100: $magenta100, indigo100: $indigo100, orange100: $orange100, purple100: $purple100, yellow100: $yellow100)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalColorsImpl && + (identical(other.accent100, accent100) || + other.accent100 == accent100) && + (identical(other.accent090, accent090) || + other.accent090 == accent090) && + (identical(other.accent080, accent080) || + other.accent080 == accent080) && + (identical(other.grayGlass100, grayGlass100) || + other.grayGlass100 == grayGlass100) && + (identical(other.foreground100, foreground100) || + other.foreground100 == foreground100) && + (identical(other.foreground125, foreground125) || + other.foreground125 == foreground125) && + (identical(other.foreground150, foreground150) || + other.foreground150 == foreground150) && + (identical(other.foreground175, foreground175) || + other.foreground175 == foreground175) && + (identical(other.foreground200, foreground200) || + other.foreground200 == foreground200) && + (identical(other.foreground225, foreground225) || + other.foreground225 == foreground225) && + (identical(other.foreground250, foreground250) || + other.foreground250 == foreground250) && + (identical(other.foreground275, foreground275) || + other.foreground275 == foreground275) && + (identical(other.foreground300, foreground300) || + other.foreground300 == foreground300) && + (identical(other.background100, background100) || + other.background100 == background100) && + (identical(other.background125, background125) || + other.background125 == background125) && + (identical(other.background150, background150) || + other.background150 == background150) && + (identical(other.background175, background175) || + other.background175 == background175) && + (identical(other.background200, background200) || + other.background200 == background200) && + (identical(other.background225, background225) || + other.background225 == background225) && + (identical(other.background250, background250) || + other.background250 == background250) && + (identical(other.background275, background275) || + other.background275 == background275) && + (identical(other.background300, background300) || + other.background300 == background300) && + (identical(other.inverse100, inverse100) || + other.inverse100 == inverse100) && + (identical(other.inverse000, inverse000) || + other.inverse000 == inverse000) && + (identical(other.success100, success100) || + other.success100 == success100) && + (identical(other.error100, error100) || + other.error100 == error100) && + (identical(other.teal100, teal100) || other.teal100 == teal100) && + (identical(other.magenta100, magenta100) || + other.magenta100 == magenta100) && + (identical(other.indigo100, indigo100) || + other.indigo100 == indigo100) && + (identical(other.orange100, orange100) || + other.orange100 == orange100) && + (identical(other.purple100, purple100) || + other.purple100 == purple100) && + (identical(other.yellow100, yellow100) || + other.yellow100 == yellow100)); + } + + @override + int get hashCode => Object.hashAll([ + runtimeType, + accent100, + accent090, + accent080, + grayGlass100, + foreground100, + foreground125, + foreground150, + foreground175, + foreground200, + foreground225, + foreground250, + foreground275, + foreground300, + background100, + background125, + background150, + background175, + background200, + background225, + background250, + background275, + background300, + inverse100, + inverse000, + success100, + error100, + teal100, + magenta100, + indigo100, + orange100, + purple100, + yellow100 + ]); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalColorsImplCopyWith<_$AppKitModalColorsImpl> get copyWith => + __$$AppKitModalColorsImplCopyWithImpl<_$AppKitModalColorsImpl>( + this, _$identity); +} + +abstract class _AppKitModalColors implements AppKitModalColors { + const factory _AppKitModalColors( + {required final Color accent100, + required final Color accent090, + required final Color accent080, + required final Color grayGlass100, + required final Color foreground100, + required final Color foreground125, + required final Color foreground150, + required final Color foreground175, + required final Color foreground200, + required final Color foreground225, + required final Color foreground250, + required final Color foreground275, + required final Color foreground300, + required final Color background100, + required final Color background125, + required final Color background150, + required final Color background175, + required final Color background200, + required final Color background225, + required final Color background250, + required final Color background275, + required final Color background300, + required final Color inverse100, + required final Color inverse000, + required final Color success100, + required final Color error100, + required final Color teal100, + required final Color magenta100, + required final Color indigo100, + required final Color orange100, + required final Color purple100, + required final Color yellow100}) = _$AppKitModalColorsImpl; + + @override + Color get accent100; + @override + Color get accent090; + @override + Color get accent080; + @override // + Color get grayGlass100; + @override // + Color get foreground100; + @override + Color get foreground125; + @override + Color get foreground150; + @override + Color get foreground175; + @override + Color get foreground200; + @override + Color get foreground225; + @override + Color get foreground250; + @override + Color get foreground275; + @override + Color get foreground300; + @override // + Color get background100; + @override + Color get background125; + @override + Color get background150; + @override + Color get background175; + @override + Color get background200; + @override + Color get background225; + @override + Color get background250; + @override + Color get background275; + @override + Color get background300; + @override // + Color get inverse100; + @override + Color get inverse000; + @override + Color get success100; + @override + Color get error100; + @override + Color get teal100; + @override + Color get magenta100; + @override + Color get indigo100; + @override + Color get orange100; + @override + Color get purple100; + @override + Color get yellow100; + @override + @JsonKey(ignore: true) + _$$AppKitModalColorsImplCopyWith<_$AppKitModalColorsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/w3m_radiuses.freezed.dart b/packages/reown_appkit/lib/modal/theme/w3m_radiuses.freezed.dart new file mode 100644 index 0000000..644658d --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/w3m_radiuses.freezed.dart @@ -0,0 +1,291 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'w3m_radiuses.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalRadiuses { + double get radius4XS => throw _privateConstructorUsedError; + double get radius3XS => throw _privateConstructorUsedError; + double get radius2XS => throw _privateConstructorUsedError; + double get radiusXS => throw _privateConstructorUsedError; + double get radiusS => throw _privateConstructorUsedError; + double get radiusM => throw _privateConstructorUsedError; + double get radiusL => throw _privateConstructorUsedError; + double get radius3XL => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalRadiusesCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalRadiusesCopyWith<$Res> { + factory $AppKitModalRadiusesCopyWith( + AppKitModalRadiuses value, $Res Function(AppKitModalRadiuses) then) = + _$AppKitModalRadiusesCopyWithImpl<$Res, AppKitModalRadiuses>; + @useResult + $Res call( + {double radius4XS, + double radius3XS, + double radius2XS, + double radiusXS, + double radiusS, + double radiusM, + double radiusL, + double radius3XL}); +} + +/// @nodoc +class _$AppKitModalRadiusesCopyWithImpl<$Res, $Val extends AppKitModalRadiuses> + implements $AppKitModalRadiusesCopyWith<$Res> { + _$AppKitModalRadiusesCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? radius4XS = null, + Object? radius3XS = null, + Object? radius2XS = null, + Object? radiusXS = null, + Object? radiusS = null, + Object? radiusM = null, + Object? radiusL = null, + Object? radius3XL = null, + }) { + return _then(_value.copyWith( + radius4XS: null == radius4XS + ? _value.radius4XS + : radius4XS // ignore: cast_nullable_to_non_nullable + as double, + radius3XS: null == radius3XS + ? _value.radius3XS + : radius3XS // ignore: cast_nullable_to_non_nullable + as double, + radius2XS: null == radius2XS + ? _value.radius2XS + : radius2XS // ignore: cast_nullable_to_non_nullable + as double, + radiusXS: null == radiusXS + ? _value.radiusXS + : radiusXS // ignore: cast_nullable_to_non_nullable + as double, + radiusS: null == radiusS + ? _value.radiusS + : radiusS // ignore: cast_nullable_to_non_nullable + as double, + radiusM: null == radiusM + ? _value.radiusM + : radiusM // ignore: cast_nullable_to_non_nullable + as double, + radiusL: null == radiusL + ? _value.radiusL + : radiusL // ignore: cast_nullable_to_non_nullable + as double, + radius3XL: null == radius3XL + ? _value.radius3XL + : radius3XL // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalRadiusesImplCopyWith<$Res> + implements $AppKitModalRadiusesCopyWith<$Res> { + factory _$$AppKitModalRadiusesImplCopyWith(_$AppKitModalRadiusesImpl value, + $Res Function(_$AppKitModalRadiusesImpl) then) = + __$$AppKitModalRadiusesImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {double radius4XS, + double radius3XS, + double radius2XS, + double radiusXS, + double radiusS, + double radiusM, + double radiusL, + double radius3XL}); +} + +/// @nodoc +class __$$AppKitModalRadiusesImplCopyWithImpl<$Res> + extends _$AppKitModalRadiusesCopyWithImpl<$Res, _$AppKitModalRadiusesImpl> + implements _$$AppKitModalRadiusesImplCopyWith<$Res> { + __$$AppKitModalRadiusesImplCopyWithImpl(_$AppKitModalRadiusesImpl _value, + $Res Function(_$AppKitModalRadiusesImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? radius4XS = null, + Object? radius3XS = null, + Object? radius2XS = null, + Object? radiusXS = null, + Object? radiusS = null, + Object? radiusM = null, + Object? radiusL = null, + Object? radius3XL = null, + }) { + return _then(_$AppKitModalRadiusesImpl( + radius4XS: null == radius4XS + ? _value.radius4XS + : radius4XS // ignore: cast_nullable_to_non_nullable + as double, + radius3XS: null == radius3XS + ? _value.radius3XS + : radius3XS // ignore: cast_nullable_to_non_nullable + as double, + radius2XS: null == radius2XS + ? _value.radius2XS + : radius2XS // ignore: cast_nullable_to_non_nullable + as double, + radiusXS: null == radiusXS + ? _value.radiusXS + : radiusXS // ignore: cast_nullable_to_non_nullable + as double, + radiusS: null == radiusS + ? _value.radiusS + : radiusS // ignore: cast_nullable_to_non_nullable + as double, + radiusM: null == radiusM + ? _value.radiusM + : radiusM // ignore: cast_nullable_to_non_nullable + as double, + radiusL: null == radiusL + ? _value.radiusL + : radiusL // ignore: cast_nullable_to_non_nullable + as double, + radius3XL: null == radius3XL + ? _value.radius3XL + : radius3XL // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc + +class _$AppKitModalRadiusesImpl implements _AppKitModalRadiuses { + const _$AppKitModalRadiusesImpl( + {this.radius4XS = 6.0, + this.radius3XS = 8.0, + this.radius2XS = 12.0, + this.radiusXS = 16.0, + this.radiusS = 20.0, + this.radiusM = 28.0, + this.radiusL = 36.0, + this.radius3XL = 80.0}); + + @override + @JsonKey() + final double radius4XS; + @override + @JsonKey() + final double radius3XS; + @override + @JsonKey() + final double radius2XS; + @override + @JsonKey() + final double radiusXS; + @override + @JsonKey() + final double radiusS; + @override + @JsonKey() + final double radiusM; + @override + @JsonKey() + final double radiusL; + @override + @JsonKey() + final double radius3XL; + + @override + String toString() { + return 'AppKitModalRadiuses(radius4XS: $radius4XS, radius3XS: $radius3XS, radius2XS: $radius2XS, radiusXS: $radiusXS, radiusS: $radiusS, radiusM: $radiusM, radiusL: $radiusL, radius3XL: $radius3XL)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalRadiusesImpl && + (identical(other.radius4XS, radius4XS) || + other.radius4XS == radius4XS) && + (identical(other.radius3XS, radius3XS) || + other.radius3XS == radius3XS) && + (identical(other.radius2XS, radius2XS) || + other.radius2XS == radius2XS) && + (identical(other.radiusXS, radiusXS) || + other.radiusXS == radiusXS) && + (identical(other.radiusS, radiusS) || other.radiusS == radiusS) && + (identical(other.radiusM, radiusM) || other.radiusM == radiusM) && + (identical(other.radiusL, radiusL) || other.radiusL == radiusL) && + (identical(other.radius3XL, radius3XL) || + other.radius3XL == radius3XL)); + } + + @override + int get hashCode => Object.hash(runtimeType, radius4XS, radius3XS, radius2XS, + radiusXS, radiusS, radiusM, radiusL, radius3XL); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalRadiusesImplCopyWith<_$AppKitModalRadiusesImpl> get copyWith => + __$$AppKitModalRadiusesImplCopyWithImpl<_$AppKitModalRadiusesImpl>( + this, _$identity); +} + +abstract class _AppKitModalRadiuses implements AppKitModalRadiuses { + const factory _AppKitModalRadiuses( + {final double radius4XS, + final double radius3XS, + final double radius2XS, + final double radiusXS, + final double radiusS, + final double radiusM, + final double radiusL, + final double radius3XL}) = _$AppKitModalRadiusesImpl; + + @override + double get radius4XS; + @override + double get radius3XS; + @override + double get radius2XS; + @override + double get radiusXS; + @override + double get radiusS; + @override + double get radiusM; + @override + double get radiusL; + @override + double get radius3XL; + @override + @JsonKey(ignore: true) + _$$AppKitModalRadiusesImplCopyWith<_$AppKitModalRadiusesImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/w3m_text_styles.freezed.dart b/packages/reown_appkit/lib/modal/theme/w3m_text_styles.freezed.dart new file mode 100644 index 0000000..f4dae41 --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/w3m_text_styles.freezed.dart @@ -0,0 +1,514 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'w3m_text_styles.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalTextStyles { + String get fontFamily => throw _privateConstructorUsedError; + TextStyle get title400 => throw _privateConstructorUsedError; + TextStyle get title500 => throw _privateConstructorUsedError; + TextStyle get title600 => throw _privateConstructorUsedError; + TextStyle get large400 => throw _privateConstructorUsedError; + TextStyle get large500 => throw _privateConstructorUsedError; + TextStyle get large600 => throw _privateConstructorUsedError; + TextStyle get paragraph400 => throw _privateConstructorUsedError; + TextStyle get paragraph500 => throw _privateConstructorUsedError; + TextStyle get paragraph600 => throw _privateConstructorUsedError; + TextStyle get small400 => throw _privateConstructorUsedError; + TextStyle get small500 => throw _privateConstructorUsedError; + TextStyle get small600 => throw _privateConstructorUsedError; + TextStyle get tiny400 => throw _privateConstructorUsedError; + TextStyle get tiny500 => throw _privateConstructorUsedError; + TextStyle get tiny600 => throw _privateConstructorUsedError; + TextStyle get micro600 => throw _privateConstructorUsedError; + TextStyle get micro700 => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalTextStylesCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalTextStylesCopyWith<$Res> { + factory $AppKitModalTextStylesCopyWith(AppKitModalTextStyles value, + $Res Function(AppKitModalTextStyles) then) = + _$AppKitModalTextStylesCopyWithImpl<$Res, AppKitModalTextStyles>; + @useResult + $Res call( + {String fontFamily, + TextStyle title400, + TextStyle title500, + TextStyle title600, + TextStyle large400, + TextStyle large500, + TextStyle large600, + TextStyle paragraph400, + TextStyle paragraph500, + TextStyle paragraph600, + TextStyle small400, + TextStyle small500, + TextStyle small600, + TextStyle tiny400, + TextStyle tiny500, + TextStyle tiny600, + TextStyle micro600, + TextStyle micro700}); +} + +/// @nodoc +class _$AppKitModalTextStylesCopyWithImpl<$Res, + $Val extends AppKitModalTextStyles> + implements $AppKitModalTextStylesCopyWith<$Res> { + _$AppKitModalTextStylesCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fontFamily = null, + Object? title400 = null, + Object? title500 = null, + Object? title600 = null, + Object? large400 = null, + Object? large500 = null, + Object? large600 = null, + Object? paragraph400 = null, + Object? paragraph500 = null, + Object? paragraph600 = null, + Object? small400 = null, + Object? small500 = null, + Object? small600 = null, + Object? tiny400 = null, + Object? tiny500 = null, + Object? tiny600 = null, + Object? micro600 = null, + Object? micro700 = null, + }) { + return _then(_value.copyWith( + fontFamily: null == fontFamily + ? _value.fontFamily + : fontFamily // ignore: cast_nullable_to_non_nullable + as String, + title400: null == title400 + ? _value.title400 + : title400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title500: null == title500 + ? _value.title500 + : title500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title600: null == title600 + ? _value.title600 + : title600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large400: null == large400 + ? _value.large400 + : large400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large500: null == large500 + ? _value.large500 + : large500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large600: null == large600 + ? _value.large600 + : large600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph400: null == paragraph400 + ? _value.paragraph400 + : paragraph400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph500: null == paragraph500 + ? _value.paragraph500 + : paragraph500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph600: null == paragraph600 + ? _value.paragraph600 + : paragraph600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small400: null == small400 + ? _value.small400 + : small400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small500: null == small500 + ? _value.small500 + : small500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small600: null == small600 + ? _value.small600 + : small600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny400: null == tiny400 + ? _value.tiny400 + : tiny400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny500: null == tiny500 + ? _value.tiny500 + : tiny500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny600: null == tiny600 + ? _value.tiny600 + : tiny600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro600: null == micro600 + ? _value.micro600 + : micro600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro700: null == micro700 + ? _value.micro700 + : micro700 // ignore: cast_nullable_to_non_nullable + as TextStyle, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppKitModalTextStylesImplCopyWith<$Res> + implements $AppKitModalTextStylesCopyWith<$Res> { + factory _$$AppKitModalTextStylesImplCopyWith( + _$AppKitModalTextStylesImpl value, + $Res Function(_$AppKitModalTextStylesImpl) then) = + __$$AppKitModalTextStylesImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String fontFamily, + TextStyle title400, + TextStyle title500, + TextStyle title600, + TextStyle large400, + TextStyle large500, + TextStyle large600, + TextStyle paragraph400, + TextStyle paragraph500, + TextStyle paragraph600, + TextStyle small400, + TextStyle small500, + TextStyle small600, + TextStyle tiny400, + TextStyle tiny500, + TextStyle tiny600, + TextStyle micro600, + TextStyle micro700}); +} + +/// @nodoc +class __$$AppKitModalTextStylesImplCopyWithImpl<$Res> + extends _$AppKitModalTextStylesCopyWithImpl<$Res, + _$AppKitModalTextStylesImpl> + implements _$$AppKitModalTextStylesImplCopyWith<$Res> { + __$$AppKitModalTextStylesImplCopyWithImpl(_$AppKitModalTextStylesImpl _value, + $Res Function(_$AppKitModalTextStylesImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fontFamily = null, + Object? title400 = null, + Object? title500 = null, + Object? title600 = null, + Object? large400 = null, + Object? large500 = null, + Object? large600 = null, + Object? paragraph400 = null, + Object? paragraph500 = null, + Object? paragraph600 = null, + Object? small400 = null, + Object? small500 = null, + Object? small600 = null, + Object? tiny400 = null, + Object? tiny500 = null, + Object? tiny600 = null, + Object? micro600 = null, + Object? micro700 = null, + }) { + return _then(_$AppKitModalTextStylesImpl( + fontFamily: null == fontFamily + ? _value.fontFamily + : fontFamily // ignore: cast_nullable_to_non_nullable + as String, + title400: null == title400 + ? _value.title400 + : title400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title500: null == title500 + ? _value.title500 + : title500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + title600: null == title600 + ? _value.title600 + : title600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large400: null == large400 + ? _value.large400 + : large400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large500: null == large500 + ? _value.large500 + : large500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + large600: null == large600 + ? _value.large600 + : large600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph400: null == paragraph400 + ? _value.paragraph400 + : paragraph400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph500: null == paragraph500 + ? _value.paragraph500 + : paragraph500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + paragraph600: null == paragraph600 + ? _value.paragraph600 + : paragraph600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small400: null == small400 + ? _value.small400 + : small400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small500: null == small500 + ? _value.small500 + : small500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + small600: null == small600 + ? _value.small600 + : small600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny400: null == tiny400 + ? _value.tiny400 + : tiny400 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny500: null == tiny500 + ? _value.tiny500 + : tiny500 // ignore: cast_nullable_to_non_nullable + as TextStyle, + tiny600: null == tiny600 + ? _value.tiny600 + : tiny600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro600: null == micro600 + ? _value.micro600 + : micro600 // ignore: cast_nullable_to_non_nullable + as TextStyle, + micro700: null == micro700 + ? _value.micro700 + : micro700 // ignore: cast_nullable_to_non_nullable + as TextStyle, + )); + } +} + +/// @nodoc + +class _$AppKitModalTextStylesImpl implements _AppKitModalTextStyles { + const _$AppKitModalTextStylesImpl( + {required this.fontFamily, + required this.title400, + required this.title500, + required this.title600, + required this.large400, + required this.large500, + required this.large600, + required this.paragraph400, + required this.paragraph500, + required this.paragraph600, + required this.small400, + required this.small500, + required this.small600, + required this.tiny400, + required this.tiny500, + required this.tiny600, + required this.micro600, + required this.micro700}); + + @override + final String fontFamily; + @override + final TextStyle title400; + @override + final TextStyle title500; + @override + final TextStyle title600; + @override + final TextStyle large400; + @override + final TextStyle large500; + @override + final TextStyle large600; + @override + final TextStyle paragraph400; + @override + final TextStyle paragraph500; + @override + final TextStyle paragraph600; + @override + final TextStyle small400; + @override + final TextStyle small500; + @override + final TextStyle small600; + @override + final TextStyle tiny400; + @override + final TextStyle tiny500; + @override + final TextStyle tiny600; + @override + final TextStyle micro600; + @override + final TextStyle micro700; + + @override + String toString() { + return 'AppKitModalTextStyles(fontFamily: $fontFamily, title400: $title400, title500: $title500, title600: $title600, large400: $large400, large500: $large500, large600: $large600, paragraph400: $paragraph400, paragraph500: $paragraph500, paragraph600: $paragraph600, small400: $small400, small500: $small500, small600: $small600, tiny400: $tiny400, tiny500: $tiny500, tiny600: $tiny600, micro600: $micro600, micro700: $micro700)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalTextStylesImpl && + (identical(other.fontFamily, fontFamily) || + other.fontFamily == fontFamily) && + (identical(other.title400, title400) || + other.title400 == title400) && + (identical(other.title500, title500) || + other.title500 == title500) && + (identical(other.title600, title600) || + other.title600 == title600) && + (identical(other.large400, large400) || + other.large400 == large400) && + (identical(other.large500, large500) || + other.large500 == large500) && + (identical(other.large600, large600) || + other.large600 == large600) && + (identical(other.paragraph400, paragraph400) || + other.paragraph400 == paragraph400) && + (identical(other.paragraph500, paragraph500) || + other.paragraph500 == paragraph500) && + (identical(other.paragraph600, paragraph600) || + other.paragraph600 == paragraph600) && + (identical(other.small400, small400) || + other.small400 == small400) && + (identical(other.small500, small500) || + other.small500 == small500) && + (identical(other.small600, small600) || + other.small600 == small600) && + (identical(other.tiny400, tiny400) || other.tiny400 == tiny400) && + (identical(other.tiny500, tiny500) || other.tiny500 == tiny500) && + (identical(other.tiny600, tiny600) || other.tiny600 == tiny600) && + (identical(other.micro600, micro600) || + other.micro600 == micro600) && + (identical(other.micro700, micro700) || + other.micro700 == micro700)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + fontFamily, + title400, + title500, + title600, + large400, + large500, + large600, + paragraph400, + paragraph500, + paragraph600, + small400, + small500, + small600, + tiny400, + tiny500, + tiny600, + micro600, + micro700); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalTextStylesImplCopyWith<_$AppKitModalTextStylesImpl> + get copyWith => __$$AppKitModalTextStylesImplCopyWithImpl< + _$AppKitModalTextStylesImpl>(this, _$identity); +} + +abstract class _AppKitModalTextStyles implements AppKitModalTextStyles { + const factory _AppKitModalTextStyles( + {required final String fontFamily, + required final TextStyle title400, + required final TextStyle title500, + required final TextStyle title600, + required final TextStyle large400, + required final TextStyle large500, + required final TextStyle large600, + required final TextStyle paragraph400, + required final TextStyle paragraph500, + required final TextStyle paragraph600, + required final TextStyle small400, + required final TextStyle small500, + required final TextStyle small600, + required final TextStyle tiny400, + required final TextStyle tiny500, + required final TextStyle tiny600, + required final TextStyle micro600, + required final TextStyle micro700}) = _$AppKitModalTextStylesImpl; + + @override + String get fontFamily; + @override + TextStyle get title400; + @override + TextStyle get title500; + @override + TextStyle get title600; + @override + TextStyle get large400; + @override + TextStyle get large500; + @override + TextStyle get large600; + @override + TextStyle get paragraph400; + @override + TextStyle get paragraph500; + @override + TextStyle get paragraph600; + @override + TextStyle get small400; + @override + TextStyle get small500; + @override + TextStyle get small600; + @override + TextStyle get tiny400; + @override + TextStyle get tiny500; + @override + TextStyle get tiny600; + @override + TextStyle get micro600; + @override + TextStyle get micro700; + @override + @JsonKey(ignore: true) + _$$AppKitModalTextStylesImplCopyWith<_$AppKitModalTextStylesImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/theme/w3m_theme_data.freezed.dart b/packages/reown_appkit/lib/modal/theme/w3m_theme_data.freezed.dart new file mode 100644 index 0000000..909f26d --- /dev/null +++ b/packages/reown_appkit/lib/modal/theme/w3m_theme_data.freezed.dart @@ -0,0 +1,254 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'w3m_theme_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AppKitModalThemeData { + AppKitModalColors get lightColors => throw _privateConstructorUsedError; + AppKitModalColors get darkColors => throw _privateConstructorUsedError; + AppKitModalTextStyles get textStyles => throw _privateConstructorUsedError; + AppKitModalRadiuses get radiuses => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AppKitModalThemeDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppKitModalThemeDataCopyWith<$Res> { + factory $AppKitModalThemeDataCopyWith(AppKitModalThemeData value, + $Res Function(AppKitModalThemeData) then) = + _$AppKitModalThemeDataCopyWithImpl<$Res, AppKitModalThemeData>; + @useResult + $Res call( + {AppKitModalColors lightColors, + AppKitModalColors darkColors, + AppKitModalTextStyles textStyles, + AppKitModalRadiuses radiuses}); + + $AppKitModalColorsCopyWith<$Res> get lightColors; + $AppKitModalColorsCopyWith<$Res> get darkColors; + $AppKitModalTextStylesCopyWith<$Res> get textStyles; + $AppKitModalRadiusesCopyWith<$Res> get radiuses; +} + +/// @nodoc +class _$AppKitModalThemeDataCopyWithImpl<$Res, + $Val extends AppKitModalThemeData> + implements $AppKitModalThemeDataCopyWith<$Res> { + _$AppKitModalThemeDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lightColors = null, + Object? darkColors = null, + Object? textStyles = null, + Object? radiuses = null, + }) { + return _then(_value.copyWith( + lightColors: null == lightColors + ? _value.lightColors + : lightColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + darkColors: null == darkColors + ? _value.darkColors + : darkColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + textStyles: null == textStyles + ? _value.textStyles + : textStyles // ignore: cast_nullable_to_non_nullable + as AppKitModalTextStyles, + radiuses: null == radiuses + ? _value.radiuses + : radiuses // ignore: cast_nullable_to_non_nullable + as AppKitModalRadiuses, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalColorsCopyWith<$Res> get lightColors { + return $AppKitModalColorsCopyWith<$Res>(_value.lightColors, (value) { + return _then(_value.copyWith(lightColors: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalColorsCopyWith<$Res> get darkColors { + return $AppKitModalColorsCopyWith<$Res>(_value.darkColors, (value) { + return _then(_value.copyWith(darkColors: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalTextStylesCopyWith<$Res> get textStyles { + return $AppKitModalTextStylesCopyWith<$Res>(_value.textStyles, (value) { + return _then(_value.copyWith(textStyles: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $AppKitModalRadiusesCopyWith<$Res> get radiuses { + return $AppKitModalRadiusesCopyWith<$Res>(_value.radiuses, (value) { + return _then(_value.copyWith(radiuses: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$AppKitModalThemeDataImplCopyWith<$Res> + implements $AppKitModalThemeDataCopyWith<$Res> { + factory _$$AppKitModalThemeDataImplCopyWith(_$AppKitModalThemeDataImpl value, + $Res Function(_$AppKitModalThemeDataImpl) then) = + __$$AppKitModalThemeDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {AppKitModalColors lightColors, + AppKitModalColors darkColors, + AppKitModalTextStyles textStyles, + AppKitModalRadiuses radiuses}); + + @override + $AppKitModalColorsCopyWith<$Res> get lightColors; + @override + $AppKitModalColorsCopyWith<$Res> get darkColors; + @override + $AppKitModalTextStylesCopyWith<$Res> get textStyles; + @override + $AppKitModalRadiusesCopyWith<$Res> get radiuses; +} + +/// @nodoc +class __$$AppKitModalThemeDataImplCopyWithImpl<$Res> + extends _$AppKitModalThemeDataCopyWithImpl<$Res, _$AppKitModalThemeDataImpl> + implements _$$AppKitModalThemeDataImplCopyWith<$Res> { + __$$AppKitModalThemeDataImplCopyWithImpl(_$AppKitModalThemeDataImpl _value, + $Res Function(_$AppKitModalThemeDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lightColors = null, + Object? darkColors = null, + Object? textStyles = null, + Object? radiuses = null, + }) { + return _then(_$AppKitModalThemeDataImpl( + lightColors: null == lightColors + ? _value.lightColors + : lightColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + darkColors: null == darkColors + ? _value.darkColors + : darkColors // ignore: cast_nullable_to_non_nullable + as AppKitModalColors, + textStyles: null == textStyles + ? _value.textStyles + : textStyles // ignore: cast_nullable_to_non_nullable + as AppKitModalTextStyles, + radiuses: null == radiuses + ? _value.radiuses + : radiuses // ignore: cast_nullable_to_non_nullable + as AppKitModalRadiuses, + )); + } +} + +/// @nodoc + +class _$AppKitModalThemeDataImpl implements _AppKitModalThemeData { + const _$AppKitModalThemeDataImpl( + {this.lightColors = AppKitModalColors.lightMode, + this.darkColors = AppKitModalColors.darkMode, + this.textStyles = AppKitModalTextStyles.textStyle, + this.radiuses = const AppKitModalRadiuses()}); + + @override + @JsonKey() + final AppKitModalColors lightColors; + @override + @JsonKey() + final AppKitModalColors darkColors; + @override + @JsonKey() + final AppKitModalTextStyles textStyles; + @override + @JsonKey() + final AppKitModalRadiuses radiuses; + + @override + String toString() { + return 'AppKitModalThemeData(lightColors: $lightColors, darkColors: $darkColors, textStyles: $textStyles, radiuses: $radiuses)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppKitModalThemeDataImpl && + (identical(other.lightColors, lightColors) || + other.lightColors == lightColors) && + (identical(other.darkColors, darkColors) || + other.darkColors == darkColors) && + (identical(other.textStyles, textStyles) || + other.textStyles == textStyles) && + (identical(other.radiuses, radiuses) || + other.radiuses == radiuses)); + } + + @override + int get hashCode => + Object.hash(runtimeType, lightColors, darkColors, textStyles, radiuses); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AppKitModalThemeDataImplCopyWith<_$AppKitModalThemeDataImpl> + get copyWith => + __$$AppKitModalThemeDataImplCopyWithImpl<_$AppKitModalThemeDataImpl>( + this, _$identity); +} + +abstract class _AppKitModalThemeData implements AppKitModalThemeData { + const factory _AppKitModalThemeData( + {final AppKitModalColors lightColors, + final AppKitModalColors darkColors, + final AppKitModalTextStyles textStyles, + final AppKitModalRadiuses radiuses}) = _$AppKitModalThemeDataImpl; + + @override + AppKitModalColors get lightColors; + @override + AppKitModalColors get darkColors; + @override + AppKitModalTextStyles get textStyles; + @override + AppKitModalRadiuses get radiuses; + @override + @JsonKey(ignore: true) + _$$AppKitModalThemeDataImplCopyWith<_$AppKitModalThemeDataImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/reown_appkit/lib/modal/utils/asset_util.dart b/packages/reown_appkit/lib/modal/utils/asset_util.dart new file mode 100644 index 0000000..31d0af7 --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/asset_util.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +class AssetUtils { + static String getThemedAsset(BuildContext context, String assetName) { + if (AppKitModalTheme.maybeOf(context)?.isDarkMode == true) { + return 'lib/modal/assets/dark/$assetName'; + } + return 'lib/modal/assets/light/$assetName'; + } +} diff --git a/packages/reown_appkit/lib/modal/utils/core_utils.dart b/packages/reown_appkit/lib/modal/utils/core_utils.dart new file mode 100644 index 0000000..c9c2da9 --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/core_utils.dart @@ -0,0 +1,117 @@ +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class CoreUtils { + static bool isValidProjectID(String projectId) { + return RegExp(r'^[0-9a-fA-F]{32}$').hasMatch(projectId); + } + + static bool isValidEmail(String email) { + return RegExp( + r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") + .hasMatch(email); + } + + static bool isHttpUrl(String url) { + return url.startsWith('http://') || url.startsWith('https://'); + } + + static String createPlainUrl(String url) { + if (url.isEmpty) return url; + + String plainUrl = url; + if (!plainUrl.endsWith('/')) { + plainUrl = '$url/'; + } + return plainUrl; + } + + static String createSafeUrl(String url) { + if (url.isEmpty) return url; + + String safeUrl = url; + if (!safeUrl.contains('://')) { + safeUrl = url.replaceAll('/', '').replaceAll(':', ''); + safeUrl = '$safeUrl://'; + } else { + final parts = safeUrl.split('://'); + if (parts.last.isNotEmpty && parts.last != 'wc') { + if (!safeUrl.endsWith('/')) { + return '$safeUrl/'; + } + return safeUrl; + } else { + safeUrl = url.replaceFirst('://wc', '://'); + } + } + return safeUrl; + } + + static Uri? formatCustomSchemeUri(String? appUrl, String? wcUri) { + if (appUrl == null || appUrl.isEmpty) return null; + + if (isHttpUrl(appUrl)) { + return formatWebUrl(appUrl, wcUri); + } + + final safeAppUrl = createSafeUrl(appUrl); + + if (wcUri == null) { + return Uri.parse(safeAppUrl); + } + + final encodedWcUrl = Uri.encodeComponent(wcUri); + + return Uri.parse('${safeAppUrl}wc?uri=$encodedWcUrl'); + } + + static Uri? formatWebUrl(String? appUrl, String? wcUri) { + if (appUrl == null || appUrl.isEmpty) return null; + + if (!isHttpUrl(appUrl)) { + return formatCustomSchemeUri(appUrl, wcUri); + } + String plainAppUrl = createPlainUrl(appUrl); + + if (wcUri == null) { + return Uri.parse(plainAppUrl); + } + + final encodedWcUrl = Uri.encodeComponent(wcUri); + + return Uri.parse('${plainAppUrl}wc?uri=$encodedWcUrl'); + } + + static String formatChainBalance(double? chainBalance, {int precision = 3}) { + if (chainBalance == null) { + return '_.'.padRight(precision + 1, '_'); + } + if (chainBalance == 0.0) { + return '0.'.padRight(precision + 2, '0'); + } + return chainBalance.toStringAsPrecision(precision) + ..replaceAll(RegExp(r'([.]*0+)(?!.*\d)'), ''); + } + + static String getUserAgent() { + String userAgent = '${CoreConstants.X_SDK_TYPE}' + '-flutter-' + '${CoreConstants.X_SDK_VERSION}/' + '${CoreConstants.X_CORE_SDK_VERSION}/' + '${ReownCoreUtils.getOS()}'; + return userAgent; + } + + static Map getAPIHeaders( + String projectId, [ + String? referer, + ]) { + return { + 'x-project-id': projectId, + 'x-sdk-type': CoreConstants.X_SDK_TYPE, + 'x-sdk-version': 'flutter-${CoreConstants.X_SDK_VERSION}', + 'user-agent': getUserAgent(), + if (referer != null) 'referer': referer, + }; + } +} diff --git a/packages/reown_appkit/lib/modal/utils/debouncer.dart b/packages/reown_appkit/lib/modal/utils/debouncer.dart new file mode 100644 index 0000000..c5bcdad --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/debouncer.dart @@ -0,0 +1,20 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; + +class Debouncer { + final int milliseconds; + Timer? _timer; + + Debouncer({required this.milliseconds}); + + void run(VoidCallback action) { + if (_timer?.isActive == true) { + _timer?.cancel(); + _timer = null; + } + _timer = Timer( + Duration(milliseconds: milliseconds), + action, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/utils/platform_utils.dart b/packages/reown_appkit/lib/modal/utils/platform_utils.dart new file mode 100644 index 0000000..465e3cc --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/platform_utils.dart @@ -0,0 +1,79 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_core/reown_core.dart'; + +enum PlatformType { + mobile, + desktop, + web, + unknown, +} + +enum PlatformExact { + ios, + android, + web, + macos, + windows, + linux, + unknown; + + PlatformType get pType { + switch (this) { + case ios: + case android: + return PlatformType.mobile; + case macos: + case windows: + case linux: + return PlatformType.desktop; + case web: + return PlatformType.web; + default: + return PlatformType.unknown; + } + } + + static PlatformExact fromName(String name) { + return PlatformExact.values.firstWhereOrNull((e) { + return e.name.toLowerCase() == name.toLowerCase(); + }) ?? + PlatformExact.unknown; + } +} + +class PlatformUtils { + // + static PlatformExact getPlatformExact() { + final name = ReownCoreUtils.getId(); + return PlatformExact.fromName(name); + } + + static PlatformType getPlatformType() { + final name = ReownCoreUtils.getId(); + final platform = PlatformExact.fromName(name); + return platform.pType; + } + + static bool canDetectInstalledApps() { + return getPlatformType() == PlatformType.mobile; + } + + static bool isBottomSheet() { + return getPlatformType() == PlatformType.mobile; + } + + static bool isLongBottomSheet(Orientation orientation) { + return getPlatformType() == PlatformType.mobile && + orientation == Orientation.landscape; + } + + static bool isMobileWidth(double width) { + return width <= 500.0; + } + + static bool isTablet(BuildContext context) { + final mqData = MediaQueryData.fromView(View.of(context)); + return mqData.size.shortestSide < 600 ? false : true; + } +} diff --git a/packages/reown_appkit/lib/modal/utils/public/appkit_modal_default_networks.dart b/packages/reown_appkit/lib/modal/utils/public/appkit_modal_default_networks.dart new file mode 100644 index 0000000..528889d --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/public/appkit_modal_default_networks.dart @@ -0,0 +1,299 @@ +import 'package:collection/collection.dart'; +import 'package:reown_appkit/modal/models/public/appkit_network_info.dart'; + +class AppKitModalNetworks { + // https://github.com/WalletConnect/blockchain-api/blob/master/SUPPORTED_CHAINS.md + static Map> supported = { + 'eip155': [ + AppKitModalNetworkInfo( + name: 'Ethereum', + chainId: '1', + chainIcon: chainImagesId['1'], + currency: 'ETH', + rpcUrl: 'https://ethereum-rpc.publicnode.com', + explorerUrl: 'https://etherscan.io', + ), + AppKitModalNetworkInfo( + name: 'Optimism', + chainId: '10', + chainIcon: chainImagesId['10'], + currency: 'ETH', + rpcUrl: 'https://mainnet.optimism.io/', + explorerUrl: 'https://optimistic.etherscan.io', + ), + AppKitModalNetworkInfo( + name: 'Binance Smart Chain', + chainId: '56', + chainIcon: chainImagesId['56'], + currency: 'BNB', + rpcUrl: 'https://bsc-dataseed.binance.org/', + explorerUrl: 'https://bscscan.com', + ), + AppKitModalNetworkInfo( + name: 'Gnosis Chain', + chainId: '100', + chainIcon: chainImagesId['100'], + currency: 'xDAI', + rpcUrl: 'https://rpc.gnosischain.com', + explorerUrl: 'https://gnosis.blockscout.com', + ), + AppKitModalNetworkInfo( + name: 'Polygon', + chainId: '137', + chainIcon: chainImagesId['137'], + currency: 'MATIC', + rpcUrl: 'https://polygon.drpc.org', + explorerUrl: 'https://polygonscan.com', + ), + AppKitModalNetworkInfo( + name: 'zkSync Era', + chainId: '324', + chainIcon: chainImagesId['324'], + currency: 'ETH', + rpcUrl: 'https://mainnet.era.zksync.io', + explorerUrl: 'https://explorer.zksync.io', + ), + AppKitModalNetworkInfo( + name: 'Polygon zkEVM', + chainId: '1101', + chainIcon: chainImagesId['1101'], + currency: 'ETH', + rpcUrl: 'https://rpc-mainnet.matic.network', + explorerUrl: 'https://explorer-evm.polygon.technology', + ), + AppKitModalNetworkInfo( + name: 'Mantle', + chainId: '5000', + chainIcon: chainImagesId['5000'], + currency: 'BIT', + rpcUrl: 'https://rpc.mantlenetwork.io', + explorerUrl: 'https://explorer.mantlenetwork.io', + ), + AppKitModalNetworkInfo( + name: 'Klaytn Mainnet', + chainId: '8217', + chainIcon: chainImagesId['8217'], + currency: 'KLAY', + rpcUrl: 'https://public-node-api.klaytnapi.com/v1/cypress', + explorerUrl: 'https://scope.klaytn.com', + ), + AppKitModalNetworkInfo( + name: 'Base', + chainId: '8453', + chainIcon: chainImagesId['8453'], + currency: 'ETH', + rpcUrl: 'https://mainnet.base.org', + explorerUrl: 'https://basescan.org', + ), + AppKitModalNetworkInfo( + name: 'Arbitrum', + chainId: '42161', + chainIcon: chainImagesId['42161'], + currency: 'ETH', + rpcUrl: 'https://arbitrum.blockpi.network/v1/rpc/public', + explorerUrl: 'https://arbiscan.io/', + ), + AppKitModalNetworkInfo( + name: 'Celo', + chainId: '42220', + chainIcon: chainImagesId['42220'], + currency: 'CELO', + rpcUrl: 'https://forno.celo.org', + explorerUrl: 'https://explorer.celo.org/mainnet', + ), + AppKitModalNetworkInfo( + name: 'Avalanche', + chainId: '43114', + chainIcon: chainImagesId['43114'], + currency: 'AVAX', + rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', + explorerUrl: 'https://snowtrace.io', + ), + AppKitModalNetworkInfo( + name: 'Linea', + chainId: '59144', + chainIcon: chainImagesId['59144'], + currency: 'ETH', + rpcUrl: 'https://rpc.linea.build', + explorerUrl: 'https://explorer.linea.build', + ), + AppKitModalNetworkInfo( + name: 'Zora', + chainId: '7777777', + chainIcon: chainImagesId['7777777'], + currency: 'ETH', + rpcUrl: 'https://rpc.zora.energy', + explorerUrl: 'https://explorer.zora.energy', + ), + AppKitModalNetworkInfo( + name: 'Aurora', + chainId: '1313161554', + chainIcon: chainImagesId['1313161554'], + currency: 'ETH', + rpcUrl: 'https://mainnet.aurora.dev', + explorerUrl: 'https://explorer.aurora.dev', + ), + ], + }; + + static Map> extra = { + 'eip155': [ + AppKitModalNetworkInfo( + name: 'Fantom', + chainId: '250', + chainIcon: chainImagesId['250'], + currency: 'FTM', + rpcUrl: 'https://rpc.ftm.tools/', + explorerUrl: 'https://ftmscan.com', + ), + AppKitModalNetworkInfo( + name: 'EVMos', + chainId: '9001', + chainIcon: chainImagesId['9001'], + currency: 'EVMOS', + rpcUrl: 'https://evmos-evm.publicnode.com', + explorerUrl: '', + ), + AppKitModalNetworkInfo( + name: 'Iotx', + chainId: '4689', + chainIcon: chainImagesId['4689'], + currency: 'IOTX', + rpcUrl: 'https://rpc.ankr.com/iotex', + explorerUrl: 'https://iotexscan.io', + ), + AppKitModalNetworkInfo( + name: 'Metis', + chainId: '1088', + chainIcon: chainImagesId['1088'], + currency: 'METIS', + rpcUrl: 'https://metis-mainnet.public.blastapi.io', + explorerUrl: 'https://andromeda-explorer.metis.io', + ), + ] + }; + + static Map> test = { + 'eip155': [ + AppKitModalNetworkInfo( + name: 'Sepolia', + chainId: '11155111', + currency: 'SEP', + rpcUrl: 'https://ethereum-sepolia.publicnode.com', + explorerUrl: 'https://sepolia.etherscan.io/', + isTestNetwork: true, + ), + AppKitModalNetworkInfo( + name: 'Holesky', + chainId: '17000', + chainIcon: chainImagesId['17000'], + currency: 'ETH', + rpcUrl: 'https://rpc.holesky.test', + explorerUrl: 'https://explorer.holesky.test', + isTestNetwork: true, + ), + AppKitModalNetworkInfo( + name: 'Mumbai', + chainId: '80001', + currency: 'MATIC', + rpcUrl: 'https://polygon-mumbai-bor-rpc.publicnode.com', + extraRpcUrls: [ + 'https://rpc.ankr.com/polygon_mumbai', + 'https://polygon-testnet.public.blastapi.io', + 'https://polygon-mumbai.blockpi.network/v1/rpc/public', + ], + explorerUrl: 'https://mumbai.polygonscan.com', + isTestNetwork: true, + ), + AppKitModalNetworkInfo( + name: 'Amoy', + chainId: '80002', + currency: 'MATIC', + rpcUrl: 'https://rpc-amoy.polygon.technology/', + extraRpcUrls: [], + explorerUrl: 'https://amoy.polygonscan.com', + isTestNetwork: true, + ) + ], + }; + + static Map chainImagesId = { + // Ethereum + '1': 'ba0ba0cd-17c6-4806-ad93-f9d174f17900', + // Optimism + '10': 'ab9c186a-c52f-464b-2906-ca59d760a400', + // Binance Smart Chain + '56': '93564157-2e8e-4ce7-81df-b264dbee9b00', + // Gnosis + '100': '02b53f6a-e3d4-479e-1cb4-21178987d100', + // Polygon + '137': '41d04d42-da3b-4453-8506-668cc0727900', + // Fantom + '250': '06b26297-fe0c-4733-5d6b-ffa5498aac00', + // Filecoin + '314': '5a73b3dd-af74-424e-cae0-0de859ee9400', + // ZkSync + '324': 'b310f07f-4ef7-49f3-7073-2a0a39685800', + // Polygon zkEVM + '1101': '1f078e54-72f0-4b5b-89ca-11ea0483e900', + // Metis, + '1088': '3897a66d-40b9-4833-162f-a2c90531c900', + // Moonbeam + '1284': '161038da-44ae-4ec7-1208-0ea569454b00', + // Moonriver + '1285': 'f1d73bb6-5450-4e18-38f7-fb6484264a00', + // Iotx + '4689': '34e68754-e536-40da-c153-6ef2e7188a00', + // Mantle + '5000': 'f784171a-f4cf-4c4d-a0b0-faf2abf35b00', + // Klaytn + '8217': 'b1707ac9-94f1-4cd8-8e41-80af13cd5800', + // Linea + '59144': 'b6a252d9-b084-4bdc-e1ba-0d3186958700', + // Base + '8453': '7289c336-3981-4081-c5f4-efc26ac64a00', + // EVMos + '9001': 'f926ff41-260d-4028-635e-91913fc28e00', + // Arbitrum + '42161': '3bff954d-5cb0-47a0-9a23-d20192e74600', + // Celo + '42220': 'ab781bbc-ccc6-418d-d32d-789b15da1f00', + // Avalanche + '43114': '30c46e53-e989-45fb-4549-be3bd4eb3b00', + // Zora + '7777777': '845c60df-d429-4991-e687-91ae45791600', + // Aurora + '1313161554': '3ff73439-a619-4894-9262-4470c773a100', + }; + + static AppKitModalNetworkInfo? getNetworkById( + String namespace, + String chainId, + ) { + return supported[namespace]?.firstWhere((e) => e.chainId == chainId); + } + + static List getNetworks(String namespace) { + return supported[namespace] ?? []; + } + + static String? getNamespaceForChainId(String chainId) { + // final allChains = supported.values.expand((e) => e).toList(); + String? namespace; + final namespaces = supported.keys.toList(); + for (var ns in namespaces) { + final found = supported[ns]!.firstWhereOrNull( + (e) => e.chainId == chainId, + ); + if (found != null) { + namespace = ns; + break; + } + } + return namespace; + } + + static String getNetworkIconId(String chainId) { + return chainImagesId[chainId] ?? ''; + } +} diff --git a/packages/reown_appkit/lib/modal/utils/public/appkit_modal_siwe_utils.dart b/packages/reown_appkit/lib/modal/utils/public/appkit_modal_siwe_utils.dart new file mode 100644 index 0000000..a514c01 --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/public/appkit_modal_siwe_utils.dart @@ -0,0 +1,41 @@ +import 'package:reown_appkit/modal/services/siwe_service/siwe_service_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class SIWEUtils { + /// Given SIWECreateMessageArgs will format message according to EIP-4361 https://docs.login.xyz/general-information/siwe-overview/eip-4361 + static String formatMessage(SIWECreateMessageArgs params) { + return siweService.instance!.formatMessage( + params, + ); + } + + static String getAddressFromMessage(String message) { + return AuthSignature.getAddressFromMessage(message); + } + + static String getChainIdFromMessage(String message) { + return AuthSignature.getChainIdFromMessage(message); + } + + // verifies CACAO signature + // Used by the wallet after formatting the message + static Future verifySignature( + String address, + String message, + CacaoSignature cacaoSignature, + String chainId, + String projectId, + ) async { + return AuthSignature.verifySignature( + address, + message, + cacaoSignature, + chainId, + projectId, + ); + } + + static String generateNonce() { + return AuthUtils.generateNonce(); + } +} diff --git a/packages/reown_appkit/lib/modal/utils/public/appkit_modal_utils.dart b/packages/reown_appkit/lib/modal/utils/public/appkit_modal_utils.dart new file mode 100644 index 0000000..9193060 --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/public/appkit_modal_utils.dart @@ -0,0 +1,2 @@ +export 'appkit_modal_default_networks.dart'; +export 'appkit_modal_siwe_utils.dart'; diff --git a/packages/reown_appkit/lib/modal/utils/render_utils.dart b/packages/reown_appkit/lib/modal/utils/render_utils.dart new file mode 100644 index 0000000..8160e0e --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/render_utils.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +class RenderUtils { + static String shorten(String value, {bool short = false}) { + return short && value.length > 8 ? '${value.substring(0, 8)}..' : value; + } + + static String truncate(String value, {int length = 4}) { + if (value.length <= length) { + return value; + } + + return '${value.substring(0, length)}...${value.substring(value.length - length)}'; + } + + static List get defaultAvatarColors => [ + Color(0xFFf5ccfc), + Color(0xFFdba4f5), + Color(0xFF9a8ee8), + Color(0xFF6493da), + Color(0xFF6ebdea), + ]; + + static List generateAvatarColors(String? address) { + if ((address ?? '').isEmpty) { + return defaultAvatarColors; + } + + try { + final hash = address!.toLowerCase().replaceFirst('0x', ''); + final baseColor = hash.substring(0, 6); + final rgbColor = _hexToRgb(baseColor); + + final List colors = []; + + for (int i = 0; i < 5; i += 1) { + final tintedColor = _tintColor(rgbColor, 0.15 * i); + colors.add( + Color.fromRGBO( + tintedColor[0], + tintedColor[1], + tintedColor[2], + 1.0, + ), + ); + } + + return colors; + } catch (e) { + return defaultAvatarColors; + } + } + + static List _hexToRgb(String hex) { + final bigint = int.parse(hex, radix: 16); + + final r = (bigint >> 16) & 255; + final g = (bigint >> 8) & 255; + final b = bigint & 255; + + return [r, g, b]; + } + + static List _tintColor(List rgb, double tint) { + final tintedR = (rgb[0] + (255 - rgb[0]) * tint).round(); + final tintedG = (rgb[1] + (255 - rgb[1]) * tint).round(); + final tintedB = (rgb[2] + (255 - rgb[2]) * tint).round(); + + return [tintedR, tintedG, tintedB]; + } + + static String colorToRGBA(Color color) { + final r = color.red; + final g = color.green; + final b = color.blue; + final a = color.opacity; + return 'rgba($r, $g, $b, $a)'; + } + + static String colorToHex(Color color) { + return '${color.alpha.toRadixString(16).padLeft(2, '0')}' + '${color.red.toRadixString(16).padLeft(2, '0')}' + '${color.green.toRadixString(16).padLeft(2, '0')}' + '${color.blue.toRadixString(16).padLeft(2, '0')}'; + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/avatars/account_avatar.dart b/packages/reown_appkit/lib/modal/widgets/avatars/account_avatar.dart new file mode 100644 index 0000000..f4bda0e --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/avatars/account_avatar.dart @@ -0,0 +1,136 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/utils/core_utils.dart'; +import 'package:reown_appkit/modal/utils/render_utils.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AccountAvatar extends StatefulWidget { + const AccountAvatar({ + super.key, + required this.service, + this.size = 40.0, + this.disabled = false, + }); + + final IAppKitModal service; + final double size; + final bool disabled; + + @override + State createState() => _AccountAvatarState(); +} + +class _AccountAvatarState extends State { + String? _avatarUrl; + String? _address; + + @override + void initState() { + super.initState(); + widget.service.addListener(_modalNotifyListener); + _modalNotifyListener(); + } + + @override + void dispose() { + widget.service.removeListener(_modalNotifyListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + return SizedBox( + width: widget.size, + height: widget.size, + child: ClipRRect( + borderRadius: BorderRadius.circular(widget.size / 2), + child: ColorFiltered( + colorFilter: ColorFilter.mode( + widget.disabled ? themeColors.foreground300 : Colors.transparent, + BlendMode.saturation, + ), + child: (_avatarUrl ?? '').isNotEmpty + ? CachedNetworkImage( + imageUrl: _avatarUrl!, + httpHeaders: CoreUtils.getAPIHeaders( + widget.service.appKit!.core.projectId, + ), + fadeInDuration: const Duration(milliseconds: 500), + fadeOutDuration: const Duration(milliseconds: 500), + ) + : GradientOrb(address: _address, size: widget.size), + ), + ), + ); + } + + void _modalNotifyListener() { + setState(() { + _avatarUrl = widget.service.avatarUrl; + _address = widget.service.session?.address; + }); + } +} + +class GradientOrb extends StatelessWidget { + const GradientOrb({ + super.key, + this.address, + this.size = 40.0, + }); + final String? address; + final double size; + + @override + Widget build(BuildContext context) { + List colors = RenderUtils.generateAvatarColors(address); + final themeColors = AppKitModalTheme.colorsOf(context); + return Stack( + children: [ + Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: colors[4], + borderRadius: BorderRadius.circular(size / 2.0), + boxShadow: [ + BoxShadow( + color: themeColors.grayGlass025, + spreadRadius: 1.0, + blurRadius: 0.0, + ), + ], + ), + ), + ..._buildGradients(colors..removeAt(4)), + ], + ); + } + + List _buildGradients(List colors) { + double size = 1.5; + final gradients = colors.reversed.map((c) { + size -= 0.1; + return _gradient(c, size); + }).toList(); + gradients.add( + _gradient(Colors.white.withOpacity(0.8), 0.5), + ); + return gradients; + } + + Widget _gradient(Color color, double size) => Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [color, color, color.withOpacity(0.0)], + stops: [0.0, size / 4, size], + center: const Alignment(0.3, -0.3), + ), + ), + ), + ); +} diff --git a/packages/reown_appkit/lib/modal/widgets/avatars/account_orb.dart b/packages/reown_appkit/lib/modal/widgets/avatars/account_orb.dart new file mode 100644 index 0000000..24c1218 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/avatars/account_orb.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/avatars/account_avatar.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; + +class Orb extends StatelessWidget { + const Orb({super.key, this.size = 70.0}); + final double size; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final service = ModalProvider.of(context).service; + return Container( + width: size, + height: size, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size / 2), + border: Border.all( + color: themeColors.grayGlass005, + width: 8.0, + ), + ), + child: AccountAvatar( + service: service, + size: size - 8.0, + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/avatars/loading_border.dart b/packages/reown_appkit/lib/modal/widgets/avatars/loading_border.dart new file mode 100644 index 0000000..470081a --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/avatars/loading_border.dart @@ -0,0 +1,221 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +class LoadingBorder extends StatefulWidget { + const LoadingBorder({ + super.key, + required this.child, + this.padding = 16.0, + this.strokeWidth = 4.0, + this.borderRadius = 0.0, + this.animate = true, + this.isNetwork = false, + }); + final Widget child; + final double padding; + final double strokeWidth; + final double borderRadius; + final bool animate; + final bool isNetwork; + + @override + State createState() => _LoadingBorderState(); +} + +class _LoadingBorderState extends State + with TickerProviderStateMixin { + // + late AnimationController _controller; + late Animation _tweenAnimation; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + + _tweenAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(_controller); + + _controller.repeat(); + } + + @override + void didUpdateWidget(covariant LoadingBorder oldWidget) { + super.didUpdateWidget(oldWidget); + if (!widget.animate) { + _controller.stop(); + setState(() {}); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + return Stack( + alignment: Alignment.center, + children: [ + SizedBox( + height: kSelectedWalletIconHeight + widget.padding, + width: kSelectedWalletIconHeight + widget.padding, + child: CustomPaint( + painter: widget.isNetwork + ? _NetworkPainter( + sides: 6, + radius: + (kSelectedWalletIconHeight / 2) + (widget.padding / 2), + radians: 9.95, + frontColor: themeColors.accent100, + strokeWidth: widget.strokeWidth, + ) + : _CircularBorderPainter( + borderRadius: widget.borderRadius, + frontColor: themeColors.accent100, + strokeWidth: widget.strokeWidth, + ), + child: RotationTransition( + turns: _tweenAnimation, + child: CustomPaint( + painter: _RotatingPainter( + show: widget.animate, + backColor: themeColors.background125, + ), + ), + ), + ), + ), + SizedBox.square( + dimension: kSelectedWalletIconHeight, + child: widget.child, + ), + ], + ); + } +} + +class _CircularBorderPainter extends CustomPainter { + const _CircularBorderPainter({ + required this.frontColor, + required this.strokeWidth, + this.borderRadius = 32.0, + }); + final Color frontColor; + final double strokeWidth, borderRadius; + + @override + void paint(Canvas canvas, Size size) { + final w = size.width, h = size.height; + + final paint2 = Paint() + ..strokeWidth = strokeWidth + ..color = frontColor + ..style = PaintingStyle.stroke; + + final rect1 = Rect.fromCenter( + center: Offset(w / 2, h / 2), + width: w * 0.95, + height: h * 0.95, + ); + + final rrect1 = RRect.fromRectAndRadius( + rect1, + Radius.circular(borderRadius + 4.0), + ); + canvas.drawRRect(rrect1, paint2); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} + +class _NetworkPainter extends CustomPainter { + const _NetworkPainter({ + required this.sides, + required this.radius, + required this.radians, + required this.frontColor, + required this.strokeWidth, + }); + final double sides; + final double radius; + final double radians; + final Color frontColor; + final double strokeWidth; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..color = frontColor + ..strokeWidth = strokeWidth + ..strokeJoin = StrokeJoin.round + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + var path = Path(); + var angle = (math.pi * 2) / sides; + Offset center = Offset(size.width / 2, size.height / 2); + Offset startPoint = Offset( + radius * math.cos(radians), + radius * math.sin(radians), + ); + path.moveTo(startPoint.dx + center.dx, startPoint.dy + center.dy); + for (int i = 1; i <= sides; i++) { + double x = radius * math.cos(radians + angle * i) + center.dx; + double y = radius * math.sin(radians + angle * i) + center.dy; + path.lineTo(x, y); + } + path.close(); + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} + +class _RotatingPainter extends CustomPainter { + const _RotatingPainter({ + required this.backColor, + this.show = true, + }); + final Color backColor; + final bool show; + + @override + void paint(Canvas canvas, Size size) { + final w = size.width, h = size.height; + final paint1 = Paint() + ..color = backColor + ..style = PaintingStyle.fill; + + final rect2 = Rect.fromCenter( + center: Offset(w / 2, h / 2), + width: w * 1.4, + height: h * 1.4, + ); + + canvas.drawArc( + rect2, + 0, + show ? math.pi * 1.5 : math.pi * 2, + true, + paint1, + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/packages/reown_appkit/lib/modal/widgets/avatars/wallet_avatar.dart b/packages/reown_appkit/lib/modal/widgets/avatars/wallet_avatar.dart new file mode 100644 index 0000000..0f4dab4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/avatars/wallet_avatar.dart @@ -0,0 +1,106 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/utils/core_utils.dart'; + +class ListAvatar extends StatelessWidget { + const ListAvatar({ + super.key, + this.imageUrl, + this.borderRadius, + this.isNetwork = false, + this.color, + this.disabled = false, + }); + final String? imageUrl; + final double? borderRadius; + final bool isNetwork; + final Color? color; + final bool disabled; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + final radius = borderRadius ?? radiuses.radiusM; + final projectId = explorerService.instance.projectId; + return Stack( + children: [ + AspectRatio( + aspectRatio: 1.0, + child: Container( + decoration: isNetwork + ? ShapeDecoration( + shape: StarBorder.polygon( + side: BorderSide( + color: color ?? themeColors.grayGlass010, + width: 1.0, + strokeAlign: BorderSide.strokeAlignInside, + ), + pointRounding: 0.3, + sides: 6, + ), + ) + : BoxDecoration( + borderRadius: BorderRadius.circular(radius), + border: Border.all( + color: color ?? themeColors.grayGlass010, + width: 1.0, + strokeAlign: BorderSide.strokeAlignOutside, + ), + ), + ), + ), + AspectRatio( + aspectRatio: 1.0, + child: Container( + decoration: isNetwork + ? ShapeDecoration( + shape: StarBorder.polygon( + pointRounding: 0.3, + sides: 6, + ), + ) + : BoxDecoration( + borderRadius: BorderRadius.circular(radius), + ), + clipBehavior: Clip.antiAlias, + child: (imageUrl ?? '').isNotEmpty + ? ColorFiltered( + colorFilter: ColorFilter.mode( + disabled ? Colors.grey : Colors.transparent, + BlendMode.saturation, + ), + child: CachedNetworkImage( + imageUrl: imageUrl!, + httpHeaders: CoreUtils.getAPIHeaders(projectId), + fadeInDuration: const Duration(milliseconds: 500), + fadeOutDuration: const Duration(milliseconds: 500), + errorWidget: (context, url, error) => ColoredBox( + color: themeColors.grayGlass005, + ), + ), + ) + : isNetwork + ? Padding( + padding: const EdgeInsets.all(18.0), + child: SvgPicture.asset( + 'lib/modal/assets/icons/network.svg', + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + themeColors.grayGlass030, + BlendMode.srcIn, + ), + ), + ) + : ColoredBox( + color: themeColors.grayGlass005, + ), + ), + ), + ], + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/address_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/address_button.dart new file mode 100644 index 0000000..78d2dae --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/address_button.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/utils/render_utils.dart'; +import 'package:reown_appkit/modal/widgets/avatars/account_avatar.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AddressButton extends StatefulWidget { + const AddressButton({ + super.key, + required this.service, + this.size = BaseButtonSize.regular, + this.onTap, + }); + final IAppKitModal service; + final BaseButtonSize size; + final VoidCallback? onTap; + + @override + State createState() => _AddressButtonState(); +} + +class _AddressButtonState extends State { + String? _address; + + @override + void initState() { + super.initState(); + _modalNotifyListener(); + widget.service.addListener(_modalNotifyListener); + } + + @override + void dispose() { + widget.service.removeListener(_modalNotifyListener); + super.dispose(); + } + + void _modalNotifyListener() { + setState(() { + _address = widget.service.session?.address; + }); + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + return BaseButton( + size: widget.size, + onTap: widget.onTap, + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass005; + } + return themeColors.grayGlass010; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass015; + } + return themeColors.foreground100; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: states.contains(MaterialState.disabled) + ? BorderSide(color: themeColors.grayGlass005, width: 1.0) + : BorderSide(color: themeColors.grayGlass010, width: 1.0), + borderRadius: BorderRadius.circular(widget.size.height / 2), + ); + }, + ), + ), + overridePadding: MaterialStateProperty.all( + EdgeInsets.only( + left: 6.0, + right: widget.size == BaseButtonSize.small ? 12.0 : 16.0, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + AccountAvatar( + service: widget.service, + size: widget.size.height * 0.7, + disabled: widget.onTap == null, + ), + const SizedBox.square(dimension: 4.0), + Text(RenderUtils.truncate(_address ?? '')), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/address_copy_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/address_copy_button.dart new file mode 100644 index 0000000..ec590f2 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/address_copy_button.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/text/appkit_address.dart'; + +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AddressCopyButton extends StatelessWidget { + const AddressCopyButton({super.key}); + + @override + Widget build(BuildContext context) { + final service = ModalProvider.of(context).service; + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + return GestureDetector( + onTap: () async { + await Clipboard.setData(ClipboardData(text: service.session!.address!)); + toastService.instance.show( + ToastMessage(type: ToastType.success, text: 'Address copied'), + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Address( + service: service, + style: themeData.textStyles.title600.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: 8.0), + SvgPicture.asset( + 'lib/modal/assets/icons/copy.svg', + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + themeColors.foreground250, + BlendMode.srcIn, + ), + width: 20.0, + height: 20.0, + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart new file mode 100644 index 0000000..cda8d64 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/utils/public/appkit_modal_default_networks.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; + +// Export +class BalanceButton extends StatefulWidget { + static const balanceDefault = '_._'; + + const BalanceButton({ + super.key, + required this.service, + this.size = BaseButtonSize.regular, + this.onTap, + }); + + final IAppKitModal service; + final BaseButtonSize size; + final VoidCallback? onTap; + + @override + State createState() => _BalanceButtonState(); +} + +class _BalanceButtonState extends State { + String _balance = BalanceButton.balanceDefault; + String? _tokenImage; + String? _tokenName; + + @override + void initState() { + super.initState(); + _modalNotifyListener(); + widget.service.addListener(_modalNotifyListener); + } + + @override + void dispose() { + widget.service.removeListener(_modalNotifyListener); + super.dispose(); + } + + void _modalNotifyListener() { + setState(() { + final chainId = widget.service.selectedChain?.chainId ?? '1'; + final imageId = AppKitModalNetworks.getNetworkIconId(chainId); + _tokenImage = explorerService.instance.getAssetImageUrl(imageId); + _balance = widget.service.chainBalance; + _tokenName = widget.service.selectedChain?.currency; + }); + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + return BaseButton( + size: widget.size, + onTap: widget.onTap, + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass005; + } + return themeColors.grayGlass010; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass015; + } + return themeColors.foreground100; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: states.contains(MaterialState.disabled) + ? BorderSide(color: themeColors.grayGlass005, width: 1.0) + : BorderSide(color: themeColors.grayGlass010, width: 1.0), + borderRadius: BorderRadius.circular(widget.size.height / 2), + ); + }, + ), + ), + overridePadding: MaterialStateProperty.all( + EdgeInsets.only( + left: 6.0, + right: widget.size == BaseButtonSize.small ? 12.0 : 16.0, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + RoundedIcon( + imageUrl: _tokenImage, + size: widget.size.height * 0.7, + ), + const SizedBox.square(dimension: 4.0), + Text('$_balance ${_tokenName ?? ''}'), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/base_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/base_button.dart new file mode 100644 index 0000000..67e1435 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/base_button.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +enum BaseButtonSize { + small, + regular, + big; + + double get height { + switch (this) { + case small: + return 32.0; + case regular: + return 40.0; + default: + return 48.0; + } + } + + double get iconSize { + switch (this) { + case small: + return 16.0; + case regular: + return 20.0; + default: + return 24.0; + } + } +} + +class BaseButton extends StatelessWidget { + const BaseButton({ + super.key, + required this.child, + required this.size, + this.onTap, + this.buttonStyle, + this.overridePadding, + }); + final Widget child; + final VoidCallback? onTap; + final BaseButtonSize size; + final ButtonStyle? buttonStyle; + final MaterialStateProperty? overridePadding; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final textStyle = size == BaseButtonSize.small + ? themeData.textStyles.small600 + : themeData.textStyles.paragraph600; + return FilledButton( + onPressed: onTap, + child: child, + style: ButtonStyle( + textStyle: MaterialStateProperty.all(textStyle), + minimumSize: MaterialStateProperty.all(Size( + size.height, + size.height, + )), + maximumSize: MaterialStateProperty.all(Size( + 1000.0, + size.height, + )), + padding: overridePadding ?? + MaterialStateProperty.all( + const EdgeInsets.only( + left: 16.0, + right: 16.0, + ), + ), + ).merge(buttonStyle), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/connect_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/connect_button.dart new file mode 100644 index 0000000..9d1b80f --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/connect_button.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/modal/widgets/circular_loader.dart'; + +enum ConnectButtonState { + error, + idle, + disabled, + connecting, + connected, + none, +} + +class ConnectButton extends StatelessWidget { + const ConnectButton({ + super.key, + this.size = BaseButtonSize.regular, + this.state = ConnectButtonState.idle, + this.serviceStatus = AppKitModalStatus.idle, + this.titleOverride, + this.onTap, + }); + final BaseButtonSize size; + final ConnectButtonState state; + final AppKitModalStatus serviceStatus; + final String? titleOverride; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final connecting = state == ConnectButtonState.connecting; + final disabled = state == ConnectButtonState.disabled; + final connected = state == ConnectButtonState.connected; + final radiuses = AppKitModalTheme.radiusesOf(context); + final borderRadius = radiuses.isSquare() ? 0.0 : size.height / 2; + final showLoading = connecting || serviceStatus.isLoading; + return BaseButton( + onTap: disabled || connecting + ? null + : serviceStatus.isInitialized + ? onTap + : null, + size: size, + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (connecting) { + return themeColors.grayGlass010; + } + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass005; + } + return themeColors.accent100; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (connecting) { + return themeColors.accent100; + } + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass015; + } + return themeColors.inverse100; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: (states.contains(MaterialState.disabled) || connecting) + ? BorderSide(color: themeColors.grayGlass010, width: 1.0) + : BorderSide.none, + borderRadius: BorderRadius.circular(borderRadius), + ); + }, + ), + ), + overridePadding: MaterialStateProperty.all( + showLoading + ? const EdgeInsets.only(left: 6.0, right: 16.0) + : const EdgeInsets.only(left: 16.0, right: 16.0), + ), + child: showLoading + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox.square(dimension: kPadding6), + CircularLoader( + size: size.height * 0.4, + strokeWidth: size == BaseButtonSize.small ? 1.0 : 1.5, + ), + const SizedBox.square(dimension: kPadding6), + if (connecting) + Text(titleOverride ?? UIConstants.connectButtonConnecting), + if (serviceStatus.isLoading) + size == BaseButtonSize.small + ? Text( + titleOverride ?? UIConstants.connectButtonIdleShort) + : Text(titleOverride ?? UIConstants.connectButtonIdle), + ], + ) + : connected + ? Text(titleOverride ?? UIConstants.connectButtonConnected) + : size == BaseButtonSize.small + ? Text(titleOverride ?? UIConstants.connectButtonIdleShort) + : Text(titleOverride ?? UIConstants.connectButtonIdle), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/network_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/network_button.dart new file mode 100644 index 0000000..61bd877 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/network_button.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/models/public/appkit_network_info.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/utils/public/appkit_modal_default_networks.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/circular_loader.dart'; + +class NetworkButton extends StatelessWidget { + const NetworkButton({ + super.key, + this.size = BaseButtonSize.regular, + this.serviceStatus = AppKitModalStatus.idle, + this.chainInfo, + this.onTap, + }); + final AppKitModalNetworkInfo? chainInfo; + final BaseButtonSize size; + final AppKitModalStatus serviceStatus; + final VoidCallback? onTap; + + String _getImageUrl(AppKitModalNetworkInfo chainInfo) { + if (chainInfo.chainIcon != null && chainInfo.chainIcon!.contains('http')) { + return chainInfo.chainIcon!; + } + final imageId = AppKitModalNetworks.getNetworkIconId(chainInfo.chainId); + return explorerService.instance.getAssetImageUrl(imageId); + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + String imageUrl = ''; + if (chainInfo != null && (chainInfo!.chainIcon ?? '').isNotEmpty) { + imageUrl = _getImageUrl(chainInfo!); + } + final radiuses = AppKitModalTheme.radiusesOf(context); + final borderRadius = radiuses.isSquare() ? 0.0 : size.height / 2; + return BaseButton( + size: size, + onTap: serviceStatus.isInitialized ? onTap : null, + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass005; + } + return themeColors.grayGlass010; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass015; + } + return themeColors.foreground100; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: states.contains(MaterialState.disabled) + ? BorderSide(color: themeColors.grayGlass005, width: 1.0) + : BorderSide(color: themeColors.grayGlass010, width: 1.0), + borderRadius: BorderRadius.circular(borderRadius), + ); + }, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + serviceStatus.isLoading + ? Row( + children: [ + const SizedBox.square(dimension: kPadding6), + CircularLoader( + size: size.height * 0.4, + strokeWidth: size == BaseButtonSize.small ? 1.0 : 1.5, + ), + const SizedBox.square(dimension: kPadding6), + ], + ) + : RoundedIcon( + assetPath: 'lib/modal/assets/icons/network.svg', + imageUrl: imageUrl, + size: size.height * 0.7, + assetColor: themeColors.inverse100, + padding: size == BaseButtonSize.small ? 5.0 : 6.0, + ), + const SizedBox.square(dimension: 4.0), + Text( + chainInfo?.name ?? + (size == BaseButtonSize.small + ? UIConstants.selectNetworkShort + : UIConstants.selectNetwork), + ), + ], + ), + overridePadding: MaterialStateProperty.all( + const EdgeInsets.only(left: 6.0, right: 16.0), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart new file mode 100644 index 0000000..108e95a --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class PrimaryButton extends StatelessWidget { + final String title; + final VoidCallback? onTap; + final bool loading; + const PrimaryButton({ + super.key, + required this.title, + this.onTap, + this.loading = false, + }); + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return BaseButton( + size: BaseButtonSize.big, + child: loading + ? SizedBox( + height: BaseButtonSize.big.height * 0.4, + width: BaseButtonSize.big.height * 0.4, + child: CircularProgressIndicator( + color: themeColors.accent100, + strokeWidth: 2.0, + ), + ) + : Text(title), + onTap: loading ? null : onTap, + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass010; + } + return themeColors.accent100; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.foreground200; + } + return themeColors.inverse100; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: BorderSide( + color: themeColors.grayGlass010, + width: 1.0, + ), + borderRadius: radiuses.isSquare() + ? BorderRadius.all(Radius.zero) + : BorderRadius.circular(16.0), + ); + }, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/secondary_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/secondary_button.dart new file mode 100644 index 0000000..c1fa504 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/secondary_button.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class SecondaryButton extends StatelessWidget { + final String title; + final VoidCallback? onTap; + const SecondaryButton({ + super.key, + required this.title, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return BaseButton( + size: BaseButtonSize.big, + child: Text(title), + onTap: onTap, + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) => themeColors.grayGlass001, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) => themeColors.foreground200, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: BorderSide( + color: themeColors.grayGlass010, + width: 1.0, + ), + borderRadius: radiuses.isSquare() + ? BorderRadius.all(Radius.zero) + : BorderRadius.circular(16.0), + ); + }, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/simple_icon_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/simple_icon_button.dart new file mode 100644 index 0000000..7caa407 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/simple_icon_button.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; + +class SimpleIconButton extends StatelessWidget { + const SimpleIconButton({ + super.key, + required this.onTap, + required this.title, + this.fontSize, + this.leftIcon, + this.iconSize, + this.rightIcon, + this.backgroundColor, + this.foregroundColor, + this.size = BaseButtonSize.regular, + this.overlayColor, + this.withBorder = true, + }); + final VoidCallback? onTap; + final String title; + final double? fontSize; + final String? leftIcon, rightIcon; + final double? iconSize; + final Color? backgroundColor, foregroundColor; + final BaseButtonSize size; + final MaterialStateProperty? overlayColor; + final bool withBorder; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final textStyles = AppKitModalTheme.getDataOf(context).textStyles; + final radiuses = AppKitModalTheme.radiusesOf(context); + final borderRadius = + radiuses.isSquare() ? 0.0 : (BaseButtonSize.regular.height / 2); + return BaseButton( + onTap: onTap, + size: size, + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + backgroundColor ?? themeColors.accent100, + ), + foregroundColor: MaterialStateProperty.all( + foregroundColor ?? themeColors.inverse100, + ), + overlayColor: overlayColor, + shape: withBorder + ? MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: BorderSide( + color: themeColors.grayGlass010, + width: 1.0, + ), + borderRadius: BorderRadius.circular(borderRadius), + ); + }, + ) + : null, + padding: + MaterialStateProperty.all(EdgeInsets.all(0.0)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (leftIcon != null) + Row( + children: [ + SvgPicture.asset( + leftIcon!, + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + foregroundColor ?? themeColors.inverse100, + BlendMode.srcIn, + ), + width: iconSize ?? 14.0, + height: iconSize ?? 14.0, + ), + const SizedBox.square(dimension: 4.0), + ], + ), + Text( + title, + style: textStyles.paragraph600.copyWith( + color: foregroundColor, + fontSize: fontSize, + ), + ), + if (rightIcon != null) + Row( + children: [ + const SizedBox.square(dimension: 4.0), + SvgPicture.asset( + rightIcon!, + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + foregroundColor ?? themeColors.inverse100, + BlendMode.srcIn, + ), + width: iconSize ?? 14.0, + height: iconSize ?? 14.0, + ), + ], + ), + ], + ), + overridePadding: MaterialStateProperty.all( + size == BaseButtonSize.regular + ? EdgeInsets.only( + left: (leftIcon != null) ? 12.0 : 16.0, + right: (rightIcon != null) ? 12.0 : 16.0, + ) + : EdgeInsets.only( + left: (leftIcon != null) ? 10.0 : 12.0, + right: (rightIcon != null) ? 10.0 : 12.0, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/circular_loader.dart b/packages/reown_appkit/lib/modal/widgets/circular_loader.dart new file mode 100644 index 0000000..f2a50a5 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/circular_loader.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class CircularLoader extends StatelessWidget { + final double? size; + final double? strokeWidth; + final EdgeInsetsGeometry? padding; + const CircularLoader({ + super.key, + this.size, + this.strokeWidth, + this.padding, + }); + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + return Container( + padding: padding ?? const EdgeInsets.all(0.0), + width: size, + height: size, + child: CircularProgressIndicator( + color: themeColors.accent100, + strokeWidth: strokeWidth ?? 4.0, + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/help/help_section.dart b/packages/reown_appkit/lib/modal/widgets/help/help_section.dart new file mode 100644 index 0000000..51fd913 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/help/help_section.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +class HelpSection extends StatelessWidget { + const HelpSection({ + super.key, + required this.title, + required this.description, + required this.images, + }); + final String title, description; + final List images; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + return Container( + padding: const EdgeInsets.all(kPadding12), + child: Column( + children: [ + const SizedBox.square(dimension: 8.0), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: images + .map( + (path) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: SvgPicture.asset( + path, + package: 'reown_appkit', + ), + ), + ) + .toList(), + ), + const SizedBox.square(dimension: kPadding12), + Text( + title, + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: 8.0), + Text( + description, + textAlign: TextAlign.center, + style: themeData.textStyles.small500.copyWith( + color: themeColors.foreground200, + ), + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/icons/rounded_icon.dart b/packages/reown_appkit/lib/modal/widgets/icons/rounded_icon.dart new file mode 100644 index 0000000..8158661 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/icons/rounded_icon.dart @@ -0,0 +1,76 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/utils/core_utils.dart'; + +class RoundedIcon extends StatelessWidget { + const RoundedIcon({ + super.key, + this.assetPath, + this.imageUrl, + this.assetColor, + this.circleColor, + this.borderColor, + this.size = 34.0, + this.padding = 8.0, + this.borderRadius, + }); + final String? assetPath, imageUrl; + final Color? assetColor, circleColor, borderColor; + final double size, padding; + final double? borderRadius; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final projectId = explorerService.instance.projectId; + final radius = borderRadius ?? size; + return Container( + width: size, + height: size, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(radius)), + border: Border.fromBorderSide( + BorderSide( + color: borderColor ?? themeColors.grayGlass005, + width: 2, + strokeAlign: BorderSide.strokeAlignOutside, + ), + ), + color: circleColor ?? themeColors.grayGlass010, + ), + clipBehavior: Clip.antiAlias, + child: (imageUrl ?? '').isNotEmpty + ? ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(radius)), + child: CachedNetworkImage( + imageUrl: imageUrl!, + width: size, + height: size, + fit: BoxFit.fill, + fadeInDuration: const Duration(milliseconds: 500), + fadeOutDuration: const Duration(milliseconds: 500), + httpHeaders: CoreUtils.getAPIHeaders(projectId), + errorWidget: (context, url, error) => ColoredBox( + color: themeColors.grayGlass005, + ), + ), + ) + : Padding( + padding: EdgeInsets.all(padding), + child: SvgPicture.asset( + colorFilter: ColorFilter.mode( + assetColor ?? themeColors.foreground200, + BlendMode.srcIn, + ), + assetPath ?? 'lib/modal/assets/icons/coin.svg', + package: 'reown_appkit', + width: size, + height: size, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/icons/themed_icon.dart b/packages/reown_appkit/lib/modal/widgets/icons/themed_icon.dart new file mode 100644 index 0000000..79bd42c --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/icons/themed_icon.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class ThemedIcon extends StatelessWidget { + const ThemedIcon({ + super.key, + required this.iconPath, + this.size, + }); + final String iconPath; + final double? size; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return Container( + width: size, + height: size, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(radiuses.radius2XS), + border: Border.all( + color: themeColors.accenGlass010, + width: 1.0, + strokeAlign: BorderSide.strokeAlignInside, + ), + color: themeColors.accenGlass010, + ), + clipBehavior: Clip.antiAlias, + padding: const EdgeInsets.all(kPadding8), + child: SvgPicture.asset( + iconPath, + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + themeColors.accent100, + BlendMode.srcIn, + ), + ), + ); + } +} + +class ThemedButton extends StatelessWidget { + const ThemedButton({ + super.key, + required this.onPressed, + required this.iconPath, + this.size, + }); + final Function()? onPressed; + final String iconPath; + final double? size; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return TextButton( + onPressed: onPressed, + clipBehavior: Clip.antiAlias, + style: ButtonStyle( + minimumSize: MaterialStateProperty.all( + const Size(kSearchFieldHeight, kSearchFieldHeight), + ), + maximumSize: MaterialStateProperty.all( + const Size(kSearchFieldHeight, kSearchFieldHeight), + ), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + iconSize: MaterialStateProperty.all(0.0), + elevation: MaterialStateProperty.all(0.0), + overlayColor: MaterialStateProperty.all( + themeColors.accenGlass010, + ), + padding: MaterialStateProperty.all( + const EdgeInsets.all(0.0), + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiuses.radius2XS), + ); + }, + ), + ), + child: ThemedIcon( + size: size, + iconPath: iconPath, + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/grid_items/base_grid_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/grid_items/base_grid_item.dart new file mode 100644 index 0000000..e395f0b --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/grid_items/base_grid_item.dart @@ -0,0 +1,51 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +class BaseGridItem extends StatelessWidget { + const BaseGridItem({ + super.key, + this.onTap, + this.isSelected = false, + required this.child, + }); + final VoidCallback? onTap; + final bool isSelected; + final Widget child; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + final maxRadius = min(radiuses.radiusXS, 32.0); + return FilledButton( + onPressed: onTap, + clipBehavior: Clip.antiAlias, + style: ButtonStyle( + fixedSize: MaterialStateProperty.all( + const Size(kGridItemWidth, kGridItemHeight), + ), + backgroundColor: MaterialStateProperty.all( + isSelected ? themeColors.accenGlass020 : themeColors.grayGlass002, + ), + overlayColor: MaterialStateProperty.all( + isSelected ? themeColors.accenGlass020 : themeColors.grayGlass005, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(maxRadius), + ), + ), + padding: MaterialStateProperty.all( + const EdgeInsets.all(0.0), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: child, + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/grid_items/wallet_grid_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/grid_items/wallet_grid_item.dart new file mode 100644 index 0000000..99d8627 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/grid_items/wallet_grid_item.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; + +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/avatars/wallet_avatar.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/lists/grid_items/base_grid_item.dart'; + +class WalletGridItem extends StatelessWidget { + const WalletGridItem({ + super.key, + required this.title, + this.imageUrl, + this.bottom, + this.onTap, + this.isSelected = false, + this.isNetwork = false, + this.showCheckmark = false, + }); + + final String title; + final String? imageUrl; + final Widget? bottom; + final VoidCallback? onTap; + final bool isSelected, isNetwork, showCheckmark; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return BaseGridItem( + onTap: onTap, + isSelected: isSelected, + child: Column( + children: [ + Expanded( + child: Align( + alignment: Alignment.topCenter, + child: Stack( + children: [ + ListAvatar( + borderRadius: radiuses.radiusXS, + imageUrl: imageUrl, + isNetwork: isNetwork, + color: isSelected ? themeColors.accent100 : null, + disabled: isNetwork && onTap == null, + ), + Visibility( + visible: showCheckmark, + child: Positioned( + bottom: 0, + right: 0, + child: Container( + decoration: BoxDecoration( + color: themeColors.background150, + borderRadius: BorderRadius.all(Radius.circular(30.0)), + ), + padding: const EdgeInsets.all(1.0), + clipBehavior: Clip.antiAlias, + child: RoundedIcon( + assetPath: 'lib/modal/assets/icons/checkmark.svg', + assetColor: themeColors.success100, + circleColor: themeColors.success100.withOpacity(0.3), + borderColor: themeColors.background150, + padding: 2.0, + size: 18.0, + ), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 2.0), + Padding( + padding: const EdgeInsets.only( + top: kPadding6, + left: kPadding8, + right: kPadding8, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + title, + textAlign: TextAlign.center, + maxLines: 1, + overflow: radiuses.isCircular() + ? TextOverflow.fade + : TextOverflow.ellipsis, + softWrap: !radiuses.isCircular(), + style: themeData.textStyles.tiny500.copyWith( + color: isSelected + ? themeColors.accent100 + : themeColors.foreground100, + height: 1.0, + ), + ), + bottom ?? const SizedBox.shrink(), + ], + ), + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/account_list_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/account_list_item.dart new file mode 100644 index 0000000..0d63873 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/account_list_item.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/base_list_item.dart'; + +class AccountListItem extends StatelessWidget { + const AccountListItem({ + super.key, + required this.title, + this.titleStyle, + this.subtitle, + this.subtitleStyle, + this.iconWidget, + this.iconPath, + this.trailing, + this.onTap, + this.iconColor, + this.iconBGColor, + this.iconBorderColor, + this.hightlighted = false, + this.flexible = false, + this.padding, + }); + final Widget? iconWidget; + final String? iconPath; + final String title; + final TextStyle? titleStyle; + final String? subtitle; + final TextStyle? subtitleStyle; + final Widget? trailing; + final VoidCallback? onTap; + final Color? iconColor, iconBGColor, iconBorderColor; + final bool hightlighted; + final bool flexible; + final EdgeInsets? padding; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return BaseListItem( + onTap: onTap, + hightlighted: hightlighted, + padding: padding, + flexible: flexible, + child: Row( + children: [ + iconWidget ?? const SizedBox.shrink(), + if (iconPath != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: RoundedIcon( + assetPath: iconPath!, + assetColor: iconColor, + circleColor: iconBGColor, + borderColor: iconBorderColor, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + overflow: TextOverflow.ellipsis, + style: titleStyle ?? + themeData.textStyles.paragraph600.copyWith( + color: themeColors.foreground100, + ), + ), + if ((subtitle ?? '').isNotEmpty) + Text( + subtitle!, + overflow: TextOverflow.ellipsis, + style: subtitleStyle ?? + themeData.textStyles.small400.copyWith( + color: themeColors.foreground150, + ), + ), + ], + ), + ), + ), + trailing ?? + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: SvgPicture.asset( + 'lib/modal/assets/icons/chevron_right.svg', + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + themeColors.foreground200, + BlendMode.srcIn, + ), + width: 18.0, + height: 18.0, + ), + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/all_wallets_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/all_wallets_item.dart new file mode 100644 index 0000000..33adb6e --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/all_wallets_item.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/icons/themed_icon.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/base_list_item.dart'; + +class AllWalletsItem extends StatelessWidget { + const AllWalletsItem({ + super.key, + this.title = 'All wallets', + this.trailing, + this.onTap, + }); + final String title; + final Widget? trailing; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + return BaseListItem( + onTap: onTap, + child: Row( + children: [ + LayoutBuilder( + builder: (_, constraints) { + return ThemedIcon( + iconPath: 'lib/modal/assets/icons/dots.svg', + size: constraints.maxHeight, + ); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + title, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + ), + ), + trailing ?? const SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/base_list_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/base_list_item.dart new file mode 100644 index 0000000..67670ed --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/base_list_item.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +class BaseListItem extends StatelessWidget { + const BaseListItem({ + super.key, + required this.child, + this.trailing, + this.onTap, + this.padding, + this.hightlighted = false, + this.flexible = false, + }); + final Widget? trailing; + final VoidCallback? onTap; + final Widget child; + final EdgeInsets? padding; + final bool hightlighted; + final bool flexible; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return FilledButton( + onPressed: onTap, + style: ButtonStyle( + minimumSize: flexible + ? MaterialStateProperty.all( + const Size(1000.0, kListItemHeight), + ) + : null, + fixedSize: !flexible + ? MaterialStateProperty.all( + const Size(1000.0, kListItemHeight), + ) + : null, + backgroundColor: MaterialStateProperty.all( + hightlighted ? themeColors.accenGlass015 : themeColors.grayGlass002, + ), + overlayColor: MaterialStateProperty.all( + themeColors.grayGlass005, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiuses.radiusXS), + ), + ), + padding: MaterialStateProperty.all( + const EdgeInsets.all(0.0), + ), + ), + child: Padding( + padding: padding ?? const EdgeInsets.all(8.0), + child: child, + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/download_wallet_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/download_wallet_item.dart new file mode 100644 index 0000000..08044c1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/download_wallet_item.dart @@ -0,0 +1,65 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/wallet_list_item.dart'; + +class DownloadWalletItem extends StatelessWidget { + const DownloadWalletItem({ + super.key, + required this.walletInfo, + this.webOnly = false, + }); + final AppKitModalWalletInfo walletInfo; + final bool webOnly; + + String get _storeUrl { + if (webOnly) { + return walletInfo.listing.homepage; + } + if (Platform.isIOS) { + return walletInfo.listing.appStore ?? ''; + } + if (Platform.isAndroid) { + return walletInfo.listing.playStore ?? ''; + } + return ''; + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + if (_storeUrl.isEmpty) { + return SizedBox.shrink(); + } + return WalletListItem( + hideAvatar: true, + title: 'Don\'t have ${walletInfo.listing.name}?', + trailing: SimpleIconButton( + onTap: () => _downloadApp(context), + title: 'Get', + rightIcon: 'lib/modal/assets/icons/chevron_right.svg', + backgroundColor: Colors.transparent, + foregroundColor: themeColors.accent100, + size: BaseButtonSize.small, + fontSize: 14.0, + iconSize: 12.0, + ), + ); + } + + void _downloadApp(BuildContext context) { + try { + launchUrlString( + _storeUrl, + mode: LaunchMode.externalApplication, + ); + } catch (e) { + ModalProvider.of(context).service.connectSelectedWallet(); + } + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_connect_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_connect_item.dart new file mode 100644 index 0000000..1b966b8 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_connect_item.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/base_list_item.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/wallet_item_chip.dart'; + +class WalletConnectItem extends StatelessWidget { + const WalletConnectItem({ + super.key, + this.onTap, + }); + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + return BaseListItem( + onTap: onTap, + child: Row( + children: [ + LayoutBuilder( + builder: (context, constraints) { + return SvgPicture.asset( + AssetUtils.getThemedAsset(context, 'logo_walletconnect.svg'), + package: 'reown_appkit', + height: constraints.maxHeight, + width: constraints.maxHeight, + ); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + 'WalletConnect', + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + ), + ), + WalletItemChip( + value: ' QR CODE ', + color: themeColors.accenGlass015, + textStyle: themeData.textStyles.micro700.copyWith( + color: themeColors.accent100, + ), + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_item_chip.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_item_chip.dart new file mode 100644 index 0000000..3a13df1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_item_chip.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +class WalletItemChip extends StatelessWidget { + const WalletItemChip({ + super.key, + required this.value, + this.color, + this.textStyle, + }); + final String value; + final Color? color; + final TextStyle? textStyle; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return Container( + decoration: BoxDecoration( + color: color ?? themeColors.grayGlass010, + borderRadius: BorderRadius.all(Radius.circular(radiuses.radius4XS)), + ), + padding: const EdgeInsets.all(5.0), + margin: const EdgeInsets.only(right: 8.0), + child: Text( + value, + style: textStyle ?? + themeData.textStyles.micro700.copyWith( + color: themeColors.foreground150, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_list_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_list_item.dart new file mode 100644 index 0000000..71d9248 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_list_item.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/avatars/wallet_avatar.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/base_list_item.dart'; + +class WalletListItem extends StatelessWidget { + const WalletListItem({ + super.key, + required this.title, + this.imageUrl, + this.trailing, + this.hideAvatar = false, + this.showCheckmark = false, + this.onTap, + }); + + final String title; + final String? imageUrl; + final Widget? trailing; + final bool hideAvatar; + final bool showCheckmark; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return BaseListItem( + onTap: onTap, + padding: const EdgeInsets.all(0.0), + child: Row( + children: [ + const SizedBox.square(dimension: 8.0), + Visibility( + visible: !hideAvatar, + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 8.0, + right: 8.0, + bottom: 8.0, + ), + child: ListAvatar( + borderRadius: radiuses.radius2XS, + imageUrl: imageUrl, + ), + ), + Visibility( + visible: showCheckmark, + child: Positioned( + bottom: 6, + right: 6, + child: Container( + decoration: BoxDecoration( + color: themeColors.background150, + borderRadius: BorderRadius.all(Radius.circular(30.0)), + ), + padding: const EdgeInsets.all(1.0), + clipBehavior: Clip.antiAlias, + child: RoundedIcon( + assetPath: 'lib/modal/assets/icons/checkmark.svg', + assetColor: themeColors.success100, + circleColor: themeColors.success100.withOpacity(0.3), + borderColor: themeColors.background150, + padding: 2.0, + size: 15.0, + ), + ), + ), + ), + ], + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 8.0, + bottom: 8.0, + left: 4.0, + right: 8.0, + ), + child: Text( + title, + style: themeData.textStyles.paragraph500.copyWith( + color: onTap == null + ? themeColors.foreground200 + : themeColors.foreground100, + ), + ), + ), + ), + trailing ?? const SizedBox.shrink(), + const SizedBox.square(dimension: 8.0), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_list_item_simple.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_list_item_simple.dart new file mode 100644 index 0000000..5493519 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_list_item_simple.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/base_list_item.dart'; + +class WalletListItemSimple extends StatelessWidget { + const WalletListItemSimple({ + super.key, + required this.title, + required this.icon, + this.onTap, + }); + final String title; + final String icon; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + return BaseListItem( + onTap: onTap, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + icon, + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + themeColors.foreground200, + BlendMode.srcIn, + ), + ), + const SizedBox.square(dimension: 8.0), + Text( + title, + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph600.copyWith( + color: themeColors.foreground200, + ), + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/networks_grid.dart b/packages/reown_appkit/lib/modal/widgets/lists/networks_grid.dart new file mode 100644 index 0000000..749f1ae --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/networks_grid.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; + +import 'package:reown_appkit/modal/models/grid_item.dart'; +import 'package:reown_appkit/modal/models/public/appkit_network_info.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/lists/grid_items/wallet_grid_item.dart'; + +class NetworksGrid extends StatelessWidget { + const NetworksGrid({ + super.key, + required this.itemList, + this.onTapNetwork, + }); + final List> itemList; + final Function(AppKitModalNetworkInfo)? onTapNetwork; + + @override + Widget build(BuildContext context) { + final service = ModalProvider.of(context).service; + final itemSize = ResponsiveData.gridItemSzieOf(context); + // final themeData = AppKitModalTheme.getDataOf(context); + // final themeColors = AppKitModalTheme.colorsOf(context); + final children = itemList + .map( + (info) => WalletGridItem( + onTap: info.disabled ? null : () => onTapNetwork?.call(info.data), + isSelected: service.selectedChain?.chainId == info.id, + imageUrl: info.image, + title: info.title, + isNetwork: true, + // info.data.isTestNetwork + // bottom: info.data.isTestNetwork + // ? Text( + // 'Test', + // style: themeData.textStyles.tiny400.copyWith( + // color: themeColors.accent100, + // height: 1.0, + // ), + // ) + // : null, + ), + ) + .toList(); + return GridView.builder( + padding: EdgeInsets.only( + bottom: kPadding12 + ResponsiveData.paddingBottomOf(context), + left: kPadding6, + right: kPadding6, + top: ResponsiveData.isPortrait(context) ? kPadding12 : kPadding6, + ), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: ResponsiveData.gridAxisCountOf(context), + mainAxisSpacing: kPadding12, + crossAxisSpacing: 0.0, + mainAxisExtent: itemSize.height, + ), + itemBuilder: (_, index) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: itemSize.width, + height: itemSize.height, + child: children[index], + ), + ], + ); + }, + itemCount: children.length, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/wallets_grid.dart b/packages/reown_appkit/lib/modal/widgets/lists/wallets_grid.dart new file mode 100644 index 0000000..17278d6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/wallets_grid.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:shimmer/shimmer.dart'; + +import 'package:reown_appkit/modal/models/grid_item.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/lists/grid_items/wallet_grid_item.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; + +class WalletsGrid extends StatelessWidget { + const WalletsGrid({ + super.key, + required this.itemList, + this.onTapWallet, + this.showLoading = false, + this.loadingCount = 8, + this.scrollController, + this.paddingTop = 0.0, + }); + final List> itemList; + final Function(AppKitModalWalletInfo walletInfo)? onTapWallet; + final bool showLoading; + final int loadingCount; + final ScrollController? scrollController; + final double paddingTop; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final List children = itemList + .map( + (info) => SizedBox( + child: WalletGridItem( + onTap: () => onTapWallet?.call(info.data), + imageUrl: info.image, + title: info.title, + showCheckmark: info.data.installed, + ), + ), + ) + .toList(); + + if (showLoading) { + final offset = itemList.length % loadingCount; + final count = offset == 0 ? 0 : loadingCount - offset; + for (var i = 0; i < (loadingCount + count); i++) { + children.add( + SizedBox( + child: Shimmer.fromColors( + baseColor: themeColors.grayGlass100, + highlightColor: themeColors.grayGlass025, + child: const WalletGridItem(title: ''), + ), + ), + ); + } + } + + final itemSize = ResponsiveData.gridItemSzieOf(context); + return GridView.builder( + controller: scrollController, + padding: EdgeInsets.only( + bottom: kPadding12 + ResponsiveData.paddingBottomOf(context), + left: kPadding6, + right: kPadding6, + top: paddingTop, + ), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: ResponsiveData.gridAxisCountOf(context), + mainAxisSpacing: kPadding12, + crossAxisSpacing: 0.0, + mainAxisExtent: itemSize.height, + ), + itemBuilder: (_, index) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: itemSize.width, + height: itemSize.height, + child: children[index], + ), + ], + ); + }, + itemCount: children.length, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart b/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart new file mode 100644 index 0000000..fe641b6 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:shimmer/shimmer.dart'; + +import 'package:reown_appkit/modal/models/grid_item.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/wallet_item_chip.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/wallet_list_item.dart'; + +class WalletsList extends StatelessWidget { + const WalletsList({ + super.key, + required this.itemList, + this.firstItem, + this.bottomItems = const [], + this.onTapWallet, + this.isLoading = false, + }); + final List> itemList; + final Widget? firstItem; + final List bottomItems; + final Function(AppKitModalWalletInfo walletInfo)? onTapWallet; + final bool isLoading; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final loadingList = [ + const WalletListItem(title: ''), + const WalletListItem(title: ''), + const WalletListItem(title: ''), + const WalletListItem(title: ''), + const WalletListItem(title: ''), + ].map( + (e) => Shimmer.fromColors( + baseColor: themeColors.grayGlass100, + highlightColor: themeColors.grayGlass025, + child: e, + ), + ); + + final walletsListItems = isLoading + ? loadingList + : itemList.map( + (e) => WalletListItem( + onTap: () => onTapWallet?.call(e.data), + showCheckmark: e.data.installed, + imageUrl: e.image, + title: e.title, + trailing: e.data.recent + ? const WalletItemChip(value: ' RECENT ') + : null, + ), + ); + final List items = List.from(walletsListItems); + if (firstItem != null) { + items.insert(0, firstItem!); + } + if (bottomItems.isNotEmpty) { + items.addAll(bottomItems); + } + + return ListView.separated( + padding: const EdgeInsets.symmetric( + horizontal: kPadding12, + vertical: kPadding12, + ), + itemBuilder: (context, index) { + return SizedBox( + width: 1000.0, + child: items[index], + ); + }, + separatorBuilder: (_, index) => SizedBox.square( + dimension: index == 0 ? 0.0 : kListViewSeparatorHeight, + ), + itemCount: items.length, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/all_wallets_header.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/all_wallets_header.dart new file mode 100644 index 0000000..abeb6f3 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/all_wallets_header.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/pages/public/appkit_modal_qrcode_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/icons/themed_icon.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/searchbar.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; + +class AllWalletsHeader extends StatelessWidget { + const AllWalletsHeader({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + padding: const EdgeInsets.all(kPadding8), + child: Row( + children: [ + Expanded( + child: ModalSearchBar( + hint: 'Search wallet', + onTextChanged: (value) { + explorerService.instance.search(query: value); + }, + onDismissKeyboard: (clear) { + FocusManager.instance.primaryFocus?.unfocus(); + if (clear) { + explorerService.instance.search(query: null); + } + }, + ), + ), + const SizedBox.square(dimension: kPadding8), + ThemedButton( + size: kSearchFieldHeight, + iconPath: 'lib/modal/assets/icons/code.svg', + onPressed: () { + widgetStack.instance.push( + const AppKitModalQRCodePage(), + event: SelectWalletEvent( + name: 'WalletConnect', + platform: AnalyticsPlatform.qrcode, + ), + ); + }, + ), + const SizedBox.square(dimension: 4.0), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/content_loading.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/content_loading.dart new file mode 100644 index 0000000..95cb19c --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/content_loading.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/widgets/circular_loader.dart'; + +class ContentLoading extends StatelessWidget { + const ContentLoading({super.key, this.viewHeight}); + final double? viewHeight; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(8.0), + height: viewHeight ?? 300.0, + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CircularLoader(), + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart new file mode 100644 index 0000000..ec2e8ed --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/utils/core_utils.dart'; +import 'package:reown_appkit/modal/widgets/circular_loader.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/searchbar.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class InputEmailWidget extends StatefulWidget { + final Function(String value) onSubmitted; + final String? initialValue; + final Function(String value)? onValueChange; + final Widget? suffixIcon; + final Function(bool value)? onFocus; + const InputEmailWidget({ + super.key, + required this.onSubmitted, + this.initialValue, + this.onValueChange, + this.suffixIcon, + this.onFocus, + }); + + @override + State createState() => _InputEmailWidgetState(); +} + +class _InputEmailWidgetState extends State { + bool hasFocus = false; + late TextEditingController _controller; + bool _ready = false; + bool _timedOut = false; + bool _submitted = false; + // + @override + void initState() { + super.initState(); + _controller = TextEditingController(text: widget.initialValue); + _ready = magicService.instance.isReady.value; + _timedOut = magicService.instance.isTimeout.value; + magicService.instance.isReady.addListener(_updateStatus); + magicService.instance.isTimeout.addListener(_updateStatus); + } + + void _updateStatus() { + setState(() { + _ready = magicService.instance.isReady.value; + _timedOut = magicService.instance.isTimeout.value; + }); + } + + @override + void didUpdateWidget(covariant InputEmailWidget oldWidget) { + _updateStatus(); + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + magicService.instance.isTimeout.addListener(_updateStatus); + magicService.instance.isReady.removeListener(_updateStatus); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + return ModalSearchBar( + enabled: !_timedOut && _ready && !_submitted, + controller: _controller, + initialValue: _controller.text, + hint: 'Email', + iconPath: 'lib/modal/assets/icons/mail.svg', + textInputType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + onSubmitted: _validate, + debounce: false, + onTextChanged: (value) { + widget.onValueChange?.call(value); + }, + onFocusChange: _onFocusChange, + suffixIcon: widget.suffixIcon ?? + (!magicService.instance.isReady.value || _submitted + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircularLoader(size: 20.0, strokeWidth: 2.0), + ], + ) + : ValueListenableBuilder( + valueListenable: magicService.instance.email, + builder: (context, value, _) { + if (!hasFocus || _invalidEmail(value)) { + return SizedBox.shrink(); + } + return GestureDetector( + onTap: () => _validate(value), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: SvgPicture.asset( + 'lib/modal/assets/icons/chevron_right.svg', + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + themeColors.foreground300, + BlendMode.srcIn, + ), + ), + ), + ); + }, + )), + ); + } + + void _onFocusChange(bool focus) { + if (hasFocus == focus) return; + widget.onFocus?.call(focus); + setState(() => hasFocus = focus); + } + + bool _invalidEmail(String value) { + return value.isEmpty || !CoreUtils.isValidEmail(value); + } + + void _validate(String value) { + if (_invalidEmail(value)) { + if (value.isEmpty) { + _clearEmail(); + } + return; + } + widget.onSubmitted(value); + setState(() => _submitted = true); + } + + void _clearEmail() { + _controller.clear(); + magicService.instance.setEmail(''); + magicService.instance.setNewEmail(''); + FocusManager.instance.primaryFocus?.unfocus(); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/responsive_container.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/responsive_container.dart new file mode 100644 index 0000000..93879cb --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/responsive_container.dart @@ -0,0 +1,126 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; + +class ResponsiveContainer extends StatelessWidget { + const ResponsiveContainer({ + super.key, + required this.child, + }); + final Widget child; + + @override + Widget build(BuildContext context) { + return OrientationBuilder( + builder: (context, orientation) { + return LayoutBuilder( + builder: (context, constraints) { + final screenHeight = constraints.maxHeight; + final screenWidth = constraints.maxWidth; + final isPortrait = orientation == Orientation.portrait; + final realMaxHeight = + isPortrait ? screenHeight * 0.75 : screenHeight; + final realMinHeight = isPortrait ? 0.0 : realMaxHeight; + + final data = MediaQueryData.fromView(View.of(context)); + final isTabletSize = data.size.shortestSide < 600 ? false : true; + final maxWidth = isTabletSize ? 360.0 : data.size.shortestSide; + final maxHeight = isTabletSize ? 600.0 : realMaxHeight; + + return ResponsiveData( + maxHeight: maxHeight, + minHeight: realMinHeight, + maxWidth: maxWidth, + minWidth: screenWidth, + orientation: orientation, + child: child, + ); + }, + ); + }, + ); + } +} + +class ResponsiveData extends InheritedWidget { + const ResponsiveData({ + super.key, + required this.maxHeight, + required this.minHeight, + required this.maxWidth, + required this.minWidth, + required this.orientation, + required super.child, + }); + final double maxHeight, minHeight, maxWidth, minWidth; + final Orientation orientation; + + static ResponsiveData? maybeOf(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } + + static ResponsiveData of(BuildContext context) { + final ResponsiveData? result = maybeOf(context); + assert(result != null, 'No ResponsiveContainer found in context'); + return result!; + } + + static double maxHeightOf(BuildContext context) { + final container = maybeOf(context); + return min( + (container?.maxHeight ?? MediaQuery.of(context).size.height), + 900, + ); + } + + static double minHeightOf(BuildContext context) { + final container = maybeOf(context); + return (container?.minHeight ?? MediaQuery.of(context).size.height); + } + + static double maxWidthOf(BuildContext context) { + final container = maybeOf(context); + return (container?.maxWidth ?? MediaQuery.of(context).size.width); + } + + static double minWidthOf(BuildContext context) { + final container = maybeOf(context); + return (container?.minWidth ?? MediaQuery.of(context).size.width); + } + + static double paddingBottomOf(BuildContext context) { + return max(MediaQuery.of(context).viewInsets.bottom, kPadding6); + } + + static bool isKeyboardShown(BuildContext context) { + return MediaQuery.of(context).viewInsets.bottom > 0; + } + + static bool isPortrait(BuildContext context) { + final container = maybeOf(context); + return container?.orientation.isPortrait ?? false; + } + + static int gridAxisCountOf(BuildContext context) { + final isPortraitMode = isPortrait(context); + final maxWidth = maxWidthOf(context); + final maxExtent = + (kGridAxisCountPW * kGridItemWidth) + (kPadding12 * 2) + kPadding16; + if (maxWidth > maxExtent) { + return isPortraitMode ? kGridAxisCountPW : kGridAxisCountLW; + } + return isPortraitMode ? kGridAxisCountP : kGridAxisCountL; + } + + static Size gridItemSzieOf(BuildContext context) { + return Size(kGridItemWidth, kGridItemHeight); + } + + @override + bool updateShouldNotify(ResponsiveData oldWidget) => true; +} + +extension on Orientation { + bool get isPortrait => this == Orientation.portrait; +} diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart new file mode 100644 index 0000000..17a27a4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart @@ -0,0 +1,406 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; +import 'package:reown_appkit/modal/utils/debouncer.dart'; + +class ModalSearchBar extends StatefulWidget { + const ModalSearchBar({ + super.key, + required this.onTextChanged, + this.controller, + this.onDismissKeyboard, + this.hint = '', + this.initialValue = '', + this.iconPath = 'lib/modal/assets/icons/search.svg', + this.prefixIcon, + this.suffixIcon, + this.textAlign, + this.textInputType, + this.textInputAction, + this.onSubmitted, + this.autofocus, + this.onFocusChange, + this.noIcons = false, + this.showCursor = true, + this.textStyle, + this.debounce = true, + this.focusNode, + this.width, + this.enabled = true, + this.inputFormatters, + }); + final Function(String) onTextChanged; + final String hint; + final Function(bool)? onDismissKeyboard; + final TextEditingController? controller; + final String? iconPath; + final String initialValue; + final Widget? prefixIcon; + final Widget? suffixIcon; + final TextAlign? textAlign; + final TextInputType? textInputType; + final TextInputAction? textInputAction; + final Function(String)? onSubmitted; + final bool? autofocus; + final Function(bool)? onFocusChange; + final bool noIcons; + final bool showCursor; + final TextStyle? textStyle; + final bool debounce; + final FocusNode? focusNode; + final double? width; + final bool enabled; + final List? inputFormatters; + + @override + State createState() => _ModalSearchBarState(); +} + +class _ModalSearchBarState extends State + with TickerProviderStateMixin { + late final TextEditingController _controller; + late final FocusNode _focusNode; + final _debouncer = Debouncer(milliseconds: 300); + + late DecorationTween _decorationTween = DecorationTween( + begin: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.transparent, + offset: Offset.zero, + blurRadius: 0.0, + spreadRadius: 1.0, + blurStyle: BlurStyle.normal, + ), + ], + ), + end: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.transparent, + offset: Offset.zero, + blurRadius: 0.0, + spreadRadius: 1.0, + blurStyle: BlurStyle.normal, + ), + ], + ), + ); + + late final AnimationController _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 150), + ); + + @override + void initState() { + super.initState(); + _controller = widget.controller ?? TextEditingController(); + _focusNode = widget.focusNode ?? FocusNode(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _setDecoration(); + _controller.text = widget.initialValue; + _controller.addListener(_updateState); + _focusNode.addListener(_updateState); + }); + } + + void _setDecoration() { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + _decorationTween = DecorationTween( + begin: BoxDecoration( + borderRadius: BorderRadius.circular(radiuses.radiusXS), + boxShadow: [ + BoxShadow( + color: Colors.transparent, + offset: Offset.zero, + blurRadius: 0.0, + spreadRadius: 1.0, + blurStyle: BlurStyle.normal, + ), + ], + ), + end: BoxDecoration( + borderRadius: BorderRadius.circular(radiuses.radiusXS), + boxShadow: [ + BoxShadow( + color: themeColors.accenGlass015, + offset: Offset.zero, + blurRadius: 0.0, + spreadRadius: 1.0, + blurStyle: BlurStyle.normal, + ), + ], + ), + ); + } + + @override + void didUpdateWidget(covariant ModalSearchBar oldWidget) { + super.didUpdateWidget(oldWidget); + _setDecoration(); + } + + bool _hasFocus = false; + void _updateState() { + if (_focusNode.hasFocus && !_hasFocus) { + _hasFocus = _focusNode.hasFocus; + _animationController.forward(); + } + if (!_focusNode.hasFocus && _hasFocus) { + _hasFocus = _focusNode.hasFocus; + _animationController.reverse(); + } + widget.onFocusChange?.call(_focusNode.hasFocus); + setState(() {}); + } + + @override + void dispose() { + _animationController.dispose(); + _controller.removeListener(_updateState); + _controller.dispose(); + _focusNode.removeListener(_updateState); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + final unfocusedBorder = OutlineInputBorder( + borderSide: BorderSide(color: themeColors.grayGlass005, width: 1.0), + borderRadius: BorderRadius.circular(radiuses.radius2XS), + ); + final focusedBorder = unfocusedBorder.copyWith( + borderSide: BorderSide(color: themeColors.accent100, width: 1.0), + ); + final disabledBorder = unfocusedBorder.copyWith( + borderSide: BorderSide(color: themeColors.background100, width: 1.0), + ); + + return DecoratedBoxTransition( + decoration: _decorationTween.animate(_animationController), + child: Container( + height: kSearchFieldHeight + 8.0, + width: widget.width, + padding: const EdgeInsets.all(4.0), + child: TextFormField( + keyboardType: widget.textInputType ?? TextInputType.text, + textInputAction: + widget.textInputAction ?? TextInputAction.unspecified, + autofocus: widget.autofocus ?? false, + onFieldSubmitted: widget.onSubmitted, + onEditingComplete: () {}, + focusNode: _focusNode, + controller: _controller, + inputFormatters: widget.inputFormatters, + onChanged: (value) { + if (!widget.debounce) { + widget.onTextChanged(value); + } else { + _debouncer.run(() => widget.onTextChanged(value)); + } + }, + enabled: widget.enabled, + readOnly: !widget.enabled, + onTapOutside: (_) => widget.onDismissKeyboard?.call(false), + textAlignVertical: TextAlignVertical.center, + textAlign: widget.textAlign ?? TextAlign.left, + style: + widget.textStyle ?? TextStyle(color: themeColors.foreground100), + cursorColor: themeColors.accent100, + enableSuggestions: false, + autocorrect: false, + showCursor: widget.showCursor, + decoration: InputDecoration( + isDense: true, + prefixIcon: widget.prefixIcon ?? + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 16.0, + height: 16.0, + margin: const EdgeInsets.only(left: kPadding12), + child: GestureDetector( + onTap: () { + _controller.clear(); + widget.onDismissKeyboard?.call(true); + }, + child: SvgPicture.asset( + widget.iconPath ?? '', + package: 'reown_appkit', + height: 10.0, + width: 10.0, + colorFilter: ColorFilter.mode( + themeColors.foreground275, + BlendMode.srcIn, + ), + ), + ), + ), + ], + ), + prefixIconConstraints: BoxConstraints( + maxHeight: kSearchFieldHeight, + minHeight: kSearchFieldHeight, + maxWidth: 36.0, + minWidth: widget.noIcons ? 0.0 : 36.0, + ), + labelStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.inverse100, + ), + hintText: widget.hint, + hintStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground275, + height: 1.5, + ), + suffixIcon: widget.suffixIcon ?? + (_controller.value.text.isNotEmpty || _focusNode.hasFocus + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + width: 18.0, + height: 18.0, + margin: const EdgeInsets.only(right: kPadding12), + child: GestureDetector( + onTap: () { + _controller.clear(); + widget.onDismissKeyboard?.call(true); + }, + child: SvgPicture.asset( + AssetUtils.getThemedAsset( + context, + 'input_cancel.svg', + ), + package: 'reown_appkit', + height: 10.0, + width: 10.0, + ), + ), + ), + ], + ) + : null), + suffixIconConstraints: BoxConstraints( + maxHeight: kSearchFieldHeight, + minHeight: kSearchFieldHeight, + maxWidth: 36.0, + minWidth: widget.noIcons ? 0.0 : 36.0, + ), + border: unfocusedBorder, + errorBorder: unfocusedBorder, + enabledBorder: unfocusedBorder, + disabledBorder: disabledBorder, + focusedBorder: focusedBorder, + filled: true, + fillColor: themeColors.grayGlass005, + contentPadding: const EdgeInsets.all(0.0), + ), + ), + ), + ); + } +} + +// ignore: unused_element +class _DecoratedInputBorder extends InputBorder { + _DecoratedInputBorder({ + required this.child, + required this.shadow, + }) : super(borderSide: child.borderSide); + + final InputBorder child; + + final BoxShadow shadow; + + @override + bool get isOutline => child.isOutline; + + @override + Path getInnerPath(Rect rect, {TextDirection? textDirection}) => + child.getInnerPath(rect, textDirection: textDirection); + + @override + Path getOuterPath(Rect rect, {TextDirection? textDirection}) => + child.getOuterPath(rect, textDirection: textDirection); + + @override + EdgeInsetsGeometry get dimensions => child.dimensions; + + @override + InputBorder copyWith({ + BorderSide? borderSide, + InputBorder? child, + BoxShadow? shadow, + bool? isOutline, + }) { + return _DecoratedInputBorder( + child: (child ?? this.child).copyWith(borderSide: borderSide), + shadow: shadow ?? this.shadow, + ); + } + + @override + ShapeBorder scale(double t) { + final scalledChild = child.scale(t); + + return _DecoratedInputBorder( + child: scalledChild is InputBorder ? scalledChild : child, + shadow: BoxShadow.lerp(null, shadow, t)!, + ); + } + + @override + void paint( + Canvas canvas, + Rect rect, { + double? gapStart, + double gapExtent = 0.0, + double gapPercentage = 0.0, + TextDirection? textDirection, + }) { + final clipPath = Path() + ..addRect(const Rect.fromLTWH(-5000, -5000, 10000, 10000)) + ..addPath(getInnerPath(rect), Offset.zero) + ..fillType = PathFillType.evenOdd; + canvas.clipPath(clipPath); + + final Paint paint = shadow.toPaint(); + final Rect bounds = rect.shift(shadow.offset).inflate(shadow.spreadRadius); + + canvas.drawPath(getOuterPath(bounds), paint); + + child.paint(canvas, rect, + gapStart: gapStart, + gapExtent: gapExtent, + gapPercentage: gapPercentage, + textDirection: textDirection); + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) return false; + return other is _DecoratedInputBorder && + other.borderSide == borderSide && + other.child == child && + other.shadow == shadow; + } + + @override + int get hashCode => Object.hash(borderSide, child, shadow); + + @override + String toString() { + return '$runtimeType($borderSide, $shadow, $child)'; + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/segmented_control.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/segmented_control.dart new file mode 100644 index 0000000..f1492fc --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/segmented_control.dart @@ -0,0 +1,116 @@ +import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +enum SegmentOption { + mobile, + browser, +} + +class SegmentedControl extends StatefulWidget { + const SegmentedControl({ + super.key, + this.onChange, + }); + final Function(SegmentOption option)? onChange; + + @override + State createState() => _SegmentedControlState(); +} + +class _SegmentedControlState extends State { + SegmentOption _selectedSegment = SegmentOption.mobile; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return SizedBox( + height: 32.0, + child: CustomSlidingSegmentedControl( + initialValue: SegmentOption.mobile, + fixedWidth: 100.0, + children: { + SegmentOption.mobile: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + 'lib/modal/assets/icons/mobile.svg', + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + _selectedSegment == SegmentOption.mobile + ? themeColors.foreground100 + : themeColors.foreground200, + BlendMode.srcIn, + ), + height: 14.0, + ), + const SizedBox.square(dimension: 4.0), + Text( + 'Mobile', + style: themeData.textStyles.small500.copyWith( + color: _selectedSegment == SegmentOption.mobile + ? themeColors.foreground100 + : themeColors.foreground200, + ), + ), + ], + ), + SegmentOption.browser: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + 'lib/modal/assets/icons/extension.svg', + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + _selectedSegment == SegmentOption.browser + ? themeColors.foreground100 + : themeColors.foreground200, + BlendMode.srcIn, + ), + height: 14.0, + ), + const SizedBox.square(dimension: 4.0), + Text( + 'Browser', + style: themeData.textStyles.small500.copyWith( + color: _selectedSegment == SegmentOption.browser + ? themeColors.foreground100 + : themeColors.foreground200, + ), + ), + ], + ), + }, + decoration: BoxDecoration( + color: themeColors.grayGlass002, + borderRadius: radiuses.isSquare() + ? BorderRadius.all(Radius.zero) + : BorderRadius.circular(16.0), + ), + thumbDecoration: BoxDecoration( + color: themeColors.grayGlass002, + borderRadius: radiuses.isSquare() + ? BorderRadius.all(Radius.zero) + : BorderRadius.circular(16.0), + border: Border.all( + color: themeColors.grayGlass002, + width: 1, + ), + ), + duration: Duration(milliseconds: 200), + curve: Curves.linear, + onValueChanged: (value) { + setState(() { + _selectedSegment = value; + }); + widget.onChange?.call(value); + }, + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/verify_otp_view.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/verify_otp_view.dart new file mode 100644 index 0000000..29e018e --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/verify_otp_view.dart @@ -0,0 +1,251 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/searchbar.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class VerifyOtpView extends StatefulWidget { + final Future Function({required String value}) resendEmail; + final Future Function({required String otp}) verifyOtp; + final String currentEmail; + + VerifyOtpView({ + super.key, + required this.resendEmail, + required this.verifyOtp, + required this.currentEmail, + }); + + @override + State createState() => _VerifyOtpViewState(); +} + +class _VerifyOtpViewState extends State + with WidgetsBindingObserver { + late DateTime _resendEnabledAt; + static const _emptyString = ' '; + final List _focusNodes = [ + FocusNode(debugLabel: 'focus0'), + FocusNode(debugLabel: 'focus1'), + FocusNode(debugLabel: 'focus2'), + FocusNode(debugLabel: 'focus3'), + FocusNode(debugLabel: 'focus4'), + FocusNode(debugLabel: 'focus5'), + ]; + + final List _controllers = [ + TextEditingController(), + TextEditingController(), + TextEditingController(), + TextEditingController(), + TextEditingController(), + TextEditingController(), + ]; + + final Map _digit = { + 0: _emptyString, + 1: _emptyString, + 2: _emptyString, + 3: _emptyString, + 4: _emptyString, + 5: _emptyString, + }; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _resendEnabledAt = DateTime.now().add(Duration(seconds: 30)); + _focusNodes.first.requestFocus(); + for (var fn in _focusNodes) { + fn.addListener(_focusListener); + } + } + + void _resendEmail() async { + final diff = DateTime.now().difference(_resendEnabledAt).inSeconds; + if (diff < 0) { + toastService.instance.show(ToastMessage( + type: ToastType.error, + text: 'Try again after ${diff.abs()} seconds', + )); + } else { + final email = widget.currentEmail; + widget.resendEmail(value: email); + _resendEnabledAt = DateTime.now().add(Duration(seconds: 30)); + toastService.instance.show(ToastMessage( + type: ToastType.success, + text: 'Code email resent', + )); + } + } + + @override + void dispose() { + for (var fn in _focusNodes) { + fn.removeListener(_focusListener); + } + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + void _focusListener() { + int? emptyIndex = _digit.entries.firstWhereOrNull((e) { + return e.value.trim().isEmpty; + })?.key; + final firstEmptyIndex = emptyIndex ?? -1; + final focusedIndex = _focusNodes.indexWhere((fn) => fn.hasFocus); + // + for (var entry in _digit.entries) { + if (entry.key >= focusedIndex) { + _digit[entry.key] = _emptyString; + _controllers[entry.key].text = _emptyString; + } + } + if (focusedIndex > firstEmptyIndex && firstEmptyIndex >= 0) { + _focusNodes[firstEmptyIndex].requestFocus(); + } + } + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final textStyles = AppKitModalTheme.getDataOf(context).textStyles; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: kPadding8, + horizontal: kPadding12, + ), + child: Column( + children: [ + const SizedBox.square(dimension: kPadding16), + Text( + 'Enter the code we sent to ', + textAlign: TextAlign.center, + style: textStyles.paragraph400.copyWith( + color: themeColors.foreground100, + ), + ), + Text( + widget.currentEmail, + textAlign: TextAlign.center, + style: textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: kPadding12), + Text( + 'The code expires in 20 minutes', + style: textStyles.small400.copyWith( + color: themeColors.foreground200, + ), + ), + const SizedBox.square(dimension: kPadding16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _focusNodes + .mapIndexed( + (index, fn) => ModalSearchBar( + width: kSearchFieldHeight + 8.0, + initialValue: _digit[index]!, + controller: _controllers[index], + focusNode: fn, + textInputType: TextInputType.number, + textAlign: TextAlign.center, + showCursor: false, + debounce: false, + inputFormatters: [ + LengthLimitingTextInputFormatter(2), + ], + textStyle: textStyles.large400.copyWith( + color: themeColors.foreground100, + ), + prefixIcon: const SizedBox.shrink(), + suffixIcon: const SizedBox.shrink(), + noIcons: true, + onTextChanged: (value) { + _onTextChanged(index, value, fn); + }, + ), + ) + .toList(), + ), + const SizedBox.square(dimension: kPadding16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Didn\'t receive it?', + style: textStyles.small400.copyWith( + color: themeColors.foreground200, + ), + ), + TextButton( + onPressed: () { + _resendEmail(); + }, + child: Text( + 'Resend code', + style: textStyles.small600.copyWith( + color: themeColors.accent100, + ), + ), + style: ButtonStyle( + overlayColor: MaterialStateProperty.all( + themeColors.accenGlass010, + ), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(horizontal: 8.0), + ), + visualDensity: VisualDensity.compact, + ), + ), + ], + ), + ], + ), + ), + ], + ); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) async { + if (state == AppLifecycleState.resumed) { + final clipboardHasData = await Clipboard.hasStrings(); + if (clipboardHasData) { + final clipboardData = await Clipboard.getData(Clipboard.kTextPlain); + await Clipboard.setData(ClipboardData(text: '')); + final code = clipboardData?.text ?? ''; + if (code.isNotEmpty) { + widget.verifyOtp(otp: code); + } + } + } + } + + void _onTextChanged(int index, String value, FocusNode node) { + _digit[index] = value.trim(); + if (_digit[index]!.isNotEmpty) { + if (index == 5) { + final code = _digit.values.join(); + widget.verifyOtp(otp: code); + } else { + node.nextFocus(); + } + } else { + if (_digit[index]!.isEmpty) { + _digit[index] = _emptyString; + } + if (index > 0) { + node.previousFocus(); + } + } + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/modal_container.dart b/packages/reown_appkit/lib/modal/widgets/modal_container.dart new file mode 100644 index 0000000..e4f5067 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/modal_container.dart @@ -0,0 +1,97 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/utils/platform_utils.dart'; +import 'package:reown_appkit/modal/widgets/toast/toast_presenter.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/transition_container.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; + +class ModalContainer extends StatefulWidget { + const ModalContainer({super.key, this.startWidget}); + + final Widget? startWidget; + + @override + State createState() => _ModalContainerState(); +} + +class _ModalContainerState extends State { + bool _initialized = false; + Widget? _currentScreen; + + @override + void initState() { + super.initState(); + widgetStack.instance.addListener(_widgetStackUpdated); + + if (widget.startWidget != null) { + widgetStack.instance.push(widget.startWidget!, renderScreen: true); + } else { + widgetStack.instance.addDefault(); + } + + _initialize(); + } + + @override + void dispose() { + widgetStack.instance.removeListener(_widgetStackUpdated); + super.dispose(); + } + + void _initialize() => setState(() => _initialized = true); + + void _widgetStackUpdated() { + // + try { + _currentScreen = widgetStack.instance.getCurrent(); + } catch (_) {} + setState(() {}); + } + + bool get _isLoading => !_initialized || _currentScreen == null; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + final bottomSheet = PlatformUtils.isBottomSheet(); + final isTabletSize = PlatformUtils.isTablet(context); + final maxRadius = min(radiuses.radiusM, 36.0); + final innerContainerBorderRadius = bottomSheet && !isTabletSize + ? BorderRadius.only( + topLeft: Radius.circular(maxRadius), + topRight: Radius.circular(maxRadius), + ) + : BorderRadius.all( + Radius.circular(maxRadius), + ); + + return ResponsiveContainer( + child: ClipRRect( + borderRadius: innerContainerBorderRadius, + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all( + color: themeColors.grayGlass005, + width: 1, + ), + color: themeColors.background125, + ), + child: Stack( + children: [ + TransitionContainer( + child: _isLoading ? const ContentLoading() : _currentScreen!, + ), + const ToastPresenter(), + ], + ), + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/modal_provider.dart b/packages/reown_appkit/lib/modal/widgets/modal_provider.dart new file mode 100644 index 0000000..a9b01b9 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/modal_provider.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; + +class ModalProvider extends InheritedWidget { + final IAppKitModal service; + + const ModalProvider({ + super.key, + required this.service, + required super.child, + }); + + static ModalProvider? maybeOf(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } + + static ModalProvider of(BuildContext context) { + final ModalProvider? result = maybeOf(context); + assert(result != null, 'No ModalProvider found in context'); + return result!; + } + + @override + bool updateShouldNotify(ModalProvider oldWidget) { + return true; + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart b/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart new file mode 100644 index 0000000..6b1a4e1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar_action_button.dart'; + +class ModalNavbar extends StatelessWidget { + const ModalNavbar({ + super.key, + this.onBack, + this.onTapTitle, + required this.body, + required this.title, + this.leftAction, + this.safeAreaLeft = false, + this.safeAreaRight = false, + this.safeAreaBottom = true, + this.noClose = false, + this.divider = true, + }); + + final VoidCallback? onBack; + final VoidCallback? onTapTitle; + final Widget body; + final String title; + final NavbarActionButton? leftAction; + final bool safeAreaLeft, safeAreaRight, safeAreaBottom, noClose, divider; + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final keyboardOpened = ResponsiveData.isKeyboardShown(context); + final paddingBottom = + keyboardOpened ? ResponsiveData.paddingBottomOf(context) : 0.0; + return Padding( + padding: EdgeInsets.only(bottom: paddingBottom), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SafeArea( + left: true, + right: true, + top: false, + bottom: false, + child: SizedBox( + height: kNavbarHeight, + child: ValueListenableBuilder( + valueListenable: widgetStack.instance.onRenderScreen, + builder: (context, render, _) { + if (!render) return SizedBox.shrink(); + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + widgetStack.instance.canPop() + ? NavbarActionButton( + asset: 'lib/modal/assets/icons/chevron_left.svg', + action: onBack ?? widgetStack.instance.pop, + ) + : (leftAction ?? + const SizedBox.square(dimension: kNavbarHeight)), + Expanded( + child: GestureDetector( + onTap: () => onTapTitle?.call(), + child: Center( + child: Text( + title, + style: themeData.textStyles.paragraph600.copyWith( + color: themeColors.foreground100, + ), + ), + ), + ), + ), + noClose + ? const SizedBox.square(dimension: kNavbarHeight) + : NavbarActionButton( + asset: 'lib/modal/assets/icons/close.svg', + action: () { + ModalProvider.of(context).service.closeModal(); + }, + ), + ], + ); + }, + ), + ), + ), + if (divider) Divider(color: themeColors.grayGlass005, height: 0.0), + Flexible( + child: SafeArea( + left: safeAreaLeft, + right: safeAreaRight, + bottom: safeAreaBottom, + child: body, + ), + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/navigation/navbar_action_button.dart b/packages/reown_appkit/lib/modal/widgets/navigation/navbar_action_button.dart new file mode 100644 index 0000000..741bbd5 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/navigation/navbar_action_button.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; + +class NavbarActionButton extends StatelessWidget { + const NavbarActionButton({ + super.key, + required this.asset, + required this.action, + this.color, + }); + final String asset; + final VoidCallback action; + final Color? color; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + return SizedBox.square( + dimension: kNavbarHeight, + child: IconButton( + onPressed: action, + icon: SvgPicture.asset( + asset, + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + color ?? themeColors.foreground100, + BlendMode.srcIn, + ), + width: 18.0, + height: 18.0, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_account_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_account_button.dart new file mode 100644 index 0000000..b3b6017 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_account_button.dart @@ -0,0 +1,348 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/pages/approve_magic_request_page.dart'; +import 'package:reown_appkit/modal/pages/confirm_email_page.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/utils/render_utils.dart'; +import 'package:reown_appkit/modal/widgets/avatars/account_avatar.dart'; +import 'package:reown_appkit/modal/widgets/buttons/balance_button.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/circular_loader.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AppKitModalAccountButton extends StatefulWidget { + const AppKitModalAccountButton({ + super.key, + required this.service, + this.size = BaseButtonSize.regular, + this.avatar, + this.context, + this.custom, + }); + + final IAppKitModal service; + final BaseButtonSize size; + final String? avatar; + final BuildContext? context; + final Widget? custom; + + @override + State createState() => + _AppKitModalAccountButtonState(); +} + +class _AppKitModalAccountButtonState extends State { + String _balance = BalanceButton.balanceDefault; + String _address = ''; + String? _tokenImage; + String? _tokenName; + + @override + void initState() { + super.initState(); + _modalNotifyListener(); + widget.service.addListener(_modalNotifyListener); + // TODO [AppKitModalAccountButton] this should go in AppKitModal but for that, init() method of AppKitModal should receive a BuildContext, which would be a breaking change + magicService.instance.onMagicRpcRequest.subscribe(_approveSign); + magicService.instance.onMagicLoginRequest.subscribe(_loginRequested); + } + + @override + void dispose() { + widget.service.removeListener(_modalNotifyListener); + magicService.instance.onMagicRpcRequest.unsubscribe(_approveSign); + magicService.instance.onMagicLoginRequest.unsubscribe(_loginRequested); + super.dispose(); + } + + void _modalNotifyListener() { + setState(() { + _address = widget.service.session?.address ?? ''; + final chainId = widget.service.selectedChain?.chainId ?? ''; + final imageId = AppKitModalNetworks.getNetworkIconId(chainId); + _tokenImage = explorerService.instance.getAssetImageUrl(imageId); + _balance = widget.service.chainBalance; + _tokenName = widget.service.selectedChain?.currency; + }); + } + + void _onTap() { + widget.service.openModalView(); + } + + void _approveSign(MagicRequestEvent? args) async { + if (args?.request != null) { + if (widget.service.isOpen) { + widgetStack.instance.push(ApproveTransactionPage()); + } else { + widget.service.openModalView(ApproveTransactionPage()); + } + } + } + + void _loginRequested(MagicSessionEvent? args) { + if (widget.service.isOpen) { + widgetStack.instance.popAllAndPush(ConfirmEmailPage()); + } else { + widget.service.openModalView(ConfirmEmailPage()); + } + } + + @override + Widget build(BuildContext context) { + if (widget.custom != null) { + return widget.custom!; + } + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + final borderRadius = radiuses.isSquare() ? 0.0 : widget.size.height / 2; + final enabled = _address.isNotEmpty && widget.service.status.isInitialized; + // TODO [AppKitModalAccountButton] this button should be able to be disable by passing a null onTap action + // I should decouple an AccountButton from AppKitModalAccountButton like on ConnectButton and NetworkButton + return BaseButton( + size: widget.size, + onTap: enabled ? _onTap : null, + overridePadding: MaterialStateProperty.all( + const EdgeInsets.only(left: 4.0, right: 4.0), + ), + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass005; + } + return themeColors.grayGlass010; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass015; + } + return themeColors.foreground175; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: states.contains(MaterialState.disabled) + ? BorderSide(color: themeColors.grayGlass005, width: 1.0) + : BorderSide(color: themeColors.grayGlass010, width: 1.0), + borderRadius: BorderRadius.circular(borderRadius), + ); + }, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _BalanceButton( + isLoading: widget.service.status.isLoading, + balance: _balance, + tokenName: _tokenName, + tokenImage: _tokenImage, + iconSize: widget.size.iconSize, + buttonSize: widget.size, + onTap: enabled ? _onTap : null, + ), + const SizedBox.square(dimension: 4.0), + _AddressButton( + address: _address, + buttonSize: widget.size, + service: widget.service, + onTap: enabled ? _onTap : null, + ), + ], + ), + ); + } +} + +class _AddressButton extends StatelessWidget { + const _AddressButton({ + required this.buttonSize, + required this.address, + required this.service, + required this.onTap, + }); + final BaseButtonSize buttonSize; + final VoidCallback? onTap; + final String address; + final IAppKitModal service; + + @override + Widget build(BuildContext context) { + if (address.isEmpty) { + return SizedBox.shrink(); + } + final themeData = AppKitModalTheme.getDataOf(context); + final textStyle = buttonSize == BaseButtonSize.small + ? themeData.textStyles.small600 + : themeData.textStyles.paragraph600; + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + final innerBorderRadius = + radiuses.isSquare() ? 0.0 : BaseButtonSize.small.height / 2; + return Padding( + padding: EdgeInsets.only( + top: buttonSize == BaseButtonSize.small ? 4.0 : 0.0, + bottom: buttonSize == BaseButtonSize.small ? 4.0 : 0.0, + ), + child: BaseButton( + size: BaseButtonSize.small, + onTap: onTap, + overridePadding: MaterialStateProperty.all( + EdgeInsets.only( + left: buttonSize == BaseButtonSize.small ? 4.0 : 6.0, + right: 8.0, + ), + ), + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass005; + } + return themeColors.grayGlass010; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass015; + } + return themeColors.foreground175; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: states.contains(MaterialState.disabled) + ? BorderSide( + color: themeColors.grayGlass005, + width: 1.0, + ) + : BorderSide( + color: themeColors.grayGlass010, + width: 1.0, + ), + borderRadius: BorderRadius.circular(innerBorderRadius), + ); + }, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(buttonSize.iconSize), + border: Border.all( + color: themeColors.grayGlass005, + width: 1.0, + strokeAlign: BorderSide.strokeAlignInside, + ), + ), + child: AccountAvatar( + service: service, + size: buttonSize.iconSize, + disabled: false, + ), + ), + const SizedBox.square(dimension: 4.0), + Text( + RenderUtils.truncate( + address, + length: buttonSize == BaseButtonSize.small ? 2 : 4, + ), + style: textStyle, + ), + ], + ), + ), + ); + } +} + +class _BalanceButton extends StatelessWidget { + const _BalanceButton({ + required this.onTap, + required this.isLoading, + required this.balance, + required this.tokenName, + required this.tokenImage, + required this.iconSize, + required this.buttonSize, + }); + final VoidCallback? onTap; + final bool isLoading; + final String balance; + final String? tokenName; + final String? tokenImage; + final double iconSize; + final BaseButtonSize buttonSize; + + @override + Widget build(BuildContext context) { + final themeColors = AppKitModalTheme.colorsOf(context); + final themeData = AppKitModalTheme.getDataOf(context); + final textStyle = buttonSize == BaseButtonSize.small + ? themeData.textStyles.small600 + : themeData.textStyles.paragraph600; + return BaseButton( + size: BaseButtonSize.small, + onTap: onTap, + overridePadding: MaterialStateProperty.all( + const EdgeInsets.only(left: 2.0), + ), + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.transparent), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass015; + } + return themeColors.foreground100; + }, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + isLoading + ? Row( + children: [ + const SizedBox.square(dimension: kPadding6), + CircularLoader( + size: 16.0, + strokeWidth: 1.5, + ), + const SizedBox.square(dimension: kPadding6), + ], + ) + : (tokenImage ?? '').isEmpty + ? RoundedIcon( + assetPath: 'lib/modal/assets/icons/network.svg', + size: iconSize, + assetColor: themeColors.inverse100, + padding: 4.0, + ) + : RoundedIcon( + imageUrl: tokenImage, + size: iconSize + 2.0, + ), + const SizedBox.square(dimension: 4.0), + Text( + '$balance ${tokenName ?? ''}', + style: textStyle, + ), + ], + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_connect_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_connect_button.dart new file mode 100644 index 0000000..a8abf14 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_connect_button.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/modal/widgets/buttons/connect_button.dart'; + +class AppKitModalConnectButton extends StatefulWidget { + const AppKitModalConnectButton({ + super.key, + required this.service, + this.size = BaseButtonSize.regular, + this.state, + this.context, + this.custom, + }); + + final IAppKitModal service; + final BaseButtonSize size; + final ConnectButtonState? state; + final BuildContext? context; + final Widget? custom; + + @override + State createState() => + _AppKitModalConnectButtonState(); +} + +class _AppKitModalConnectButtonState extends State { + late ConnectButtonState _state; + + @override + void initState() { + super.initState(); + _state = widget.state ?? ConnectButtonState.idle; + _updateState(); + widget.service.addListener(_updateState); + } + + @override + void didUpdateWidget(covariant AppKitModalConnectButton oldWidget) { + super.didUpdateWidget(oldWidget); + _state = widget.state ?? ConnectButtonState.idle; + _updateState(); + } + + @override + void dispose() { + super.dispose(); + widget.service.removeListener(_updateState); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: AlignmentDirectional.center, + children: [ + _WebViewWidget(), + widget.custom ?? + ConnectButton( + serviceStatus: widget.service.status, + state: _state, + size: widget.size, + onTap: _onTap, + ), + ], + ); + } + + void _onTap() { + if (widget.service.isConnected) { + widget.service.disconnect(); + } else { + widget.service.openModalView(); + _updateState(); + } + } + + void _updateState() { + final isConnected = widget.service.isConnected; + if (_state == ConnectButtonState.none && !isConnected) { + return; + } + // Case 0: init error + if (widget.service.status == AppKitModalStatus.error) { + return setState(() => _state = ConnectButtonState.error); + } + // Case 1: Is connected + else if (widget.service.isConnected) { + return setState(() => _state = ConnectButtonState.connected); + } + // Case 1.5: No required namespaces + else if (!widget.service.hasNamespaces) { + return setState(() => _state = ConnectButtonState.disabled); + } + // Case 2: Is not open and is not connected + else if (!widget.service.isOpen && !widget.service.isConnected) { + return setState(() => _state = ConnectButtonState.idle); + } + // Case 3: Is open and is not connected + else if (widget.service.isOpen && !widget.service.isConnected) { + return setState(() => _state = ConnectButtonState.connecting); + } + } +} + +class _WebViewWidget extends StatefulWidget { + @override + State<_WebViewWidget> createState() => _WebViewWidgetState(); +} + +class _WebViewWidgetState extends State<_WebViewWidget> { + bool _show = true; + // + @override + void initState() { + super.initState(); + magicService.instance.onMagicRpcRequest.subscribe(_onRequest); + } + + @override + void dispose() { + magicService.instance.onMagicRpcRequest.unsubscribe(_onRequest); + super.dispose(); + } + + void _onRequest(MagicRequestEvent? args) async { + if (args != null) { + final show = args.request == null; + await Future.delayed(Duration(milliseconds: show ? 500 : 0)); + setState(() => _show = args.request == null); + } + } + + @override + Widget build(BuildContext context) { + final emailEnabled = magicService.instance.isEnabled.value; + if (emailEnabled && _show) { + return SizedBox( + width: 0.5, + height: 0.5, + child: magicService.instance.webview, + ); + } + return const SizedBox.shrink(); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_network_select_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_network_select_button.dart new file mode 100644 index 0000000..10b7b73 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_network_select_button.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/models/public/appkit_network_info.dart'; +import 'package:reown_appkit/modal/pages/public/appkit_modal_select_network_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/modal/widgets/buttons/network_button.dart'; + +class AppKitModalNetworkSelectButton extends StatefulWidget { + const AppKitModalNetworkSelectButton({ + super.key, + required this.service, + this.size = BaseButtonSize.regular, + this.context, + this.custom, + }); + + final IAppKitModal service; + final BaseButtonSize size; + final BuildContext? context; + final Widget? custom; + + @override + State createState() => + _AppKitModalNetworkSelectButtonState(); +} + +class _AppKitModalNetworkSelectButtonState + extends State { + AppKitModalNetworkInfo? _selectedChain; + + @override + void initState() { + super.initState(); + _onServiceUpdate(); + widget.service.addListener(_onServiceUpdate); + } + + @override + void dispose() { + widget.service.removeListener(_onServiceUpdate); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.custom ?? + NetworkButton( + serviceStatus: widget.service.status, + chainInfo: _selectedChain, + size: widget.size, + onTap: () => _onConnectPressed(), + ); + } + + void _onConnectPressed() { + analyticsService.instance.sendEvent(ClickNetworksEvent()); + widget.service.openModalView( + AppKitModalSelectNetworkPage( + onTapNetwork: (info) { + widget.service.selectChain(info); + widgetStack.instance.addDefault(); + }, + ), + ); + } + + void _onServiceUpdate() { + setState(() { + _selectedChain = widget.service.selectedChain; + }); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart new file mode 100644 index 0000000..317f50d --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart @@ -0,0 +1,6 @@ +export 'appkit_modal_connect_button.dart'; +export 'appkit_modal_network_select_button.dart'; +export 'appkit_modal_account_button.dart'; + +export '../buttons/base_button.dart' show BaseButtonSize; +export '../buttons/connect_button.dart' show ConnectButtonState; diff --git a/packages/reown_appkit/lib/modal/widgets/qr_code_view.dart b/packages/reown_appkit/lib/modal/widgets/qr_code_view.dart new file mode 100644 index 0000000..2fa4003 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/qr_code_view.dart @@ -0,0 +1,68 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:qr_flutter_wc/qr_flutter_wc.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; + +class QRCodeView extends StatelessWidget { + const QRCodeView({ + super.key, + required this.uri, + this.logoPath = '', + }); + + final String logoPath, uri; + + @override + Widget build(BuildContext context) { + final radiuses = AppKitModalTheme.radiusesOf(context); + final responsiveData = ResponsiveData.of(context); + final isPortrait = ResponsiveData.isPortrait(context); + final isDarkMode = AppKitModalTheme.maybeOf(context)?.isDarkMode ?? false; + final imageSize = isPortrait ? 80.0 : 60.0; + final maxRadius = min(radiuses.radiusL, 36.0); + return Container( + constraints: BoxConstraints( + maxWidth: isPortrait + ? responsiveData.maxWidth + : (responsiveData.maxHeight - kNavbarHeight - (kPadding16 * 2)), + ), + decoration: BoxDecoration( + color: isDarkMode ? Colors.white : Colors.transparent, + borderRadius: BorderRadius.circular(maxRadius), + ), + padding: const EdgeInsets.all(20.0), + child: AspectRatio( + aspectRatio: 1.0, + child: uri.isEmpty + ? const ContentLoading() + : QrImageView( + data: uri, + version: QrVersions.auto, + errorCorrectionLevel: QrErrorCorrectLevel.Q, + eyeStyle: const QrEyeStyle( + eyeShape: QrEyeShape.circle, + color: Colors.black, + ), + dataModuleStyle: const QrDataModuleStyle( + dataModuleShape: QrDataModuleShape.circle, + color: Colors.black, + ), + embeddedImage: logoPath.isNotEmpty + ? AssetImage( + logoPath, + package: 'reown_appkit', + ) + : null, + embeddedImageStyle: QrEmbeddedImageStyle( + size: Size(imageSize, imageSize), + ), + embeddedImageEmitsError: true, + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/text/appkit_address.dart b/packages/reown_appkit/lib/modal/widgets/text/appkit_address.dart new file mode 100644 index 0000000..42e4967 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/text/appkit_address.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/utils/render_utils.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class Address extends StatefulWidget { + const Address({ + super.key, + required this.service, + this.style, + }); + + final IAppKitModal service; + final TextStyle? style; + + @override + State
createState() => _AddressState(); +} + +class _AddressState extends State
{ + String? _address; + + @override + void initState() { + super.initState(); + _modalNotifyListener(); + widget.service.addListener(_modalNotifyListener); + } + + @override + void dispose() { + widget.service.removeListener(_modalNotifyListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + return Text( + RenderUtils.truncate(_address ?? ''), + style: widget.style ?? + themeData.textStyles.paragraph600.copyWith( + color: themeColors.foreground100, + ), + ); + } + + void _modalNotifyListener() { + setState(() { + _address = widget.service.session?.address; + }); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart b/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart new file mode 100644 index 0000000..c5b9aab --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/buttons/balance_button.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; + +class BalanceText extends StatefulWidget { + const BalanceText({ + super.key, + this.size = BaseButtonSize.regular, + this.onTap, + }); + + final BaseButtonSize size; + final VoidCallback? onTap; + + @override + State createState() => _BalanceTextState(); +} + +class _BalanceTextState extends State { + String _balance = BalanceButton.balanceDefault; + String? _tokenName; + IAppKitModal? _service; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _service = ModalProvider.of(context).service; + _modalNotifyListener(); + _service?.addListener(_modalNotifyListener); + }); + } + + @override + void dispose() { + _service?.removeListener(_modalNotifyListener); + super.dispose(); + } + + void _modalNotifyListener() { + if (_service == null) return; + setState(() { + _balance = _service!.chainBalance; + _tokenName = _service?.selectedChain?.currency; + }); + } + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + return Text( + '$_balance ${_tokenName ?? ''}', + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground200, + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/toast/toast.dart b/packages/reown_appkit/lib/modal/widgets/toast/toast.dart new file mode 100644 index 0000000..f82a9f1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/toast/toast.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; + +class ToastWidget extends StatefulWidget { + const ToastWidget({ + super.key, + required this.message, + }); + + final ToastMessage message; + + @override + State createState() => _ToastWidgetState(); +} + +class _ToastWidgetState extends State + with SingleTickerProviderStateMixin { + static const fadeInTime = 200; + late AnimationController _controller; + late Animation _opacityAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: fadeInTime), + vsync: this, + ); + _opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(_controller); + + _controller.forward().then((_) { + Future.delayed( + widget.message.duration - const Duration(milliseconds: fadeInTime * 2), + ).then((_) { + if (!mounted) { + return; + } + _controller.reverse().then((value) => toastService.instance.clear()); + }); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeData = AppKitModalTheme.getDataOf(context); + final themeColors = AppKitModalTheme.colorsOf(context); + final radiuses = AppKitModalTheme.radiusesOf(context); + return Positioned( + top: 10.0, + left: 20.0, + right: 20.0, + child: Center( + child: FadeTransition( + opacity: _opacityAnimation, + child: Container( + constraints: BoxConstraints( + minHeight: 40.0, + ), + decoration: BoxDecoration( + color: themeColors.background175, + borderRadius: BorderRadius.circular(radiuses.radiusM), + border: Border.all( + color: themeColors.grayGlass005, + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + widget.message.icon(context), + const SizedBox(width: 8.0), + Flexible( + child: Text( + widget.message.text, + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + ), + const SizedBox(width: 8.0), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/toast/toast_presenter.dart b/packages/reown_appkit/lib/modal/widgets/toast/toast_presenter.dart new file mode 100644 index 0000000..fe25e5a --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/toast/toast_presenter.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/toast/toast.dart'; + +class ToastPresenter extends StatelessWidget { + const ToastPresenter({super.key}); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: toastService.instance.toasts, + builder: (context, snapshot) { + if (snapshot.hasData && snapshot.data != null) { + return ToastWidget(message: snapshot.data!); + } + return const SizedBox.shrink(); + }, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/explorer_service_items_listener.dart b/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/explorer_service_items_listener.dart new file mode 100644 index 0000000..e6e753f --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/explorer_service_items_listener.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/models/grid_item.dart'; +import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class ExplorerServiceItemsListener extends StatefulWidget { + const ExplorerServiceItemsListener({ + super.key, + required this.builder, + this.listen = true, + }); + final Function( + BuildContext context, + bool initialised, + List> items, + bool searching, + ) builder; + final bool listen; + + @override + State createState() => + _ExplorerServiceItemsListenerState(); +} + +class _ExplorerServiceItemsListenerState + extends State { + List> _items = []; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: explorerService.instance.initialized, + builder: (context, initialised, _) { + if (!initialised) { + return widget.builder(context, initialised, [], false); + } + return ValueListenableBuilder( + valueListenable: explorerService.instance.isSearching, + builder: (context, searching, _) { + if (searching) { + return widget.builder(context, initialised, _items, searching); + } + return ValueListenableBuilder>( + valueListenable: explorerService.instance.listings, + builder: (context, items, _) { + if (widget.listen) { + _items = items.toGridItems(); + } + return widget.builder(context, initialised, _items, false); + }, + ); + }, + ); + }, + ); + } +} + +extension on List { + List> toGridItems() { + List> gridItems = []; + for (AppKitModalWalletInfo item in this) { + gridItems.add( + GridItem( + title: item.listing.name, + id: item.listing.id, + image: explorerService.instance.getWalletImageUrl( + item.listing.imageId, + ), + data: item, + ), + ); + } + return gridItems; + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/network_service_items_listener.dart b/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/network_service_items_listener.dart new file mode 100644 index 0000000..8eeed82 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/network_service_items_listener.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:reown_appkit/modal/models/grid_item.dart'; + +import 'package:reown_appkit/modal/models/public/appkit_network_info.dart'; +import 'package:reown_appkit/modal/services/network_service/network_service_singleton.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; + +class NetworkServiceItemsListener extends StatelessWidget { + const NetworkServiceItemsListener({ + super.key, + required this.builder, + }); + final Function( + BuildContext context, + bool initialised, + List> items, + ) builder; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: networkService.instance.initialized, + builder: (context, bool initialised, _) { + if (!initialised) { + return builder(context, initialised, []); + } + return ValueListenableBuilder( + valueListenable: networkService.instance.itemList, + builder: (context, items, _) { + final service = ModalProvider.of(context).service; + final supportedChains = service.getAvailableChains(); + final parsedItems = supportedChains == null + ? items + : items.map((e) { + final caip2Chain = + '${CoreConstants.namespace}:${e.data.chainId}'; + return e.copyWith( + disabled: !supportedChains.contains(caip2Chain), + ); + }).toList() + ..sort((a, b) { + final disabledA = a.disabled ? 0 : 1; + final disabledB = b.disabled ? 0 : 1; + return disabledB.compareTo(disabledA); + }); + return builder(context, initialised, parsedItems); + }, + ); + }, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/widget_stack/i_widget_stack.dart b/packages/reown_appkit/lib/modal/widgets/widget_stack/i_widget_stack.dart new file mode 100644 index 0000000..55bdf39 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/widget_stack/i_widget_stack.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; + +abstract class IWidgetStack with ChangeNotifier { + abstract final ValueNotifier onRenderScreen; + + /// Returns the current widget. + Widget getCurrent(); + + /// Pushes a widget to the stack. + void push( + Widget widget, { + bool renderScreen = false, + AnalyticsEvent? event, + }); + + /// Removes a widget from the stack. + void pop(); + + /// Checks if the stack can be popped. + bool canPop(); + + /// Removes widgets from the stack until the given type of widget is found. + void popUntil(Key key); + + void popAllAndPush(Widget widget, {bool renderScreen = false}); + + /// Checks if the stack contains a widget with the given key. + bool containsKey(Key key); + + /// Clears the stack. + void clear(); + + /// Adds a default widget to the stack based on platform. + void addDefault(); +} diff --git a/packages/reown_appkit/lib/modal/widgets/widget_stack/transition_container.dart b/packages/reown_appkit/lib/modal/widgets/widget_stack/transition_container.dart new file mode 100644 index 0000000..9a11cc0 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/widget_stack/transition_container.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; + +class TransitionContainer extends StatefulWidget { + const TransitionContainer({ + super.key, + required this.child, + }); + + final Widget child; + + @override + State createState() => _TransitionContainerState(); +} + +class _TransitionContainerState extends State + with TickerProviderStateMixin { + static const fadeDuration = Duration(milliseconds: 150); + static const resizeDuration = Duration(milliseconds: 150); + late AnimationController _fadeController; + late Animation _fadeAnimation; + late Animation _scaleAnimation; + late Widget _currentScreen; + + @override + void initState() { + super.initState(); + _currentScreen = widget.child; + + _fadeController = AnimationController( + vsync: this, + duration: fadeDuration, + ); + + _fadeAnimation = Tween( + begin: 1.0, + end: 0.0, + ).animate( + _fadeController, + ); + + _scaleAnimation = Tween( + begin: 1.0, + end: 0.98, + ).animate( + _fadeController, + ); + } + + @override + void dispose() { + _fadeController.dispose(); + // _resizeController.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(covariant TransitionContainer previousScreen) { + super.didUpdateWidget(previousScreen); + if (previousScreen.child.key != widget.child.key) { + _fadeController.forward().then((_) { + setState(() => _currentScreen = widget.child); + if (!widgetStack.instance.onRenderScreen.value) { + widgetStack.instance.onRenderScreen.value = true; + } + Future.delayed(fadeDuration).then((value) { + if (mounted) { + _fadeController.reverse(); + } + }); + }); + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: Listenable.merge([_fadeAnimation, _scaleAnimation]), + builder: (context, _) { + return Opacity( + opacity: _fadeAnimation.value, + child: AnimatedSize( + duration: resizeDuration, + child: _currentScreen, + ), + ); + }, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart b/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart new file mode 100644 index 0000000..917d664 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; + +import 'package:reown_appkit/modal/pages/public/appkit_modal_main_wallets_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/utils/platform_utils.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/i_widget_stack.dart'; + +class WidgetStack extends IWidgetStack { + final List _stack = []; + + @override + final ValueNotifier onRenderScreen = ValueNotifier(true); + + @override + Widget getCurrent() => _stack.last; + + @override + void push( + Widget widget, { + bool renderScreen = false, + AnalyticsEvent? event, + }) { + if (event != null) { + analyticsService.instance.sendEvent(event); + } + onRenderScreen.value = renderScreen; + _stack.add(widget); + notifyListeners(); + } + + @override + void pop() { + if (_stack.isNotEmpty) { + onRenderScreen.value = false; + _stack.removeLast(); + notifyListeners(); + } else { + throw Exception('The stack is empty. No widget to pop.'); + } + } + + @override + bool canPop() => _stack.length > 1; + + @override + void popUntil(Key key) { + if (_stack.isEmpty) { + throw Exception('The stack is empty. No widget to pop.'); + } else { + onRenderScreen.value = false; + while (_stack.isNotEmpty && _stack.last.key != key) { + _stack.removeLast(); + } + notifyListeners(); + + // Check if the stack is empty or the widget type not found + if (_stack.isEmpty) { + throw Exception('No widget of specified type found.'); + } + } + } + + @override + void popAllAndPush(Widget widget, {bool renderScreen = false}) { + _stack.clear(); + push(widget, renderScreen: renderScreen); + } + + @override + bool containsKey(Key key) { + return _stack.any((element) => element.key == key); + } + + @override + void clear() { + onRenderScreen.value = false; + _stack.clear(); + notifyListeners(); + } + + @override + void addDefault() { + final pType = PlatformUtils.getPlatformType(); + + // Choose the state based on platform + if (pType == PlatformType.mobile) { + push(const AppKitModalMainWalletsPage(), renderScreen: true); + } else if (pType == PlatformType.desktop || pType == PlatformType.web) { + // add(const QRCodeAndWalletListPage()); + push(const AppKitModalMainWalletsPage(), renderScreen: true); + // TODO [WidgetStack] fix non mobile page + } + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack_singleton.dart b/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack_singleton.dart new file mode 100644 index 0000000..7fd7e1d --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack_singleton.dart @@ -0,0 +1,9 @@ +import 'package:reown_appkit/modal/widgets/widget_stack/i_widget_stack.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack.dart'; + +class WidgetStackSingleton { + IWidgetStack instance; + WidgetStackSingleton() : instance = WidgetStack(); +} + +final widgetStack = WidgetStackSingleton(); diff --git a/packages/reown_appkit/lib/reown_appkit.dart b/packages/reown_appkit/lib/reown_appkit.dart new file mode 100644 index 0000000..1814f2f --- /dev/null +++ b/packages/reown_appkit/lib/reown_appkit.dart @@ -0,0 +1,17 @@ +library reown_appkit; + +/// +/// BASE +/// + +// packages +export 'package:reown_core/reown_core.dart'; +export 'package:reown_sign/reown_sign.dart'; +export 'package:event/event.dart'; + +// files +// export 'i_appkit_base.dart'; +export 'appkit_base.dart'; + +// export 'i_appkit_base.dart'; +export 'appkit_modal.dart'; diff --git a/packages/reown_appkit/lib/reown_appkit_method_channel.dart b/packages/reown_appkit/lib/reown_appkit_method_channel.dart new file mode 100644 index 0000000..7f5e5a5 --- /dev/null +++ b/packages/reown_appkit/lib/reown_appkit_method_channel.dart @@ -0,0 +1,18 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'reown_appkit_platform_interface.dart'; + +/// An implementation of [ReownAppkitPlatform] that uses method channels. +class MethodChannelReownAppkit extends ReownAppkitPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('reown_appkit'); + + @override + Future getPlatformVersion() async { + final version = + await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/packages/reown_appkit/lib/reown_appkit_platform_interface.dart b/packages/reown_appkit/lib/reown_appkit_platform_interface.dart new file mode 100644 index 0000000..676edb9 --- /dev/null +++ b/packages/reown_appkit/lib/reown_appkit_platform_interface.dart @@ -0,0 +1,29 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'reown_appkit_method_channel.dart'; + +abstract class ReownAppkitPlatform extends PlatformInterface { + /// Constructs a ReownAppkitPlatform. + ReownAppkitPlatform() : super(token: _token); + + static final Object _token = Object(); + + static ReownAppkitPlatform _instance = MethodChannelReownAppkit(); + + /// The default instance of [ReownAppkitPlatform] to use. + /// + /// Defaults to [MethodChannelReownAppkit]. + static ReownAppkitPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [ReownAppkitPlatform] when + /// they register themselves. + static set instance(ReownAppkitPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/packages/reown_appkit/lib/version.dart b/packages/reown_appkit/lib/version.dart new file mode 100644 index 0000000..526cd53 --- /dev/null +++ b/packages/reown_appkit/lib/version.dart @@ -0,0 +1,2 @@ +// Generated code. Do not modify. +const packageVersion = '1.0.0'; diff --git a/packages/reown_appkit/pubspec.yaml b/packages/reown_appkit/pubspec.yaml new file mode 100644 index 0000000..d403c29 --- /dev/null +++ b/packages/reown_appkit/pubspec.yaml @@ -0,0 +1,127 @@ +name: reown_appkit +description: "The communications protocol for web3" +version: 1.0.0 +homepage: https://github.com/reown-com/reown_flutter +repository: https://github.com/reown-com/reown_flutter + +environment: + sdk: ">=2.19.0 <4.0.0" + +dependencies: + appcheck: ^1.5.1 + cached_network_image: ^3.3.1 + coinbase_wallet_sdk: ^1.0.9 + collection: ^1.17.2 + convert: ^3.1.1 + cupertino_icons: ^1.0.2 + custom_sliding_segmented_control: ^1.8.1 + event: ^2.1.2 + flutter: + sdk: flutter + flutter_svg: ^2.0.10+1 + freezed_annotation: ^2.4.1 + http: ^1.1.2 + json_annotation: ^4.8.1 + plugin_platform_interface: ^2.1.8 + qr_flutter_wc: ^0.0.3 + reown_core: + path: ../reown_core + reown_sign: + path: ../reown_sign + shimmer: ^3.0.0 + url_launcher: ^6.2.5 + uuid: ^4.3.3 + webview_flutter: ^4.7.0 + webview_flutter_android: ^3.16.0 + webview_flutter_wkwebview: ^3.13.0 + +dev_dependencies: + build_runner: ^2.4.7 + build_version: ^2.1.1 + dependency_validator: ^3.2.2 + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + freezed: ^2.4.5 + json_serializable: ^6.7.0 + mockito: ^5.4.3 + package_info_plus: ^7.0.0 + reown_walletkit: + path: ../reown_walletkit + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + uses-material-design: true + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + # This plugin project was generated without specifying any + # platforms with the `--platform` argument. If you see the `some_platform` map below, remove it and + # then add platforms following the instruction here: + # https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms + # ------------------- + some_platform: + pluginClass: somePluginClass + # ------------------- + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages + assets: + - lib/modal/assets/ + - lib/modal/assets/dark/ + - lib/modal/assets/light/ + - lib/modal/assets/icons/ + - lib/modal/assets/icons/regular/ + - lib/modal/assets/help/ + - lib/modal/assets/png/ + - lib/modal/assets/png/2.0x/ + - lib/modal/assets/png/3.0x/ + +# TODO check this +platforms: + android: + ios: + # web: + # macos: + # linux: + # windows: diff --git a/packages/reown_appkit/test/reown_appkit_base_test.dart b/packages/reown_appkit/test/reown_appkit_base_test.dart new file mode 100644 index 0000000..8d08e57 --- /dev/null +++ b/packages/reown_appkit/test/reown_appkit_base_test.dart @@ -0,0 +1,201 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_values.dart'; + +import '../../reown_sign/test/tests/sign_common.dart'; +import '../../reown_walletkit/test/walletkit_helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(); + + signEngineTests( + context: 'ReownAppKit/ReownWalletKit', + clientACreator: (PairingMetadata metadata) async => + await ReownAppKit.createInstance( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + metadata: metadata, + memoryStore: true, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ), + clientBCreator: (PairingMetadata metadata) async => + await ReownWalletKit.createInstance( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + metadata: metadata, + memoryStore: true, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ), + ); + + final List Function(PairingMetadata)> appCreators = [ + (PairingMetadata metadata) async => await ReownAppKit.createInstance( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + memoryStore: true, + metadata: metadata, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ), + ]; + final List Function(PairingMetadata)> walletCreators = + [ + (PairingMetadata metadata) async => await ReownWalletKit.createInstance( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + memoryStore: true, + metadata: metadata, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ), + ]; + + final List contexts = ['ReownAppKit']; + + for (int i = 0; i < walletCreators.length; i++) { + runTests( + context: contexts[i], + appKitCreator: appCreators[i], + walletKitCreator: walletCreators[i], + ); + } +} + +void runTests({ + required String context, + required Future Function(PairingMetadata) appKitCreator, + required Future Function(PairingMetadata) walletKitCreator, +}) { + group(context, () { + late IReownAppKit clientA; + late IReownWalletKit clientB; + + setUp(() async { + clientA = await appKitCreator( + const PairingMetadata( + name: 'App A (Proposer, dapp)', + description: 'Description of Proposer App run by client A', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], + ), + ); + clientB = await walletKitCreator( + const PairingMetadata( + name: 'App B (Responder, Wallet)', + description: 'Description of Proposer App run by client B', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], + ), + ); + }); + + tearDown(() async { + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + group('happy path', () { + test('connects, reconnects, and emits proper events', () async { + Completer completer = Completer(); + Completer completerBSign = Completer(); + // Completer completerBAuth = Completer(); + int counterA = 0; + int counterBSign = 0; + // int counterBAuth = 0; + clientA.onSessionConnect.subscribe((args) { + counterA++; + completer.complete(); + }); + clientB.onSessionProposal.subscribe((args) { + counterBSign++; + completerBSign.complete(); + }); + // clientB.onAuthRequest.subscribe((args) { + // counterBAuth++; + // completerBAuth.complete(); + // }); + + final connectionInfo = await ReownWalletKitHelpers.testWalletKit( + clientA, + clientB, + qrCodeScanLatencyMs: 1000, + ); + + // print('swag 1'); + await completer.future; + // print('swag 2'); + await completerBSign.future; + // print('swag 3'); + // await completerBAuth.future; + + expect(counterA, 1); + expect(counterBSign, 1); + // expect(counterBAuth, 1); + + expect( + clientA.pairings.getAll().length, + clientB.pairings.getAll().length, + ); + expect( + clientA.getActiveSessions().length, + 1, + ); + expect( + clientA.getActiveSessions().length, + clientB.getActiveSessions().length, + ); + + completer = Completer(); + completerBSign = Completer(); + // completerBAuth = Completer(); + + final _ = await ReownWalletKitHelpers.testWalletKit( + clientA, + clientB, + pairingTopic: connectionInfo.pairing.topic, + ); + + // print('swag 4'); + await completer.future; + // print('swag 5'); + await completerBSign.future; + // print('swag 6'); + // await completerBAuth.future; + + expect(counterA, 2); + expect(counterBSign, 2); + // expect(counterBAuth, 2); + + clientA.onSessionConnect.unsubscribeAll(); + clientB.onSessionProposal.unsubscribeAll(); + }); + + test('connects, and reconnects with scan latency', () async { + final connectionInfo = await ReownWalletKitHelpers.testWalletKit( + clientA, + clientB, + qrCodeScanLatencyMs: 1000, + ); + expect( + clientA.pairings.getAll().length, + clientB.pairings.getAll().length, + ); + final _ = await ReownWalletKitHelpers.testWalletKit( + clientA, + clientB, + pairingTopic: connectionInfo.pairing.topic, + qrCodeScanLatencyMs: 1000, + ); + }); + }); + }); +} diff --git a/packages/reown_appkit/test/reown_appkit_modal_method_channel_test.dart b/packages/reown_appkit/test/reown_appkit_modal_method_channel_test.dart new file mode 100644 index 0000000..460abfe --- /dev/null +++ b/packages/reown_appkit/test/reown_appkit_modal_method_channel_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_appkit/reown_appkit_method_channel.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelReownAppkit platform = MethodChannelReownAppkit(); + const MethodChannel channel = MethodChannel('reown_appkit'); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + channel, + (MethodCall methodCall) async { + return '42'; + }, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/packages/reown_appkit/test/reown_appkit_modal_test.dart b/packages/reown_appkit/test/reown_appkit_modal_test.dart new file mode 100644 index 0000000..e094cdf --- /dev/null +++ b/packages/reown_appkit/test/reown_appkit_modal_test.dart @@ -0,0 +1,28 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:reown_appkit/reown_appkit_platform_interface.dart'; +import 'package:reown_appkit/reown_appkit_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockReownAppkitPlatform + with MockPlatformInterfaceMixin + implements ReownAppkitPlatform { + @override + Future getPlatformVersion() => Future.value('42'); +} + +void main() { + final ReownAppkitPlatform initialPlatform = ReownAppkitPlatform.instance; + + test('$MethodChannelReownAppkit is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + // ReownAppkitModal reownAppkitPlugin = ReownAppkitModal(); + // MockReownAppkitPlatform fakePlatform = MockReownAppkitPlatform(); + // ReownAppkitPlatform.instance = fakePlatform; + + // expect(await reownAppkitPlugin.getPlatformVersion(), '42'); + }); +} diff --git a/packages/reown_appkit/test/shared/shared_test_utils.dart b/packages/reown_appkit/test/shared/shared_test_utils.dart new file mode 100644 index 0000000..cc6c760 --- /dev/null +++ b/packages/reown_appkit/test/shared/shared_test_utils.dart @@ -0,0 +1,119 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import 'package:reown_core/crypto/crypto.dart'; +import 'package:reown_core/crypto/crypto_utils.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/relay_client/i_message_tracker.dart'; +import 'package:reown_core/relay_client/message_tracker.dart'; +import 'package:reown_core/relay_client/websocket/http_client.dart'; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +import 'shared_test_utils.mocks.dart'; + +@GenerateMocks([ + CryptoUtils, + Crypto, + MessageTracker, + HttpWrapper, + ReownCore, + WebSocketHandler, +]) +class SharedTestUtils {} + +ICrypto getCrypto({ + IReownCore? core, + MockCryptoUtils? utils, +}) { + final IReownCore rcore = core ?? ReownCore(projectId: '', memoryStore: true); + final ICrypto crypto = Crypto( + core: rcore, + keyChain: GenericStore( + storage: rcore.storage, + context: StoreVersions.CONTEXT_KEYCHAIN, + version: StoreVersions.VERSION_KEYCHAIN, + fromJson: (dynamic value) => value as String, + ), + utils: utils, + ); + rcore.crypto = crypto; + return crypto; +} + +IMessageTracker getMessageTracker({ + IReownCore? core, +}) { + final IReownCore rcore = core ?? ReownCore(projectId: '', memoryStore: true); + return MessageTracker( + storage: rcore.storage, + context: StoreVersions.CONTEXT_MESSAGE_TRACKER, + version: StoreVersions.VERSION_MESSAGE_TRACKER, + fromJson: (dynamic value) => ReownCoreUtils.convertMapTo(value), + ); +} + +IGenericStore getTopicMap({ + IReownCore? core, +}) { + final IReownCore rcore = core ?? ReownCore(projectId: '', memoryStore: true); + return GenericStore( + storage: rcore.storage, + context: StoreVersions.CONTEXT_TOPIC_MAP, + version: StoreVersions.VERSION_TOPIC_MAP, + fromJson: (dynamic value) => value as String, + ); +} + +MockHttpWrapper getHttpWrapper() { + final MockHttpWrapper httpWrapper = MockHttpWrapper(); + when(httpWrapper.get(any)).thenAnswer((_) async => Response('', 200)); + // when(httpWrapper.post( + // url: anyNamed('url'), + // body: anyNamed('body'), + // )).thenAnswer((_) async => ''); + + return httpWrapper; +} + +mockPackageInfo() { + PackageInfo.setMockInitialValues( + appName: _mockInitialValues['appName'], + packageName: _mockInitialValues['packageName'], + version: _mockInitialValues['version'], + buildNumber: _mockInitialValues['buildNumber'], + buildSignature: _mockInitialValues['buildSignature'], + ); +} + +mockConnectivity([List values = const ['wifi']]) { + const channel = MethodChannel('dev.fluttercommunity.plus/connectivity'); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler( + channel.name, + (data) async { + final call = channel.codec.decodeMethodCall(data); + if (call.method == 'getAll') { + return channel.codec.encodeSuccessEnvelope(_mockInitialValues); + } + if (call.method == 'check') { + return channel.codec.encodeSuccessEnvelope(values); + } + return null; + }, + ); +} + +Map get _mockInitialValues => { + 'appName': 'reown_core', + 'packageName': 'com.reown.flutterdapp', + 'version': '1.0', + 'buildNumber': '2', + 'buildSignature': 'buildSignature', + }; diff --git a/packages/reown_appkit/test/shared/shared_test_utils.mocks.dart b/packages/reown_appkit/test/shared/shared_test_utils.mocks.dart new file mode 100644 index 0000000..d8a1e50 --- /dev/null +++ b/packages/reown_appkit/test/shared/shared_test_utils.mocks.dart @@ -0,0 +1,1558 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in reown_appkit/test/shared/shared_test_utils.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i23; +import 'dart:typed_data' as _i21; + +import 'package:event/event.dart' as _i8; +import 'package:http/http.dart' as _i9; +import 'package:logger/logger.dart' as _i19; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i22; +import 'package:reown_core/connectivity/i_connectivity.dart' as _i17; +import 'package:reown_core/core_impl.dart' as _i28; +import 'package:reown_core/crypto/crypto.dart' as _i24; +import 'package:reown_core/crypto/crypto_models.dart' as _i2; +import 'package:reown_core/crypto/crypto_utils.dart' as _i20; +import 'package:reown_core/crypto/i_crypto.dart' as _i10; +import 'package:reown_core/crypto/i_crypto_utils.dart' as _i5; +import 'package:reown_core/echo/i_echo.dart' as _i14; +import 'package:reown_core/heartbit/i_heartbeat.dart' as _i15; +import 'package:reown_core/i_core_impl.dart' as _i3; +import 'package:reown_core/pairing/i_expirer.dart' as _i12; +import 'package:reown_core/pairing/i_pairing.dart' as _i13; +import 'package:reown_core/relay_auth/i_relay_auth.dart' as _i6; +import 'package:reown_core/relay_client/i_relay_client.dart' as _i11; +import 'package:reown_core/relay_client/message_tracker.dart' as _i25; +import 'package:reown_core/relay_client/websocket/http_client.dart' as _i27; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart' + as _i29; +import 'package:reown_core/store/i_generic_store.dart' as _i4; +import 'package:reown_core/store/i_store.dart' as _i7; +import 'package:reown_core/store/link_mode_store.dart' as _i18; +import 'package:reown_core/store/store_models.dart' as _i26; +import 'package:reown_core/verify/i_verify.dart' as _i16; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeCryptoKeyPair_0 extends _i1.SmartFake implements _i2.CryptoKeyPair { + _FakeCryptoKeyPair_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEncodingParams_1 extends _i1.SmartFake + implements _i2.EncodingParams { + _FakeEncodingParams_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEncodingValidation_2 extends _i1.SmartFake + implements _i2.EncodingValidation { + _FakeEncodingValidation_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIReownCore_3 extends _i1.SmartFake implements _i3.IReownCore { + _FakeIReownCore_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIGenericStore_4 extends _i1.SmartFake + implements _i4.IGenericStore { + _FakeIGenericStore_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeICryptoUtils_5 extends _i1.SmartFake implements _i5.ICryptoUtils { + _FakeICryptoUtils_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIRelayAuth_6 extends _i1.SmartFake implements _i6.IRelayAuth { + _FakeIRelayAuth_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIStore_7 extends _i1.SmartFake implements _i7.IStore { + _FakeIStore_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEvent_8 extends _i1.SmartFake + implements _i8.Event { + _FakeEvent_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponse_9 extends _i1.SmartFake implements _i9.Response { + _FakeResponse_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeICrypto_10 extends _i1.SmartFake implements _i10.ICrypto { + _FakeICrypto_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIRelayClient_11 extends _i1.SmartFake implements _i11.IRelayClient { + _FakeIRelayClient_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIExpirer_12 extends _i1.SmartFake implements _i12.IExpirer { + _FakeIExpirer_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIPairing_13 extends _i1.SmartFake implements _i13.IPairing { + _FakeIPairing_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIEcho_14 extends _i1.SmartFake implements _i14.IEcho { + _FakeIEcho_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIHeartBeat_15 extends _i1.SmartFake implements _i15.IHeartBeat { + _FakeIHeartBeat_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIVerify_16 extends _i1.SmartFake implements _i16.IVerify { + _FakeIVerify_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIConnectivity_17 extends _i1.SmartFake + implements _i17.IConnectivity { + _FakeIConnectivity_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeILinkModeStore_18 extends _i1.SmartFake + implements _i18.ILinkModeStore { + _FakeILinkModeStore_18( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLogger_19 extends _i1.SmartFake implements _i19.Logger { + _FakeLogger_19( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [CryptoUtils]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCryptoUtils extends _i1.Mock implements _i20.CryptoUtils { + MockCryptoUtils() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.CryptoKeyPair generateKeyPair() => (super.noSuchMethod( + Invocation.method( + #generateKeyPair, + [], + ), + returnValue: _FakeCryptoKeyPair_0( + this, + Invocation.method( + #generateKeyPair, + [], + ), + ), + ) as _i2.CryptoKeyPair); + + @override + _i21.Uint8List randomBytes(int? length) => (super.noSuchMethod( + Invocation.method( + #randomBytes, + [length], + ), + returnValue: _i21.Uint8List(0), + ) as _i21.Uint8List); + + @override + String generateRandomBytes32() => (super.noSuchMethod( + Invocation.method( + #generateRandomBytes32, + [], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #generateRandomBytes32, + [], + ), + ), + ) as String); + + @override + _i23.Future deriveSymKey( + String? privKeyA, + String? pubKeyB, + ) => + (super.noSuchMethod( + Invocation.method( + #deriveSymKey, + [ + privKeyA, + pubKeyB, + ], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #deriveSymKey, + [ + privKeyA, + pubKeyB, + ], + ), + )), + ) as _i23.Future); + + @override + String hashKey(String? key) => (super.noSuchMethod( + Invocation.method( + #hashKey, + [key], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashKey, + [key], + ), + ), + ) as String); + + @override + String hashMessage(String? message) => (super.noSuchMethod( + Invocation.method( + #hashMessage, + [message], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashMessage, + [message], + ), + ), + ) as String); + + @override + _i23.Future encrypt( + String? message, + String? symKey, { + int? type, + String? iv, + String? senderPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #encrypt, + [ + message, + symKey, + ], + { + #type: type, + #iv: iv, + #senderPublicKey: senderPublicKey, + }, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #encrypt, + [ + message, + symKey, + ], + { + #type: type, + #iv: iv, + #senderPublicKey: senderPublicKey, + }, + ), + )), + ) as _i23.Future); + + @override + _i23.Future decrypt( + String? symKey, + String? encoded, + ) => + (super.noSuchMethod( + Invocation.method( + #decrypt, + [ + symKey, + encoded, + ], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #decrypt, + [ + symKey, + encoded, + ], + ), + )), + ) as _i23.Future); + + @override + String serialize( + int? type, + _i21.Uint8List? sealed, + _i21.Uint8List? iv, { + _i21.Uint8List? senderPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #serialize, + [ + type, + sealed, + iv, + ], + {#senderPublicKey: senderPublicKey}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #serialize, + [ + type, + sealed, + iv, + ], + {#senderPublicKey: senderPublicKey}, + ), + ), + ) as String); + + @override + _i2.EncodingParams deserialize(String? encoded) => (super.noSuchMethod( + Invocation.method( + #deserialize, + [encoded], + ), + returnValue: _FakeEncodingParams_1( + this, + Invocation.method( + #deserialize, + [encoded], + ), + ), + ) as _i2.EncodingParams); + + @override + _i2.EncodingValidation validateDecoding( + String? encoded, { + String? receiverPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #validateDecoding, + [encoded], + {#receiverPublicKey: receiverPublicKey}, + ), + returnValue: _FakeEncodingValidation_2( + this, + Invocation.method( + #validateDecoding, + [encoded], + {#receiverPublicKey: receiverPublicKey}, + ), + ), + ) as _i2.EncodingValidation); + + @override + _i2.EncodingValidation validateEncoding({ + int? type, + String? senderPublicKey, + String? receiverPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #validateEncoding, + [], + { + #type: type, + #senderPublicKey: senderPublicKey, + #receiverPublicKey: receiverPublicKey, + }, + ), + returnValue: _FakeEncodingValidation_2( + this, + Invocation.method( + #validateEncoding, + [], + { + #type: type, + #senderPublicKey: senderPublicKey, + #receiverPublicKey: receiverPublicKey, + }, + ), + ), + ) as _i2.EncodingValidation); + + @override + bool isTypeOneEnvelope(_i2.EncodingValidation? result) => (super.noSuchMethod( + Invocation.method( + #isTypeOneEnvelope, + [result], + ), + returnValue: false, + ) as bool); + + @override + bool isTypeTwoEnvelope(_i2.EncodingValidation? result) => (super.noSuchMethod( + Invocation.method( + #isTypeTwoEnvelope, + [result], + ), + returnValue: false, + ) as bool); + + @override + _i21.Uint8List encodeTypeByte(int? type) => (super.noSuchMethod( + Invocation.method( + #encodeTypeByte, + [type], + ), + returnValue: _i21.Uint8List(0), + ) as _i21.Uint8List); + + @override + int decodeTypeByte(_i21.Uint8List? byte) => (super.noSuchMethod( + Invocation.method( + #decodeTypeByte, + [byte], + ), + returnValue: 0, + ) as int); + + @override + String encodeTypeTwoEnvelope({required String? message}) => + (super.noSuchMethod( + Invocation.method( + #encodeTypeTwoEnvelope, + [], + {#message: message}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #encodeTypeTwoEnvelope, + [], + {#message: message}, + ), + ), + ) as String); + + @override + String decodeTypeTwoEnvelope({required String? message}) => + (super.noSuchMethod( + Invocation.method( + #decodeTypeTwoEnvelope, + [], + {#message: message}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #decodeTypeTwoEnvelope, + [], + {#message: message}, + ), + ), + ) as String); +} + +/// A class which mocks [Crypto]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCrypto extends _i1.Mock implements _i24.Crypto { + MockCrypto() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.IReownCore get core => (super.noSuchMethod( + Invocation.getter(#core), + returnValue: _FakeIReownCore_3( + this, + Invocation.getter(#core), + ), + ) as _i3.IReownCore); + + @override + _i4.IGenericStore get keyChain => (super.noSuchMethod( + Invocation.getter(#keyChain), + returnValue: _FakeIGenericStore_4( + this, + Invocation.getter(#keyChain), + ), + ) as _i4.IGenericStore); + + @override + set keyChain(_i4.IGenericStore? _keyChain) => super.noSuchMethod( + Invocation.setter( + #keyChain, + _keyChain, + ), + returnValueForMissingStub: null, + ); + + @override + _i5.ICryptoUtils get utils => (super.noSuchMethod( + Invocation.getter(#utils), + returnValue: _FakeICryptoUtils_5( + this, + Invocation.getter(#utils), + ), + ) as _i5.ICryptoUtils); + + @override + set utils(_i5.ICryptoUtils? _utils) => super.noSuchMethod( + Invocation.setter( + #utils, + _utils, + ), + returnValueForMissingStub: null, + ); + + @override + _i6.IRelayAuth get relayAuth => (super.noSuchMethod( + Invocation.getter(#relayAuth), + returnValue: _FakeIRelayAuth_6( + this, + Invocation.getter(#relayAuth), + ), + ) as _i6.IRelayAuth); + + @override + set relayAuth(_i6.IRelayAuth? _relayAuth) => super.noSuchMethod( + Invocation.setter( + #relayAuth, + _relayAuth, + ), + returnValueForMissingStub: null, + ); + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + _i23.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool hasKeys(String? tag) => (super.noSuchMethod( + Invocation.method( + #hasKeys, + [tag], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future getClientId() => (super.noSuchMethod( + Invocation.method( + #getClientId, + [], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #getClientId, + [], + ), + )), + ) as _i23.Future); + + @override + _i23.Future generateKeyPair() => (super.noSuchMethod( + Invocation.method( + #generateKeyPair, + [], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #generateKeyPair, + [], + ), + )), + ) as _i23.Future); + + @override + _i23.Future generateSharedKey( + String? selfPublicKey, + String? peerPublicKey, { + String? overrideTopic, + }) => + (super.noSuchMethod( + Invocation.method( + #generateSharedKey, + [ + selfPublicKey, + peerPublicKey, + ], + {#overrideTopic: overrideTopic}, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #generateSharedKey, + [ + selfPublicKey, + peerPublicKey, + ], + {#overrideTopic: overrideTopic}, + ), + )), + ) as _i23.Future); + + @override + _i23.Future setSymKey( + String? symKey, { + String? overrideTopic, + }) => + (super.noSuchMethod( + Invocation.method( + #setSymKey, + [symKey], + {#overrideTopic: overrideTopic}, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #setSymKey, + [symKey], + {#overrideTopic: overrideTopic}, + ), + )), + ) as _i23.Future); + + @override + _i23.Future deleteKeyPair(String? publicKey) => (super.noSuchMethod( + Invocation.method( + #deleteKeyPair, + [publicKey], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future deleteSymKey(String? topic) => (super.noSuchMethod( + Invocation.method( + #deleteSymKey, + [topic], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future encode( + String? topic, + Map? payload, { + _i2.EncodeOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #encode, + [ + topic, + payload, + ], + {#options: options}, + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future decode( + String? topic, + String? encoded, { + _i2.DecodeOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #decode, + [ + topic, + encoded, + ], + {#options: options}, + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future signJWT(String? aud) => (super.noSuchMethod( + Invocation.method( + #signJWT, + [aud], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #signJWT, + [aud], + ), + )), + ) as _i23.Future); + + @override + int getPayloadType(String? encoded) => (super.noSuchMethod( + Invocation.method( + #getPayloadType, + [encoded], + ), + returnValue: 0, + ) as int); + + @override + String? getPayloadSenderPublicKey(String? encoded) => + (super.noSuchMethod(Invocation.method( + #getPayloadSenderPublicKey, + [encoded], + )) as String?); + + @override + _i5.ICryptoUtils getUtils() => (super.noSuchMethod( + Invocation.method( + #getUtils, + [], + ), + returnValue: _FakeICryptoUtils_5( + this, + Invocation.method( + #getUtils, + [], + ), + ), + ) as _i5.ICryptoUtils); +} + +/// A class which mocks [MessageTracker]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMessageTracker extends _i1.Mock implements _i25.MessageTracker { + MockMessageTracker() { + _i1.throwOnMissingStub(this); + } + + @override + String get context => (super.noSuchMethod( + Invocation.getter(#context), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#context), + ), + ) as String); + + @override + String get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#version), + ), + ) as String); + + @override + _i7.IStore get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeIStore_7( + this, + Invocation.getter(#storage), + ), + ) as _i7.IStore); + + @override + _i8.Event<_i26.StoreCreateEvent>> get onCreate => + (super.noSuchMethod( + Invocation.getter(#onCreate), + returnValue: _FakeEvent_8<_i26.StoreCreateEvent>>( + this, + Invocation.getter(#onCreate), + ), + ) as _i8.Event<_i26.StoreCreateEvent>>); + + @override + _i8.Event<_i26.StoreUpdateEvent>> get onUpdate => + (super.noSuchMethod( + Invocation.getter(#onUpdate), + returnValue: _FakeEvent_8<_i26.StoreUpdateEvent>>( + this, + Invocation.getter(#onUpdate), + ), + ) as _i8.Event<_i26.StoreUpdateEvent>>); + + @override + _i8.Event<_i26.StoreDeleteEvent>> get onDelete => + (super.noSuchMethod( + Invocation.getter(#onDelete), + returnValue: _FakeEvent_8<_i26.StoreDeleteEvent>>( + this, + Invocation.getter(#onDelete), + ), + ) as _i8.Event<_i26.StoreDeleteEvent>>); + + @override + _i8.Event<_i26.StoreSyncEvent> get onSync => (super.noSuchMethod( + Invocation.getter(#onSync), + returnValue: _FakeEvent_8<_i26.StoreSyncEvent>( + this, + Invocation.getter(#onSync), + ), + ) as _i8.Event<_i26.StoreSyncEvent>); + + @override + Map> get data => (super.noSuchMethod( + Invocation.getter(#data), + returnValue: >{}, + ) as Map>); + + @override + set data(Map>? _data) => super.noSuchMethod( + Invocation.setter( + #data, + _data, + ), + returnValueForMissingStub: null, + ); + + @override + Map Function(dynamic) get fromJson => (super.noSuchMethod( + Invocation.getter(#fromJson), + returnValue: (dynamic __p0) => {}, + ) as Map Function(dynamic)); + + @override + String get storageKey => (super.noSuchMethod( + Invocation.getter(#storageKey), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#storageKey), + ), + ) as String); + + @override + String hashMessage(String? message) => (super.noSuchMethod( + Invocation.method( + #hashMessage, + [message], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashMessage, + [message], + ), + ), + ) as String); + + @override + _i23.Future recordMessageEvent( + String? topic, + String? message, + ) => + (super.noSuchMethod( + Invocation.method( + #recordMessageEvent, + [ + topic, + message, + ], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool messageIsRecorded( + String? topic, + String? message, + ) => + (super.noSuchMethod( + Invocation.method( + #messageIsRecorded, + [ + topic, + message, + ], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool has(String? key) => (super.noSuchMethod( + Invocation.method( + #has, + [key], + ), + returnValue: false, + ) as bool); + + @override + Map? get(String? key) => + (super.noSuchMethod(Invocation.method( + #get, + [key], + )) as Map?); + + @override + List> getAll() => (super.noSuchMethod( + Invocation.method( + #getAll, + [], + ), + returnValue: >[], + ) as List>); + + @override + _i23.Future set( + String? key, + Map? value, + ) => + (super.noSuchMethod( + Invocation.method( + #set, + [ + key, + value, + ], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future delete(String? key) => (super.noSuchMethod( + Invocation.method( + #delete, + [key], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future persist() => (super.noSuchMethod( + Invocation.method( + #persist, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future restore() => (super.noSuchMethod( + Invocation.method( + #restore, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + void checkInitialized() => super.noSuchMethod( + Invocation.method( + #checkInitialized, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [HttpWrapper]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHttpWrapper extends _i1.Mock implements _i27.HttpWrapper { + MockHttpWrapper() { + _i1.throwOnMissingStub(this); + } + + @override + _i23.Future<_i9.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i23.Future<_i9.Response>); + + @override + _i23.Future<_i9.Response> delete( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + {#headers: headers}, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #delete, + [url], + {#headers: headers}, + ), + )), + ) as _i23.Future<_i9.Response>); + + @override + _i23.Future<_i9.Response> post( + Uri? url, { + Map? headers, + Object? body, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + }, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + }, + ), + )), + ) as _i23.Future<_i9.Response>); +} + +/// A class which mocks [ReownCore]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReownCore extends _i1.Mock implements _i28.ReownCore { + MockReownCore() { + _i1.throwOnMissingStub(this); + } + + @override + String get projectId => (super.noSuchMethod( + Invocation.getter(#projectId), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#projectId), + ), + ) as String); + + @override + String get relayUrl => (super.noSuchMethod( + Invocation.getter(#relayUrl), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#relayUrl), + ), + ) as String); + + @override + set relayUrl(String? _relayUrl) => super.noSuchMethod( + Invocation.setter( + #relayUrl, + _relayUrl, + ), + returnValueForMissingStub: null, + ); + + @override + String get pushUrl => (super.noSuchMethod( + Invocation.getter(#pushUrl), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#pushUrl), + ), + ) as String); + + @override + set pushUrl(String? _pushUrl) => super.noSuchMethod( + Invocation.setter( + #pushUrl, + _pushUrl, + ), + returnValueForMissingStub: null, + ); + + @override + _i10.ICrypto get crypto => (super.noSuchMethod( + Invocation.getter(#crypto), + returnValue: _FakeICrypto_10( + this, + Invocation.getter(#crypto), + ), + ) as _i10.ICrypto); + + @override + set crypto(_i10.ICrypto? _crypto) => super.noSuchMethod( + Invocation.setter( + #crypto, + _crypto, + ), + returnValueForMissingStub: null, + ); + + @override + _i11.IRelayClient get relayClient => (super.noSuchMethod( + Invocation.getter(#relayClient), + returnValue: _FakeIRelayClient_11( + this, + Invocation.getter(#relayClient), + ), + ) as _i11.IRelayClient); + + @override + set relayClient(_i11.IRelayClient? _relayClient) => super.noSuchMethod( + Invocation.setter( + #relayClient, + _relayClient, + ), + returnValueForMissingStub: null, + ); + + @override + _i12.IExpirer get expirer => (super.noSuchMethod( + Invocation.getter(#expirer), + returnValue: _FakeIExpirer_12( + this, + Invocation.getter(#expirer), + ), + ) as _i12.IExpirer); + + @override + set expirer(_i12.IExpirer? _expirer) => super.noSuchMethod( + Invocation.setter( + #expirer, + _expirer, + ), + returnValueForMissingStub: null, + ); + + @override + _i13.IPairing get pairing => (super.noSuchMethod( + Invocation.getter(#pairing), + returnValue: _FakeIPairing_13( + this, + Invocation.getter(#pairing), + ), + ) as _i13.IPairing); + + @override + set pairing(_i13.IPairing? _pairing) => super.noSuchMethod( + Invocation.setter( + #pairing, + _pairing, + ), + returnValueForMissingStub: null, + ); + + @override + _i14.IEcho get echo => (super.noSuchMethod( + Invocation.getter(#echo), + returnValue: _FakeIEcho_14( + this, + Invocation.getter(#echo), + ), + ) as _i14.IEcho); + + @override + set echo(_i14.IEcho? _echo) => super.noSuchMethod( + Invocation.setter( + #echo, + _echo, + ), + returnValueForMissingStub: null, + ); + + @override + _i15.IHeartBeat get heartbeat => (super.noSuchMethod( + Invocation.getter(#heartbeat), + returnValue: _FakeIHeartBeat_15( + this, + Invocation.getter(#heartbeat), + ), + ) as _i15.IHeartBeat); + + @override + set heartbeat(_i15.IHeartBeat? _heartbeat) => super.noSuchMethod( + Invocation.setter( + #heartbeat, + _heartbeat, + ), + returnValueForMissingStub: null, + ); + + @override + _i16.IVerify get verify => (super.noSuchMethod( + Invocation.getter(#verify), + returnValue: _FakeIVerify_16( + this, + Invocation.getter(#verify), + ), + ) as _i16.IVerify); + + @override + set verify(_i16.IVerify? _verify) => super.noSuchMethod( + Invocation.setter( + #verify, + _verify, + ), + returnValueForMissingStub: null, + ); + + @override + _i17.IConnectivity get connectivity => (super.noSuchMethod( + Invocation.getter(#connectivity), + returnValue: _FakeIConnectivity_17( + this, + Invocation.getter(#connectivity), + ), + ) as _i17.IConnectivity); + + @override + set connectivity(_i17.IConnectivity? _connectivity) => super.noSuchMethod( + Invocation.setter( + #connectivity, + _connectivity, + ), + returnValueForMissingStub: null, + ); + + @override + _i18.ILinkModeStore get linkModeStore => (super.noSuchMethod( + Invocation.getter(#linkModeStore), + returnValue: _FakeILinkModeStore_18( + this, + Invocation.getter(#linkModeStore), + ), + ) as _i18.ILinkModeStore); + + @override + set linkModeStore(_i18.ILinkModeStore? _linkModeStore) => super.noSuchMethod( + Invocation.setter( + #linkModeStore, + _linkModeStore, + ), + returnValueForMissingStub: null, + ); + + @override + _i7.IStore> get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeIStore_7>( + this, + Invocation.getter(#storage), + ), + ) as _i7.IStore>); + + @override + set storage(_i7.IStore>? _storage) => super.noSuchMethod( + Invocation.setter( + #storage, + _storage, + ), + returnValueForMissingStub: null, + ); + + @override + String get protocol => (super.noSuchMethod( + Invocation.getter(#protocol), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#protocol), + ), + ) as String); + + @override + String get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#version), + ), + ) as String); + + @override + _i19.Logger get logger => (super.noSuchMethod( + Invocation.getter(#logger), + returnValue: _FakeLogger_19( + this, + Invocation.getter(#logger), + ), + ) as _i19.Logger); + + @override + void addLogListener(_i19.LogCallback? callback) => super.noSuchMethod( + Invocation.method( + #addLogListener, + [callback], + ), + returnValueForMissingStub: null, + ); + + @override + bool removeLogListener(_i19.LogCallback? callback) => (super.noSuchMethod( + Invocation.method( + #removeLogListener, + [callback], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future start() => (super.noSuchMethod( + Invocation.method( + #start, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future addLinkModeSupportedApp(String? universalLink) => + (super.noSuchMethod( + Invocation.method( + #addLinkModeSupportedApp, + [universalLink], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + List getLinkModeSupportedApps() => (super.noSuchMethod( + Invocation.method( + #getLinkModeSupportedApps, + [], + ), + returnValue: [], + ) as List); + + @override + void confirmOnlineStateOrThrow() => super.noSuchMethod( + Invocation.method( + #confirmOnlineStateOrThrow, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [WebSocketHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSocketHandler extends _i1.Mock implements _i29.WebSocketHandler { + MockWebSocketHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i23.Future get ready => (super.noSuchMethod( + Invocation.getter(#ready), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future setup({required String? url}) => (super.noSuchMethod( + Invocation.method( + #setup, + [], + {#url: url}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future connect() => (super.noSuchMethod( + Invocation.method( + #connect, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future close() => (super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); +} diff --git a/packages/reown_appkit/test/shared/shared_test_values.dart b/packages/reown_appkit/test/shared/shared_test_values.dart new file mode 100644 index 0000000..59edf2d --- /dev/null +++ b/packages/reown_appkit/test/shared/shared_test_values.dart @@ -0,0 +1,236 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +const TEST_RELAY_URL = String.fromEnvironment( + 'RELAY_ENDPOINT', + defaultValue: 'wss://relay.walletconnect.org', +); +const TEST_PROJECT_ID = String.fromEnvironment( + 'PROJECT_ID', + defaultValue: 'cad4956f31a5e40a00b62865b030c6f8', +); + +const PROPOSER = PairingMetadata( + name: 'App A (Proposer, dapp)', + description: 'Description of Proposer App run by client A', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], +); +const RESPONDER = PairingMetadata( + name: 'App B (Responder, Wallet)', + description: 'Description of Proposer App run by client B', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], +); + +const TEST_PAIRING_TOPIC = ''; +const TEST_SESSION_TOPIC = ''; +const TEST_KEY_PAIRS = { + 'A': CryptoKeyPair( + '1fb63fca5c6ac731246f2f069d3bc2454345d5208254aa8ea7bffc6d110c8862', + 'ff7a7d5767c362b0a17ad92299ebdb7831dcbd9a56959c01368c7404543b3342', + ), + 'B': CryptoKeyPair( + '36bf507903537de91f5e573666eaa69b1fa313974f23b2b59645f20fea505854', + '590c2c627be7af08597091ff80dd41f7fa28acd10ef7191d7e830e116d3a186a', + ), +}; + +const TEST_SHARED_KEY = + '9c87e48e69b33a613907515bcd5b1b4cc10bbaf15167b19804b00f0a9217e607'; +const TEST_HASHED_KEY = + 'a492906ccc809a411bb53a84572b57329375378c6ad7566f3e1c688200123e77'; +const TEST_SYM_KEY = + '0653ca620c7b4990392e1c53c4a51c14a2840cd20f0f1524cf435b17b6fe988c'; + +const TEST_URI = + 'wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&relay-protocol=irn'; +const TEST_URI_V1 = + 'wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@1?key=abc&bridge=xyz'; + +const TEST_ETHEREUM_CHAIN = 'eip155:1'; + +final Set availableAccounts = { + 'namespace1:chain1:address1', + 'namespace1:chain1:address2', + 'namespace2:chain1:address3', + 'namespace2:chain1:address4', + 'namespace2:chain2:address5', + 'namespace4:chain1:address6', +}; + +final Set availableMethods = { + 'namespace1:chain1:method1', + 'namespace1:chain1:method2', + 'namespace2:chain1:method3', + 'namespace2:chain1:method4', + 'namespace2:chain2:method3', + 'namespace4:chain1:method5', +}; + +final Set availableEvents = { + 'namespace1:chain1:event1', + 'namespace1:chain1:event2', + 'namespace2:chain1:event3', + 'namespace2:chain1:event4', + 'namespace2:chain2:event3', + 'namespace4:chain1:event5', +}; + +final Map requiredNamespacesInAvailable = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1'], + events: ['event1'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesInAvailable2 = { + 'namespace1': const RequiredNamespace( + methods: ['method1'], + events: ['event1'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesMatchingAvailable1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1'], + methods: ['method3', 'method4'], + events: ['event3', 'event4'], + ), +}; + +final Map requiredNamespacesNonconformingAccounts1 = + { + 'namespace3': const RequiredNamespace( + chains: ['namespace3:chain1'], + methods: [], + events: [], + ), +}; + +final Map requiredNamespacesNonconformingMethods1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2', 'method3'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingMethods2 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2', 'method3'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3', 'method4'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingEvents1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2', 'event3'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingEvents2 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2', 'event3'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3', 'event4'], + ), +}; + +Map optionalNamespaces = { + 'namespace4:chain1': const RequiredNamespace( + methods: ['method5'], + events: ['event5', 'event2'], + ), +}; + +const sepolia = 'eip155:11155111'; + +final Set availableAccounts3 = { + '$sepolia:0x99999999999999999999999999', +}; + +final Set availableMethods3 = { + '$sepolia:eth_sendTransaction', + '$sepolia:personal_sign', + '$sepolia:eth_signTypedData', + '$sepolia:eth_signTypedData_v4', + '$sepolia:eth_sign', +}; + +final Set availableEvents3 = { + '$sepolia:chainChanged', + '$sepolia:accountsChanged', +}; + +final Map requiredNamespacesInAvailable3 = { + 'eip155': const RequiredNamespace( + chains: [sepolia], + methods: ['eth_sendTransaction', 'personal_sign'], + events: ['chainChanged', 'accountsChanged'], + ), +}; + +final Map optionalNamespacesInAvailable3 = { + 'eip155': const RequiredNamespace(chains: [ + 'eip155:1', + 'eip155:5', + sepolia, + 'eip155:137', + 'eip155:80001', + 'eip155:42220', + 'eip155:44787', + 'eip155:56', + 'eip155:43114', + 'eip155:42161', + 'eip155:421613', + 'eip155:10', + 'eip155:420', + 'eip155:8453' + ], methods: [ + 'eth_sendTransaction', + 'personal_sign', + 'eth_signTypedData', + 'eth_signTypedData_v4', + 'eth_sign' + ], events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ]), +}; diff --git a/packages/reown_appkit/test/shared/sign_client_constants.dart b/packages/reown_appkit/test/shared/sign_client_constants.dart new file mode 100644 index 0000000..6a7e658 --- /dev/null +++ b/packages/reown_appkit/test/shared/sign_client_constants.dart @@ -0,0 +1,227 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; + +const TEST_RELAY_OPTIONS = { + 'protocol': ReownConstants.RELAYER_DEFAULT_PROTOCOL, +}; + +const EVM_NAMESPACE = 'eip155'; + +const TEST_ARBITRUM_CHAIN = 'eip155:42161'; +const TEST_AVALANCHE_CHAIN = 'eip155:43114'; +const TEST_UNINCLUDED_CHAIN = 'eip155:2'; + +const TEST_CHAINS = [ + TEST_ETHEREUM_CHAIN, + TEST_ARBITRUM_CHAIN, +]; +const TEST_CHAIN_INVALID_1 = 'swag'; +const TEST_CHAIN_INVALID_2 = 's:w:a'; +const TEST_CHAINS_INVALID = [ + TEST_CHAIN_INVALID_1, + TEST_CHAIN_INVALID_2, +]; + +const TEST_ETHEREUM_ADDRESS = '0x3c582121909DE92Dc89A36898633C1aE4790382b'; + +const TEST_ETHEREUM_ACCOUNT = '$TEST_ETHEREUM_CHAIN:$TEST_ETHEREUM_ADDRESS'; +const TEST_ARBITRUM_ACCOUNT = '$TEST_ARBITRUM_CHAIN:$TEST_ETHEREUM_ADDRESS'; +const TEST_AVALANCHE_ACCOUNT = '$TEST_AVALANCHE_CHAIN:$TEST_ETHEREUM_ADDRESS'; + +const TEST_ACCOUNTS = [ + TEST_ETHEREUM_ACCOUNT, + TEST_ARBITRUM_ACCOUNT, +]; +const TEST_ACCOUNT_INVALID_1 = 'swag'; +const TEST_ACCOUNT_INVALID_2 = 's:w'; +const TEST_ACCOUNT_INVALID_3 = 's:w:a:g'; +const TEST_ACCOUNTS_INVALID = [ + TEST_ACCOUNT_INVALID_1, + TEST_ACCOUNT_INVALID_2, + TEST_ACCOUNT_INVALID_3, +]; + +const TEST_METHOD_1 = 'eth_sendTransaction'; +const TEST_METHOD_2 = 'eth_signTransaction'; +const TEST_METHOD_3 = 'personal_sign'; +const TEST_METHOD_4 = 'eth_signTypedData'; +const TEST_METHODS_1 = [ + TEST_METHOD_1, + TEST_METHOD_2, +]; +const TEST_METHODS_2 = [ + TEST_METHOD_3, + TEST_METHOD_4, +]; +const TEST_METHODS_FULL = [ + ...TEST_METHODS_1, + ...TEST_METHODS_2, +]; +const TEST_METHOD_INVALID_1 = 'eth_invalid'; + +const TEST_EVENT_1 = 'chainChanged'; +const TEST_EVENT_2 = 'accountsChanged'; +const TEST_EVENTS_FULL = [ + TEST_EVENT_1, + TEST_EVENT_2, +]; +const TEST_EVENT_INVALID_1 = 'eth_event_invalid'; + +const TEST_ETH_ARB_REQUIRED_NAMESPACE = RequiredNamespace( + chains: TEST_CHAINS, + methods: TEST_METHODS_1, + events: [TEST_EVENT_1], +); +const TEST_AVA_REQUIRED_NAMESPACE = RequiredNamespace( + methods: TEST_METHODS_2, + events: [TEST_EVENT_2], +); +const TEST_REQUIRED_NAMESPACES = { + EVM_NAMESPACE: TEST_ETH_ARB_REQUIRED_NAMESPACE, + TEST_AVALANCHE_CHAIN: TEST_AVA_REQUIRED_NAMESPACE, +}; + +const TEST_ETH_ARB_NAMESPACE = Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_1, + events: [TEST_EVENT_1], +); +const TEST_AVA_NAMESPACE = Namespace( + accounts: [TEST_AVALANCHE_ACCOUNT], + methods: TEST_METHODS_2, + events: [TEST_EVENT_2], +); +const TEST_NAMESPACES = { + EVM_NAMESPACE: TEST_ETH_ARB_NAMESPACE, + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; + +// Invalid RequiredNamespaces +const TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1 = { + 'eip155:2': TEST_ETH_ARB_REQUIRED_NAMESPACE, +}; +const TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_2 = { + EVM_NAMESPACE: RequiredNamespace( + chains: ['eip155:1', TEST_CHAIN_INVALID_1], + methods: [], + events: [], + ), +}; + +// Invalid Namespaces +const TEST_NAMESPACES_INVALID_ACCOUNTS = { + EVM_NAMESPACE: Namespace( + accounts: [TEST_ACCOUNT_INVALID_1], + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), +}; + +// Invalid Conforming Namespaces +const TEST_NAMESPACES_NONCONFORMING_KEY_VALUE = 'eip1111'; +const TEST_NAMESPACES_NONCONFORMING_KEY_1 = { + TEST_NAMESPACES_NONCONFORMING_KEY_VALUE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ) +}; +const TEST_NAMESPACES_NONCONFORMING_KEY_2 = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), +}; +const TEST_NAMESPACES_NONCONFORMING_CHAINS = { + EVM_NAMESPACE: Namespace( + accounts: [TEST_ETHEREUM_ACCOUNT], + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; +const TEST_NAMESPACES_NONCONFORMING_METHODS = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: [TEST_METHOD_INVALID_1], + events: TEST_EVENTS_FULL, + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; +const TEST_NAMESPACES_NONCONFORMING_EVENTS = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: [TEST_EVENT_INVALID_1], + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; + +// Session Data +const TEST_SESSION_INVALID_TOPIC = 'swagmaster'; +const TEST_SESSION_VALID_TOPIC = 'abc'; +const TEST_SESSION_EXPIRED_TOPIC = 'expired'; +final testSessionValid = SessionData( + topic: TEST_SESSION_VALID_TOPIC, + pairingTopic: TEST_PAIRING_TOPIC, + relay: Relay('irn'), + expiry: 1000000000000, + acknowledged: true, + controller: 'test', + namespaces: TEST_NAMESPACES, + requiredNamespaces: { + EVM_NAMESPACE: TEST_ETH_ARB_REQUIRED_NAMESPACE, + }, + optionalNamespaces: {}, + self: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), + peer: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), +); +final testSessionExpired = SessionData( + topic: TEST_SESSION_EXPIRED_TOPIC, + pairingTopic: TEST_PAIRING_TOPIC, + relay: Relay('irn'), + expiry: -1, + acknowledged: true, + controller: 'test', + namespaces: TEST_NAMESPACES, + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + optionalNamespaces: {}, + self: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), + peer: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), +); + +// Test Messages + +const TEST_MESSAGE_1 = {'test': 'Hello'}; +const TEST_MESSAGE_2 = 'Hello'; + +const TEST_MESSAGE = 'My name is John Doe'; +const TEST_SIGNATURE = + '0xc8906b32c9f74d0805226ffff5ecd6897ea55cdf58f54a53a2e5b5d5a21fb67f43ef1d4c2ed790a724a1549b4cc40137403048c4aed9825cfd5ba6c1d15bd0721c'; + +const TEST_SIGN_METHOD = 'personal_sign'; +const TEST_SIGN_PARAMS = [ + TEST_MESSAGE, + TEST_ETHEREUM_ADDRESS, +]; +const TEST_SIGN_REQUEST = { + 'method': TEST_SIGN_METHOD, + 'params': TEST_SIGN_PARAMS +}; + +const TEST_RANDOM_REQUEST = {'method': 'random_method', 'params': []}; diff --git a/packages/reown_core/.gitignore b/packages/reown_core/.gitignore new file mode 100644 index 0000000..474a12e --- /dev/null +++ b/packages/reown_core/.gitignore @@ -0,0 +1,58 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/packages/reown_core/.metadata b/packages/reown_core/.metadata new file mode 100644 index 0000000..bc3f0ae --- /dev/null +++ b/packages/reown_core/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "300451adae589accbece3490f4396f10bdf15e6e" + channel: "stable" + +project_type: package diff --git a/packages/reown_core/CHANGELOG.md b/packages/reown_core/CHANGELOG.md new file mode 100644 index 0000000..69b4a02 --- /dev/null +++ b/packages/reown_core/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +Initial release. diff --git a/packages/reown_core/LICENSE b/packages/reown_core/LICENSE new file mode 100644 index 0000000..212a53d --- /dev/null +++ b/packages/reown_core/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Reown, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/reown_core/README.md b/packages/reown_core/README.md new file mode 100644 index 0000000..11a6834 --- /dev/null +++ b/packages/reown_core/README.md @@ -0,0 +1 @@ +# **Reown - Core SDK Flutter** diff --git a/packages/reown_core/analysis_options.yaml b/packages/reown_core/analysis_options.yaml new file mode 100644 index 0000000..b677fc6 --- /dev/null +++ b/packages/reown_core/analysis_options.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:lints/recommended.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + non_constant_identifier_names: false + constant_identifier_names: false + avoid_print: true + prefer_single_quotes: true + sort_pub_dependencies: true + avoid_unnecessary_containers: true + cancel_subscriptions: true + +analyzer: + exclude: + - '**.freezed.dart' + - '**.g.dart' + - '**/*.freezed.dart' + - '**/*.g.dart' + - '**/generated_plugin_registrant.dart' + errors: + invalid_annotation_target: ignore +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reown_core/build.yaml b/packages/reown_core/build.yaml new file mode 100644 index 0000000..71e3c33 --- /dev/null +++ b/packages/reown_core/build.yaml @@ -0,0 +1,16 @@ +targets: + $default: + builders: + build_version: + options: + output: lib/version.dart + freezed: + generate_for: + - lib/**.dart + - test/shared/shared_test_utils.dart + json_serializable: + options: + explicit_to_json: true + generate_for: + - lib/**.dart + - test/shared/shared_test_utils.dart diff --git a/packages/reown_core/lib/connectivity/connectivity.dart b/packages/reown_core/lib/connectivity/connectivity.dart new file mode 100644 index 0000000..1cef8df --- /dev/null +++ b/packages/reown_core/lib/connectivity/connectivity.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:reown_core/connectivity/i_connectivity.dart'; +import 'package:reown_core/i_core_impl.dart'; + +class ConnectivityState implements IConnectivity { + final IReownCore _core; + ConnectivityState({required IReownCore core}) : _core = core; + + bool _initialized = false; + + @override + final isOnline = ValueNotifier(false); + + @override + Future init() async { + if (_initialized) return; + final result = await Connectivity().checkConnectivity(); + _updateConnectionStatus(result); + Connectivity().onConnectivityChanged.listen( + _updateConnectionStatus, + ); + _initialized = true; + } + + Future _updateConnectionStatus(List result) async { + final isMobileData = result.contains(ConnectivityResult.mobile); + final isWifi = result.contains(ConnectivityResult.wifi); + final isOnlineStatus = isMobileData || isWifi; + + if (isOnline.value != isOnlineStatus) { + _core.logger.i('[$runtimeType] Connectivity changed $result'); + isOnline.value = isOnlineStatus; + + if (isOnline.value && !_core.relayClient.isConnected) { + await _core.relayClient.connect(); + } else if (!isOnline.value && _core.relayClient.isConnected) { + await _core.relayClient.disconnect(); + } + } + } +} diff --git a/packages/reown_core/lib/connectivity/connectivity_models.dart b/packages/reown_core/lib/connectivity/connectivity_models.dart new file mode 100644 index 0000000..a828509 --- /dev/null +++ b/packages/reown_core/lib/connectivity/connectivity_models.dart @@ -0,0 +1,9 @@ +import 'package:event/event.dart'; + +class ConnectivityEvent extends EventArgs { + final bool connected; + + ConnectivityEvent( + this.connected, + ); +} diff --git a/packages/reown_core/lib/connectivity/i_connectivity.dart b/packages/reown_core/lib/connectivity/i_connectivity.dart new file mode 100644 index 0000000..015e9df --- /dev/null +++ b/packages/reown_core/lib/connectivity/i_connectivity.dart @@ -0,0 +1,6 @@ +import 'package:flutter/foundation.dart'; + +abstract class IConnectivity { + ValueNotifier get isOnline; + Future init(); +} diff --git a/packages/reown_core/lib/core_impl.dart b/packages/reown_core/lib/core_impl.dart new file mode 100644 index 0000000..3aa699a --- /dev/null +++ b/packages/reown_core/lib/core_impl.dart @@ -0,0 +1,221 @@ +import 'package:logger/logger.dart'; +import 'package:reown_core/connectivity/connectivity.dart'; +import 'package:reown_core/connectivity/i_connectivity.dart'; +import 'package:reown_core/crypto/crypto.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/echo/echo.dart'; +import 'package:reown_core/echo/echo_client.dart'; +import 'package:reown_core/echo/i_echo.dart'; +import 'package:reown_core/heartbit/heartbeat.dart'; +import 'package:reown_core/heartbit/i_heartbeat.dart'; +import 'package:reown_core/i_core_impl.dart'; +import 'package:reown_core/pairing/expirer.dart'; +import 'package:reown_core/pairing/i_expirer.dart'; +import 'package:reown_core/pairing/json_rpc_history.dart'; +import 'package:reown_core/pairing/pairing.dart'; +import 'package:reown_core/pairing/pairing_store.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/relay_client/message_tracker.dart'; +import 'package:reown_core/relay_client/relay_client.dart'; +import 'package:reown_core/relay_client/websocket/http_client.dart'; +import 'package:reown_core/relay_client/websocket/i_http_client.dart'; +import 'package:reown_core/relay_client/websocket/i_websocket_handler.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_store.dart'; +import 'package:reown_core/relay_client/i_relay_client.dart'; +import 'package:reown_core/pairing/i_pairing.dart'; +import 'package:reown_core/store/shared_prefs_store.dart'; +import 'package:reown_core/store/link_mode_store.dart'; +import 'package:reown_core/utils/constants.dart'; +import 'package:reown_core/verify/i_verify.dart'; +import 'package:reown_core/verify/verify.dart'; +import 'package:reown_core/utils/log_level.dart'; +import 'package:reown_core/utils/utils.dart'; +import 'package:reown_core/models/basic_models.dart'; + +class ReownCore implements IReownCore { + @override + String get protocol => 'wc'; + @override + String get version => '2'; + + @override + final String projectId; + + @override + String relayUrl = ReownConstants.DEFAULT_RELAY_URL; + + @override + String pushUrl = ReownConstants.DEFAULT_PUSH_URL; + + @override + late ICrypto crypto; + + @override + late IRelayClient relayClient; + + @override + late IExpirer expirer; + + @override + late IPairing pairing; + + @override + late IEcho echo; + + @override + late IHeartBeat heartbeat; + + @override + late IVerify verify; + + @override + late IConnectivity connectivity; + + @override + late ILinkModeStore linkModeStore; + + Logger _logger = Logger( + level: Level.off, + printer: PrettyPrinter(), + ); + @override + Logger get logger => _logger; + + @override + void addLogListener(LogCallback callback) { + Logger.addLogListener(callback); + } + + @override + bool removeLogListener(LogCallback callback) { + return Logger.removeLogListener(callback); + } + + @override + late IStore> storage; + + ReownCore({ + required this.projectId, + this.relayUrl = ReownConstants.DEFAULT_RELAY_URL, + this.pushUrl = ReownConstants.DEFAULT_PUSH_URL, + bool memoryStore = false, + LogLevel logLevel = LogLevel.nothing, + IHttpClient httpClient = const HttpWrapper(), + IWebSocketHandler? webSocketHandler, + }) { + _logger = Logger( + level: logLevel.toLevel(), + printer: PrettyPrinter(methodCount: null), + ); + heartbeat = HeartBeat(); + storage = SharedPrefsStores( + memoryStore: memoryStore, + ); + crypto = Crypto( + core: this, + keyChain: GenericStore( + storage: storage, + context: StoreVersions.CONTEXT_KEYCHAIN, + version: StoreVersions.VERSION_KEYCHAIN, + fromJson: (dynamic value) => value as String, + ), + ); + relayClient = RelayClient( + core: this, + messageTracker: MessageTracker( + storage: storage, + context: StoreVersions.CONTEXT_MESSAGE_TRACKER, + version: StoreVersions.VERSION_MESSAGE_TRACKER, + fromJson: (dynamic value) { + return ReownCoreUtils.convertMapTo(value); + }, + ), + topicMap: GenericStore( + storage: storage, + context: StoreVersions.CONTEXT_TOPIC_MAP, + version: StoreVersions.VERSION_TOPIC_MAP, + fromJson: (dynamic value) => value as String, + ), + socketHandler: webSocketHandler, + ); + expirer = Expirer( + storage: storage, + context: StoreVersions.CONTEXT_EXPIRER, + version: StoreVersions.VERSION_EXPIRER, + fromJson: (dynamic value) => value as int, + ); + pairing = Pairing( + core: this, + pairings: PairingStore( + storage: storage, + context: StoreVersions.CONTEXT_PAIRINGS, + version: StoreVersions.VERSION_PAIRINGS, + fromJson: (dynamic value) { + return PairingInfo.fromJson(value as Map); + }, + ), + history: JsonRpcHistory( + storage: storage, + context: StoreVersions.CONTEXT_JSON_RPC_HISTORY, + version: StoreVersions.VERSION_JSON_RPC_HISTORY, + fromJson: (dynamic value) => JsonRpcRecord.fromJson(value), + ), + topicToReceiverPublicKey: GenericStore( + storage: storage, + context: StoreVersions.CONTEXT_TOPIC_TO_RECEIVER_PUBLIC_KEY, + version: StoreVersions.VERSION_TOPIC_TO_RECEIVER_PUBLIC_KEY, + fromJson: (dynamic value) => ReceiverPublicKey.fromJson(value), + ), + ); + echo = Echo( + core: this, + echoClient: EchoClient( + baseUrl: pushUrl, + httpClient: httpClient, + ), + ); + verify = Verify( + core: this, + httpClient: httpClient, + ); + connectivity = ConnectivityState( + core: this, + ); + linkModeStore = LinkModeStore( + storage: storage, + context: StoreVersions.CONTEXT_LINKMODE, + version: StoreVersions.VERSION_LINKMODE, + fromJson: (dynamic value) => value as List, + ); + } + + @override + Future start() async { + await storage.init(); + await crypto.init(); + await relayClient.init(); + await expirer.init(); + await pairing.init(); + await connectivity.init(); + await linkModeStore.init(); + heartbeat.init(); + } + + @override + Future addLinkModeSupportedApp(String universalLink) async { + return await linkModeStore.update(universalLink); + } + + @override + List getLinkModeSupportedApps() { + return linkModeStore.getList(); + } + + @override + void confirmOnlineStateOrThrow() { + if (!connectivity.isOnline.value) { + throw ReownCoreError(code: -1, message: 'No internet connection'); + } + } +} diff --git a/packages/reown_core/lib/crypto/crypto.dart b/packages/reown_core/lib/crypto/crypto.dart new file mode 100644 index 0000000..4a93bfe --- /dev/null +++ b/packages/reown_core/lib/crypto/crypto.dart @@ -0,0 +1,277 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:reown_core/crypto/crypto_models.dart'; +import 'package:reown_core/crypto/crypto_utils.dart'; +import 'package:reown_core/i_core_impl.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/crypto/i_crypto_utils.dart'; +import 'package:reown_core/relay_auth/i_relay_auth.dart'; +import 'package:reown_core/relay_auth/relay_auth.dart'; +import 'package:reown_core/relay_auth/relay_auth_models.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_core/utils/constants.dart'; +import 'package:reown_core/utils/errors.dart'; + +class Crypto implements ICrypto { + static const cryptoContext = 'crypto'; + static const cryptoClientSeed = 'client_ed25519_seed'; + static const clientSeed = 'CLIENT_SEED'; + + bool _initialized = false; + + @override + String get name => cryptoContext; + + final IReownCore core; + + @override + IGenericStore keyChain; + + ICryptoUtils utils; + IRelayAuth relayAuth; + + Crypto({ + required this.core, + required this.keyChain, + CryptoUtils? utils, + RelayAuth? relayAuth, + }) : utils = utils ?? CryptoUtils(), + relayAuth = relayAuth ?? RelayAuth(); + + @override + Future init() async { + if (_initialized) { + return; + } + + await keyChain.init(); + + _initialized = true; + } + + @override + bool hasKeys(String tag) { + _checkInitialized(); + return keyChain.has(tag); + } + + @override + Future getClientId() async { + _checkInitialized(); + + // If we don't have a pub key associated with the seed yet, make one + final Uint8List seed = await _getClientSeed(); + final RelayAuthKeyPair keyPair = await relayAuth.generateKeyPair(seed); + return relayAuth.encodeIss(keyPair.publicKeyBytes); + } + + @override + Future generateKeyPair() async { + _checkInitialized(); + + CryptoKeyPair keyPair = utils.generateKeyPair(); + return await _setPrivateKey(keyPair); + } + + @override + Future generateSharedKey( + String selfPublicKey, + String peerPublicKey, { + String? overrideTopic, + }) async { + _checkInitialized(); + + String privKey = _getPrivateKey(selfPublicKey)!; + String symKey = await utils.deriveSymKey(privKey, peerPublicKey); + return await setSymKey(symKey, overrideTopic: overrideTopic); + } + + @override + Future setSymKey( + String symKey, { + String? overrideTopic, + }) async { + _checkInitialized(); + + final String topic = overrideTopic ?? utils.hashKey(symKey); + // print('crypto setSymKey, symKey: $symKey, overrideTopic: $topic'); + await keyChain.set(topic, symKey); + return topic; + } + + @override + Future deleteKeyPair(String publicKey) async { + _checkInitialized(); + await keyChain.delete(publicKey); + } + + @override + Future deleteSymKey(String topic) async { + _checkInitialized(); + await keyChain.delete(topic); + } + + @override + Future encode( + String topic, + Map payload, { + EncodeOptions? options, + }) async { + _checkInitialized(); + + EncodingValidation params; + if (options == null) { + params = utils.validateEncoding(); + } else { + params = utils.validateEncoding( + type: options.type, + senderPublicKey: options.senderPublicKey, + receiverPublicKey: options.receiverPublicKey, + ); + } + + final String message = jsonEncode(payload); + + if (utils.isTypeTwoEnvelope(params)) { + return utils.encodeTypeTwoEnvelope(message: message); + } + + if (utils.isTypeOneEnvelope(params)) { + final String selfPublicKey = params.senderPublicKey!; + final String peerPublicKey = params.receiverPublicKey!; + topic = await generateSharedKey(selfPublicKey, peerPublicKey); + } + + final String? symKey = _getSymKey(topic); + if (symKey == null) { + return null; + } + + final String result = await utils.encrypt( + message, + symKey, + type: params.type, + senderPublicKey: params.senderPublicKey, + ); + + return result; + } + + @override + Future decode( + String topic, + String encoded, { + DecodeOptions? options, + }) async { + _checkInitialized(); + + final EncodingValidation params = utils.validateDecoding( + encoded, + receiverPublicKey: options?.receiverPublicKey, + ); + + if (utils.isTypeTwoEnvelope(params)) { + return utils.decodeTypeTwoEnvelope(message: encoded); + } + + if (utils.isTypeOneEnvelope(params)) { + final String selfPublicKey = params.receiverPublicKey!; + final String peerPublicKey = params.senderPublicKey!; + topic = await generateSharedKey(selfPublicKey, peerPublicKey); + } + final String? symKey = _getSymKey(topic); + if (symKey == null) { + return null; + } + + final String message = await utils.decrypt(symKey, encoded); + + return message; + } + + @override + Future signJWT(String aud) async { + _checkInitialized(); + + final Uint8List seed = await _getClientSeed(); + final RelayAuthKeyPair keyPair = await relayAuth.generateKeyPair(seed); + final String sub = utils.generateRandomBytes32(); + final jwt = await relayAuth.signJWT( + sub: sub, + aud: aud, + ttl: ReownConstants.ONE_DAY, + keyPair: keyPair, + ); + return jwt; + } + + @override + int getPayloadType(String encoded) { + _checkInitialized(); + + return utils.deserialize(encoded).type; + } + + @override + String? getPayloadSenderPublicKey(String encoded) { + _checkInitialized(); + + final deserialized = utils.deserialize(encoded); + if (deserialized.senderPublicKey != null) { + return utf8.decode(deserialized.senderPublicKey!); + } + return null; + } + + // PRIVATE FUNCTIONS + + Future _setPrivateKey(CryptoKeyPair keyPair) async { + await keyChain.set(keyPair.publicKey, keyPair.privateKey); + return keyPair.publicKey; + } + + String? _getPrivateKey(String publicKey) { + return keyChain.get(publicKey); + } + + String? _getSymKey(String topic) { + // print('crypto getSymKey: $topic'); + return keyChain.get(topic); + } + + // Future _getClientKeyFromSeed() async { + // // Get the seed + // String seed = await _getClientSeed(); + + // String pubKey = keyChain.get(seed); + // if (pubKey == '') { + // pubKey = await generateKeyPair(); + // await keyChain.set(seed, pubKey); + // } + + // return pubKey; + // } + + Future _getClientSeed() async { + String? seed = keyChain.get(clientSeed); + if (seed == null) { + seed = utils.generateRandomBytes32(); + await keyChain.set(clientSeed, seed); + } + + return Uint8List.fromList(hex.decode(seed)); + } + + void _checkInitialized() { + if (!_initialized) { + throw Errors.getInternalError(Errors.NOT_INITIALIZED); + } + } + + @override + ICryptoUtils getUtils() { + return utils; + } +} diff --git a/packages/reown_core/lib/crypto/crypto_models.dart b/packages/reown_core/lib/crypto/crypto_models.dart new file mode 100644 index 0000000..6da6a70 --- /dev/null +++ b/packages/reown_core/lib/crypto/crypto_models.dart @@ -0,0 +1,86 @@ +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; + +class CryptoKeyPair { + final String privateKey; + final String publicKey; + + const CryptoKeyPair(this.privateKey, this.publicKey); + + Uint8List getPrivateKeyBytes() { + return Uint8List.fromList(hex.decode(privateKey)); + } + + Uint8List getPublicKeyBytes() { + return Uint8List.fromList(hex.decode(publicKey)); + } +} + +class EncryptParams { + String message; + String symKey; + int? type; + String? iv; + String? senderPublicKey; + + EncryptParams( + this.message, + this.symKey, { + this.type, + this.iv, + this.senderPublicKey, + }); +} + +class EncodingParams { + int type; + Uint8List sealed; + Uint8List iv; + Uint8List ivSealed; + Uint8List? senderPublicKey; + + EncodingParams( + this.type, + this.sealed, + this.iv, + this.ivSealed, { + this.senderPublicKey, + }); +} + +class EncodingValidation { + int type; + String? senderPublicKey; + String? receiverPublicKey; + + EncodingValidation( + this.type, { + this.senderPublicKey, + this.receiverPublicKey, + }); +} + +class EncodeOptions { + static const TYPE_0 = 0; + static const TYPE_1 = 1; + static const TYPE_2 = 2; + + int? type; + String? senderPublicKey; + String? receiverPublicKey; + + EncodeOptions({ + this.type, + this.senderPublicKey, + this.receiverPublicKey, + }); +} + +class DecodeOptions { + String? receiverPublicKey; + + DecodeOptions({ + this.receiverPublicKey, + }); +} diff --git a/packages/reown_core/lib/crypto/crypto_utils.dart b/packages/reown_core/lib/crypto/crypto_utils.dart new file mode 100644 index 0000000..1540c60 --- /dev/null +++ b/packages/reown_core/lib/crypto/crypto_utils.dart @@ -0,0 +1,327 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:cryptography/cryptography.dart' as dc; +import 'package:flutter/services.dart'; +import 'package:pointycastle/digests/sha256.dart'; +import 'package:pointycastle/key_derivators/hkdf.dart'; +import 'package:pointycastle/pointycastle.dart' show HkdfParameters; +import 'package:reown_core/crypto/crypto_models.dart'; +import 'package:reown_core/crypto/i_crypto_utils.dart'; +import 'package:reown_core/models/basic_models.dart'; +import 'package:x25519/x25519.dart' as x; + +class CryptoUtils extends ICryptoUtils { + static final _random = Random.secure(); + + static const IV_LENGTH = 12; + static const KEY_LENGTH = 32; + + static const TYPE_LENGTH = 1; + + @override + CryptoKeyPair generateKeyPair() { + final kp = x.generateKeyPair(); + + return CryptoKeyPair( + hex.encode(kp.privateKey), + hex.encode(kp.publicKey), + ); + } + + @override + Uint8List randomBytes(int length) { + final Uint8List random = Uint8List(length); + for (int i = 0; i < length; i++) { + random[i] = _random.nextInt(256); + } + return random; + } + + @override + String generateRandomBytes32() { + return hex.encode(randomBytes(32)); + } + + @override + Future deriveSymKey(String privKeyA, String pubKeyB) async { + final Uint8List sharedKey1 = x.X25519( + hex.decode(privKeyA), + hex.decode(pubKeyB), + ); + + Uint8List out = Uint8List(KEY_LENGTH); + + final HKDFKeyDerivator hkdf = HKDFKeyDerivator(SHA256Digest()); + final HkdfParameters params = HkdfParameters( + sharedKey1, + KEY_LENGTH, + ); + hkdf.init(params); + // final pc.KeyParameter keyParam = hkdf.extract(null, sharedKey1); + hkdf.deriveKey(null, 0, out, 0); + return hex.encode(out); + } + + @override + String hashKey(String key) { + return hex.encode( + SHA256Digest().process( + Uint8List.fromList( + hex.decode(key), + ), + ), + ); + // return hex.encode(Hash.sha256(hex.decode(key))); + } + + @override + String hashMessage(String message) { + return hex.encode( + SHA256Digest().process( + Uint8List.fromList( + utf8.encode(message), + ), + ), + ); + } + + @override + Future encrypt( + String message, + String symKey, { + int? type, + String? iv, + String? senderPublicKey, + }) async { + final int decodedType = type ?? EncodeOptions.TYPE_0; + // print(message); + // print(symKey); + + // Check for type 1 envelope, throw an error if data is invalid + if (decodedType == EncodeOptions.TYPE_1 && senderPublicKey == null) { + throw const ReownCoreError( + code: -1, + message: 'Missing sender public key for type 1 envelope', + ); + } + + // final String senderPublicKey = senderPublicKey != + final Uint8List usedIV = + (iv != null ? hex.decode(iv) : randomBytes(IV_LENGTH)) as Uint8List; + + final chacha = dc.Chacha20.poly1305Aead(); + dc.SecretBox b = await chacha.encrypt( + utf8.encode(message), + secretKey: dc.SecretKey( + hex.decode(symKey), + ), + nonce: usedIV, + ); + + return serialize( + decodedType, + b.concatenation(), + usedIV, + senderPublicKey: senderPublicKey != null + ? hex.decode(senderPublicKey) as Uint8List + : null, + ); + } + + @override + Future decrypt(String symKey, String encoded) async { + final chacha = dc.Chacha20.poly1305Aead(); + final dc.SecretKey secretKey = dc.SecretKey( + hex.decode(symKey), + ); + final EncodingParams encodedData = deserialize(encoded); + final dc.SecretBox b = dc.SecretBox.fromConcatenation( + encodedData.ivSealed, + nonceLength: 12, + macLength: 16, + ); + List data = await chacha.decrypt(b, secretKey: secretKey); + return utf8.decode(data); + } + + @override + String serialize( + int type, + Uint8List sealed, + Uint8List iv, { + Uint8List? senderPublicKey, + }) { + List l = [type]; + + if (type == EncodeOptions.TYPE_2) { + l.addAll(sealed); + return base64Url.encode(l); + } + + if (type == EncodeOptions.TYPE_1) { + if (senderPublicKey == null) { + throw const ReownCoreError( + code: -1, + message: 'Missing sender public key for type 1 envelope', + ); + } + + l.addAll(senderPublicKey); + } + + // l.addAll(iv); + l.addAll(sealed); + + return base64Encode(l); + } + + String _padEncodeIfNeeded(String encoded) { + final padding = encoded.length % 4; + if (padding > 0) { + encoded += '=' * (4 - padding); + } + return encoded; + } + + @override + EncodingParams deserialize(String encoded) { + final Uint8List bytes = base64Decode(_padEncodeIfNeeded(encoded)); + final int type = bytes[0]; + + int index = TYPE_LENGTH; + Uint8List? senderPublicKey; + if (type == EncodeOptions.TYPE_1) { + senderPublicKey = bytes.sublist( + index, + index + KEY_LENGTH, + ); + index += KEY_LENGTH; + } + if (type == EncodeOptions.TYPE_2) { + senderPublicKey = bytes.sublist( + index, + index + KEY_LENGTH, + ); + // index += KEY_LENGTH; + + Uint8List sealed = bytes.sublist(index); + // iv is not used in TYPE_2 envelopes + Uint8List iv = Uint8List(IV_LENGTH) + ..setAll(0, List.generate(IV_LENGTH, (_) => Random().nextInt(256))); + Uint8List ivSealed = bytes.sublist(index); + + return EncodingParams( + type, + sealed, + iv, + ivSealed, + senderPublicKey: senderPublicKey, + ); + } + + Uint8List iv = bytes.sublist(index, index + IV_LENGTH); + Uint8List ivSealed = bytes.sublist(index); + index += IV_LENGTH; + Uint8List sealed = bytes.sublist(index); + + return EncodingParams( + type, + sealed, + iv, + ivSealed, + senderPublicKey: senderPublicKey, + ); + } + + @override + EncodingValidation validateDecoding( + String encoded, { + String? receiverPublicKey, + }) { + final EncodingParams deserialized = deserialize(encoded); + final String? senderPublicKey = deserialized.senderPublicKey != null + ? hex.encode(deserialized.senderPublicKey!) + : null; + return validateEncoding( + type: deserialized.type, + senderPublicKey: senderPublicKey, + receiverPublicKey: receiverPublicKey, + ); + } + + @override + EncodingValidation validateEncoding({ + int? type, + String? senderPublicKey, + String? receiverPublicKey, + }) { + final int t = type ?? EncodeOptions.TYPE_0; + if (t == EncodeOptions.TYPE_1) { + if (senderPublicKey == null) { + throw const ReownCoreError( + code: -1, message: 'Missing sender public key'); + } + if (receiverPublicKey == null) { + throw const ReownCoreError( + code: -1, message: 'Missing receiver public key'); + } + } + return EncodingValidation( + t, + senderPublicKey: senderPublicKey, + receiverPublicKey: receiverPublicKey, + ); + } + + @override + bool isTypeOneEnvelope( + EncodingValidation result, + ) { + return result.type == EncodeOptions.TYPE_1 && + result.senderPublicKey != null && + result.receiverPublicKey != null; + } + + @override + bool isTypeTwoEnvelope(EncodingValidation result) { + return result.type == EncodeOptions.TYPE_2; + } + + @override + Uint8List encodeTypeByte(int type) { + // Convert the integer to its byte representation in base 10 + String typeString = type.toString(); + List byteList = typeString.codeUnits; + + // Convert the List to a Uint8List + return Uint8List.fromList(byteList); + } + + @override + int decodeTypeByte(Uint8List byte) { + // Convert Uint8List to a string using UTF-16 encoding + String typeString = String.fromCharCodes(byte); + + // Convert the string to an integer + return int.parse(typeString); + } + + @override + String encodeTypeTwoEnvelope({required String message}) { + // iv is not used in type 2 envelopes + Uint8List iv = Uint8List(IV_LENGTH) + ..setAll(0, List.generate(IV_LENGTH, (_) => Random().nextInt(256))); + Uint8List sealed = utf8.encode(message); + + return serialize(EncodeOptions.TYPE_2, sealed, iv); + } + + @override + String decodeTypeTwoEnvelope({required String message}) { + final EncodingParams deserialized = deserialize(message); + return utf8.decode(deserialized.sealed); + } +} diff --git a/packages/reown_core/lib/crypto/i_crypto.dart b/packages/reown_core/lib/crypto/i_crypto.dart new file mode 100644 index 0000000..26c07ce --- /dev/null +++ b/packages/reown_core/lib/crypto/i_crypto.dart @@ -0,0 +1,42 @@ +import 'package:reown_core/crypto/crypto_models.dart'; +import 'package:reown_core/crypto/i_crypto_utils.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +abstract class ICrypto { + abstract final String name; + + abstract IGenericStore keyChain; + + Future init(); + + bool hasKeys(String tag); + Future getClientId(); + Future generateKeyPair(); + Future generateSharedKey( + String selfPublicKey, + String peerPublicKey, { + String? overrideTopic, + }); + Future setSymKey( + String symKey, { + String? overrideTopic, + }); + Future deleteKeyPair(String publicKey); + Future deleteSymKey(String topic); + Future encode( + String topic, + Map payload, { + EncodeOptions? options, + }); + Future decode( + String topic, + String encoded, { + DecodeOptions? options, + }); + Future signJWT(String aud); + int getPayloadType(String encoded); + + String? getPayloadSenderPublicKey(String encoded); + + ICryptoUtils getUtils(); +} diff --git a/packages/reown_core/lib/crypto/i_crypto_utils.dart b/packages/reown_core/lib/crypto/i_crypto_utils.dart new file mode 100644 index 0000000..9170888 --- /dev/null +++ b/packages/reown_core/lib/crypto/i_crypto_utils.dart @@ -0,0 +1,48 @@ +import 'dart:typed_data'; + +import 'package:reown_core/crypto/crypto_models.dart'; + +abstract class ICryptoUtils { + CryptoKeyPair generateKeyPair(); + Uint8List randomBytes(int length); + String generateRandomBytes32(); + Future deriveSymKey(String privKeyA, String pubKeyB); + String hashKey(String key); + String hashMessage(String message); + Future encrypt( + String message, + String symKey, { + int? type, + String? iv, + String? senderPublicKey, + }); + Future decrypt(String symKey, String encoded); + String serialize( + int type, + Uint8List sealed, + Uint8List iv, { + Uint8List? senderPublicKey, + }); + EncodingParams deserialize(String encoded); + EncodingValidation validateDecoding( + String encoded, { + String? receiverPublicKey, + }); + EncodingValidation validateEncoding({ + int? type, + String? senderPublicKey, + String? receiverPublicKey, + }); + + bool isTypeOneEnvelope(EncodingValidation result); + + bool isTypeTwoEnvelope(EncodingValidation result); + + String encodeTypeTwoEnvelope({required String message}); + + String decodeTypeTwoEnvelope({required String message}); + + Uint8List encodeTypeByte(int type); + + int decodeTypeByte(Uint8List byte); +} diff --git a/packages/reown_core/lib/echo/echo.dart b/packages/reown_core/lib/echo/echo.dart new file mode 100644 index 0000000..f87f550 --- /dev/null +++ b/packages/reown_core/lib/echo/echo.dart @@ -0,0 +1,48 @@ +import 'package:reown_core/echo/i_echo.dart'; +import 'package:reown_core/echo/i_echo_client.dart'; +import 'package:reown_core/i_core_impl.dart'; + +class Echo implements IEcho { + static const SUCCESS_STATUS = 'SUCCESS'; + final IReownCore core; + final IEchoClient echoClient; + + Echo({required this.core, required this.echoClient}); + + @override + Future register(String firebaseAccessToken) async { + final projectId = core.projectId; + final clientId = await core.crypto.getClientId(); + final response = await echoClient.register( + projectId: projectId, + clientId: clientId, + firebaseAccessToken: firebaseAccessToken, + ); + + if (response.status != SUCCESS_STATUS) { + if (response.errors != null && response.errors!.isNotEmpty) { + throw ArgumentError(response.errors!.first.message); + } + + throw Exception('Unknown error'); + } + } + + @override + Future unregister() async { + final projectId = core.projectId; + final clientId = await core.crypto.getClientId(); + final response = await echoClient.unregister( + projectId: projectId, + clientId: clientId, + ); + + if (response.status != SUCCESS_STATUS) { + if (response.errors != null && response.errors!.isNotEmpty) { + throw ArgumentError(response.errors!.first.message); + } + + throw Exception('Unknown error'); + } + } +} diff --git a/packages/reown_core/lib/echo/echo_client.dart b/packages/reown_core/lib/echo/echo_client.dart new file mode 100644 index 0000000..f4649cc --- /dev/null +++ b/packages/reown_core/lib/echo/echo_client.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:reown_core/echo/i_echo_client.dart'; +import 'package:reown_core/echo/models/echo_body.dart'; +import 'package:reown_core/echo/models/echo_response.dart'; +import 'package:reown_core/relay_client/websocket/i_http_client.dart'; + +class EchoClient implements IEchoClient { + static const headers = {'Content-Type': 'application/json'}; + final IHttpClient httpClient; + final String baseUrl; + + EchoClient({required this.baseUrl, required this.httpClient}); + + @override + Future register({ + required String projectId, + required String clientId, + required String firebaseAccessToken, + }) async { + final body = EchoBody(clientId: clientId, token: firebaseAccessToken); + + final url = Uri.parse('$baseUrl/$projectId/clients?auth=$clientId'); + final http.Response response = await httpClient.post( + url, + headers: headers, + body: jsonEncode(body.toJson()), + ); + + final jsonMap = json.decode(response.body); + return EchoResponse.fromJson(jsonMap); + } + + @override + Future unregister({ + required String projectId, + required String clientId, + }) async { + final url = Uri.parse('$baseUrl/$projectId/clients/$clientId'); + final http.Response response = + await httpClient.delete(url, headers: headers); + + final jsonMap = json.decode(response.body); + return EchoResponse.fromJson(jsonMap); + } +} diff --git a/packages/reown_core/lib/echo/i_echo.dart b/packages/reown_core/lib/echo/i_echo.dart new file mode 100644 index 0000000..ab6cfbb --- /dev/null +++ b/packages/reown_core/lib/echo/i_echo.dart @@ -0,0 +1,4 @@ +abstract class IEcho { + Future register(String firebaseAccessToken); + Future unregister(); +} diff --git a/packages/reown_core/lib/echo/i_echo_client.dart b/packages/reown_core/lib/echo/i_echo_client.dart new file mode 100644 index 0000000..3dcda11 --- /dev/null +++ b/packages/reown_core/lib/echo/i_echo_client.dart @@ -0,0 +1,14 @@ +import 'package:reown_core/echo/models/echo_response.dart'; + +abstract class IEchoClient { + Future register({ + required String projectId, + required String clientId, + required String firebaseAccessToken, + }); + + Future unregister({ + required String projectId, + required String clientId, + }); +} diff --git a/packages/reown_core/lib/echo/models/echo_body.dart b/packages/reown_core/lib/echo/models/echo_body.dart new file mode 100644 index 0000000..d8b1ec7 --- /dev/null +++ b/packages/reown_core/lib/echo/models/echo_body.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'echo_body.g.dart'; + +@JsonSerializable(fieldRename: FieldRename.snake) +class EchoBody { + final String clientId; + final String token; + final String type; + + EchoBody({ + required this.clientId, + required this.token, + this.type = 'fcm', + }); + + factory EchoBody.fromJson(Map json) => + _$EchoBodyFromJson(json); + + Map toJson() => _$EchoBodyToJson(this); +} diff --git a/packages/reown_core/lib/echo/models/echo_body.g.dart b/packages/reown_core/lib/echo/models/echo_body.g.dart new file mode 100644 index 0000000..d0e74e7 --- /dev/null +++ b/packages/reown_core/lib/echo/models/echo_body.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'echo_body.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +EchoBody _$EchoBodyFromJson(Map json) => EchoBody( + clientId: json['client_id'] as String, + token: json['token'] as String, + type: json['type'] as String? ?? 'fcm', + ); + +Map _$EchoBodyToJson(EchoBody instance) => { + 'client_id': instance.clientId, + 'token': instance.token, + 'type': instance.type, + }; diff --git a/packages/reown_core/lib/echo/models/echo_response.dart b/packages/reown_core/lib/echo/models/echo_response.dart new file mode 100644 index 0000000..7b8d1c2 --- /dev/null +++ b/packages/reown_core/lib/echo/models/echo_response.dart @@ -0,0 +1,51 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'echo_response.g.dart'; + +@JsonSerializable(fieldRename: FieldRename.snake) +class EchoResponse { + final List? errors; + final List? fields; + final String status; + + EchoResponse({ + this.errors, + this.fields, + required this.status, + }); + + factory EchoResponse.fromJson(Map json) => + _$EchoResponseFromJson(json); + + Map toJson() => _$EchoResponseToJson(this); +} + +@JsonSerializable(fieldRename: FieldRename.snake) +class ResponseError { + final String message; + final String name; + + ResponseError({required this.message, required this.name}); + + factory ResponseError.fromJson(Map json) => + _$ResponseErrorFromJson(json); + + Map toJson() => _$ResponseErrorToJson(this); +} + +@JsonSerializable(fieldRename: FieldRename.snake) +class Field { + final String description; + final String field; + final String location; + + Field({ + required this.description, + required this.field, + required this.location, + }); + + factory Field.fromJson(Map json) => _$FieldFromJson(json); + + Map toJson() => _$FieldToJson(this); +} diff --git a/packages/reown_core/lib/echo/models/echo_response.g.dart b/packages/reown_core/lib/echo/models/echo_response.g.dart new file mode 100644 index 0000000..d5ba169 --- /dev/null +++ b/packages/reown_core/lib/echo/models/echo_response.g.dart @@ -0,0 +1,48 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'echo_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +EchoResponse _$EchoResponseFromJson(Map json) => EchoResponse( + errors: (json['errors'] as List?) + ?.map((e) => ResponseError.fromJson(e as Map)) + .toList(), + fields: (json['fields'] as List?) + ?.map((e) => Field.fromJson(e as Map)) + .toList(), + status: json['status'] as String, + ); + +Map _$EchoResponseToJson(EchoResponse instance) => + { + 'errors': instance.errors?.map((e) => e.toJson()).toList(), + 'fields': instance.fields?.map((e) => e.toJson()).toList(), + 'status': instance.status, + }; + +ResponseError _$ResponseErrorFromJson(Map json) => + ResponseError( + message: json['message'] as String, + name: json['name'] as String, + ); + +Map _$ResponseErrorToJson(ResponseError instance) => + { + 'message': instance.message, + 'name': instance.name, + }; + +Field _$FieldFromJson(Map json) => Field( + description: json['description'] as String, + field: json['field'] as String, + location: json['location'] as String, + ); + +Map _$FieldToJson(Field instance) => { + 'description': instance.description, + 'field': instance.field, + 'location': instance.location, + }; diff --git a/packages/reown_core/lib/heartbit/heartbeat.dart b/packages/reown_core/lib/heartbit/heartbeat.dart new file mode 100644 index 0000000..64e0cb2 --- /dev/null +++ b/packages/reown_core/lib/heartbit/heartbeat.dart @@ -0,0 +1,31 @@ +import 'dart:async'; + +import 'package:event/event.dart'; +import 'package:reown_core/heartbit/i_heartbeat.dart'; + +class HeartBeat implements IHeartBeat { + Timer? _heartbeatTimer; + + @override + int interval; + + HeartBeat({this.interval = 5}); + + @override + void init() { + if (_heartbeatTimer != null) return; + _heartbeatTimer = Timer.periodic( + Duration(seconds: interval), + (timer) => onPulse.broadcast(), + ); + } + + @override + void stop() { + _heartbeatTimer?.cancel(); + _heartbeatTimer = null; + } + + @override + final Event onPulse = Event(); +} diff --git a/packages/reown_core/lib/heartbit/i_heartbeat.dart b/packages/reown_core/lib/heartbit/i_heartbeat.dart new file mode 100644 index 0000000..0f1a5ef --- /dev/null +++ b/packages/reown_core/lib/heartbit/i_heartbeat.dart @@ -0,0 +1,9 @@ +import 'package:event/event.dart'; + +abstract class IHeartBeat { + abstract int interval; + abstract final Event onPulse; + + void init(); + void stop(); +} diff --git a/packages/reown_core/lib/i_core_impl.dart b/packages/reown_core/lib/i_core_impl.dart new file mode 100644 index 0000000..599781d --- /dev/null +++ b/packages/reown_core/lib/i_core_impl.dart @@ -0,0 +1,44 @@ +import 'package:logger/logger.dart'; +import 'package:reown_core/connectivity/i_connectivity.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/echo/i_echo.dart'; +import 'package:reown_core/heartbit/i_heartbeat.dart'; +import 'package:reown_core/pairing/i_expirer.dart'; +import 'package:reown_core/pairing/i_pairing.dart'; +import 'package:reown_core/relay_client/i_relay_client.dart'; +import 'package:reown_core/store/i_store.dart'; +import 'package:reown_core/store/link_mode_store.dart'; +import 'package:reown_core/verify/i_verify.dart'; + +abstract class IReownCore { + final String protocol = 'wc'; + final String version = '2'; + + abstract String relayUrl; + abstract final String projectId; + abstract final String pushUrl; + + abstract IHeartBeat heartbeat; + abstract ICrypto crypto; + abstract IRelayClient relayClient; + abstract IStore> storage; + + abstract IConnectivity connectivity; + abstract IExpirer expirer; + abstract IPairing pairing; + abstract IEcho echo; + abstract final Logger logger; + abstract IVerify verify; + abstract ILinkModeStore linkModeStore; + + Future start(); + + void confirmOnlineStateOrThrow(); + + Future addLinkModeSupportedApp(String universalLink); + + List getLinkModeSupportedApps(); + + void addLogListener(LogCallback callback); + bool removeLogListener(LogCallback callback); +} diff --git a/packages/reown_core/lib/models/basic_models.dart b/packages/reown_core/lib/models/basic_models.dart new file mode 100644 index 0000000..8862123 --- /dev/null +++ b/packages/reown_core/lib/models/basic_models.dart @@ -0,0 +1,21 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'basic_models.g.dart'; +part 'basic_models.freezed.dart'; + +/// ERRORS + +class ReownCoreErrorSilent {} + +@freezed +class ReownCoreError with _$ReownCoreError { + @JsonSerializable(includeIfNull: false) + const factory ReownCoreError({ + required int code, + required String message, + String? data, + }) = _ReownCoreError; + + factory ReownCoreError.fromJson(Map json) => + _$ReownCoreErrorFromJson(json); +} diff --git a/packages/reown_core/lib/models/basic_models.freezed.dart b/packages/reown_core/lib/models/basic_models.freezed.dart new file mode 100644 index 0000000..a7f81b0 --- /dev/null +++ b/packages/reown_core/lib/models/basic_models.freezed.dart @@ -0,0 +1,189 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'basic_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ReownCoreError _$ReownCoreErrorFromJson(Map json) { + return _ReownCoreError.fromJson(json); +} + +/// @nodoc +mixin _$ReownCoreError { + int get code => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + String? get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ReownCoreErrorCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ReownCoreErrorCopyWith<$Res> { + factory $ReownCoreErrorCopyWith( + ReownCoreError value, $Res Function(ReownCoreError) then) = + _$ReownCoreErrorCopyWithImpl<$Res, ReownCoreError>; + @useResult + $Res call({int code, String message, String? data}); +} + +/// @nodoc +class _$ReownCoreErrorCopyWithImpl<$Res, $Val extends ReownCoreError> + implements $ReownCoreErrorCopyWith<$Res> { + _$ReownCoreErrorCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? message = null, + Object? data = freezed, + }) { + return _then(_value.copyWith( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ReownCoreErrorImplCopyWith<$Res> + implements $ReownCoreErrorCopyWith<$Res> { + factory _$$ReownCoreErrorImplCopyWith(_$ReownCoreErrorImpl value, + $Res Function(_$ReownCoreErrorImpl) then) = + __$$ReownCoreErrorImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int code, String message, String? data}); +} + +/// @nodoc +class __$$ReownCoreErrorImplCopyWithImpl<$Res> + extends _$ReownCoreErrorCopyWithImpl<$Res, _$ReownCoreErrorImpl> + implements _$$ReownCoreErrorImplCopyWith<$Res> { + __$$ReownCoreErrorImplCopyWithImpl( + _$ReownCoreErrorImpl _value, $Res Function(_$ReownCoreErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? message = null, + Object? data = freezed, + }) { + return _then(_$ReownCoreErrorImpl( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$ReownCoreErrorImpl implements _ReownCoreError { + const _$ReownCoreErrorImpl( + {required this.code, required this.message, this.data}); + + factory _$ReownCoreErrorImpl.fromJson(Map json) => + _$$ReownCoreErrorImplFromJson(json); + + @override + final int code; + @override + final String message; + @override + final String? data; + + @override + String toString() { + return 'ReownCoreError(code: $code, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ReownCoreErrorImpl && + (identical(other.code, code) || other.code == code) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, code, message, data); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ReownCoreErrorImplCopyWith<_$ReownCoreErrorImpl> get copyWith => + __$$ReownCoreErrorImplCopyWithImpl<_$ReownCoreErrorImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ReownCoreErrorImplToJson( + this, + ); + } +} + +abstract class _ReownCoreError implements ReownCoreError { + const factory _ReownCoreError( + {required final int code, + required final String message, + final String? data}) = _$ReownCoreErrorImpl; + + factory _ReownCoreError.fromJson(Map json) = + _$ReownCoreErrorImpl.fromJson; + + @override + int get code; + @override + String get message; + @override + String? get data; + @override + @JsonKey(ignore: true) + _$$ReownCoreErrorImplCopyWith<_$ReownCoreErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_core/lib/models/basic_models.g.dart b/packages/reown_core/lib/models/basic_models.g.dart new file mode 100644 index 0000000..c6e8f31 --- /dev/null +++ b/packages/reown_core/lib/models/basic_models.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'basic_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ReownCoreErrorImpl _$$ReownCoreErrorImplFromJson(Map json) => + _$ReownCoreErrorImpl( + code: (json['code'] as num).toInt(), + message: json['message'] as String, + data: json['data'] as String?, + ); + +Map _$$ReownCoreErrorImplToJson( + _$ReownCoreErrorImpl instance) { + final val = { + 'code': instance.code, + 'message': instance.message, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('data', instance.data); + return val; +} diff --git a/packages/reown_core/lib/models/json_rpc_models.dart b/packages/reown_core/lib/models/json_rpc_models.dart new file mode 100644 index 0000000..9073249 --- /dev/null +++ b/packages/reown_core/lib/models/json_rpc_models.dart @@ -0,0 +1,65 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'json_rpc_models.g.dart'; +part 'json_rpc_models.freezed.dart'; + +@freezed +class RpcOptions with _$RpcOptions { + // @JsonSerializable() + const factory RpcOptions({ + required int ttl, + required bool prompt, + required int tag, + }) = _RpcOptions; +} + +@freezed +class JsonRpcError with _$JsonRpcError { + @JsonSerializable(includeIfNull: false) + const factory JsonRpcError({ + int? code, + String? message, + }) = _JsonRpcError; + + factory JsonRpcError.serverError(String message) => + JsonRpcError(code: -32000, message: message); + factory JsonRpcError.invalidParams(String message) => + JsonRpcError(code: -32602, message: message); + factory JsonRpcError.invalidRequest(String message) => + JsonRpcError(code: -32600, message: message); + factory JsonRpcError.parseError(String message) => + JsonRpcError(code: -32700, message: message); + factory JsonRpcError.methodNotFound(String message) => + JsonRpcError(code: -32601, message: message); + + factory JsonRpcError.fromJson(Map json) => + _$JsonRpcErrorFromJson(json); +} + +@freezed +class JsonRpcRequest with _$JsonRpcRequest { + @JsonSerializable() + const factory JsonRpcRequest({ + required int id, + @Default('2.0') String jsonrpc, + required String method, + dynamic params, + }) = _JsonRpcRequest; + + factory JsonRpcRequest.fromJson(Map json) => + _$JsonRpcRequestFromJson(json); +} + +@Freezed(genericArgumentFactories: true) +class JsonRpcResponse with _$JsonRpcResponse { + // @JsonSerializable(genericArgumentFactories: true) + const factory JsonRpcResponse({ + required int id, + @Default('2.0') String jsonrpc, + JsonRpcError? error, + T? result, + }) = _JsonRpcResponse; + + factory JsonRpcResponse.fromJson(Map json) => + _$JsonRpcResponseFromJson(json, (object) => object as T); +} diff --git a/packages/reown_core/lib/models/json_rpc_models.freezed.dart b/packages/reown_core/lib/models/json_rpc_models.freezed.dart new file mode 100644 index 0000000..72e659f --- /dev/null +++ b/packages/reown_core/lib/models/json_rpc_models.freezed.dart @@ -0,0 +1,729 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'json_rpc_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$RpcOptions { + int get ttl => throw _privateConstructorUsedError; + bool get prompt => throw _privateConstructorUsedError; + int get tag => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $RpcOptionsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RpcOptionsCopyWith<$Res> { + factory $RpcOptionsCopyWith( + RpcOptions value, $Res Function(RpcOptions) then) = + _$RpcOptionsCopyWithImpl<$Res, RpcOptions>; + @useResult + $Res call({int ttl, bool prompt, int tag}); +} + +/// @nodoc +class _$RpcOptionsCopyWithImpl<$Res, $Val extends RpcOptions> + implements $RpcOptionsCopyWith<$Res> { + _$RpcOptionsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ttl = null, + Object? prompt = null, + Object? tag = null, + }) { + return _then(_value.copyWith( + ttl: null == ttl + ? _value.ttl + : ttl // ignore: cast_nullable_to_non_nullable + as int, + prompt: null == prompt + ? _value.prompt + : prompt // ignore: cast_nullable_to_non_nullable + as bool, + tag: null == tag + ? _value.tag + : tag // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RpcOptionsImplCopyWith<$Res> + implements $RpcOptionsCopyWith<$Res> { + factory _$$RpcOptionsImplCopyWith( + _$RpcOptionsImpl value, $Res Function(_$RpcOptionsImpl) then) = + __$$RpcOptionsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int ttl, bool prompt, int tag}); +} + +/// @nodoc +class __$$RpcOptionsImplCopyWithImpl<$Res> + extends _$RpcOptionsCopyWithImpl<$Res, _$RpcOptionsImpl> + implements _$$RpcOptionsImplCopyWith<$Res> { + __$$RpcOptionsImplCopyWithImpl( + _$RpcOptionsImpl _value, $Res Function(_$RpcOptionsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ttl = null, + Object? prompt = null, + Object? tag = null, + }) { + return _then(_$RpcOptionsImpl( + ttl: null == ttl + ? _value.ttl + : ttl // ignore: cast_nullable_to_non_nullable + as int, + prompt: null == prompt + ? _value.prompt + : prompt // ignore: cast_nullable_to_non_nullable + as bool, + tag: null == tag + ? _value.tag + : tag // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$RpcOptionsImpl implements _RpcOptions { + const _$RpcOptionsImpl( + {required this.ttl, required this.prompt, required this.tag}); + + @override + final int ttl; + @override + final bool prompt; + @override + final int tag; + + @override + String toString() { + return 'RpcOptions(ttl: $ttl, prompt: $prompt, tag: $tag)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RpcOptionsImpl && + (identical(other.ttl, ttl) || other.ttl == ttl) && + (identical(other.prompt, prompt) || other.prompt == prompt) && + (identical(other.tag, tag) || other.tag == tag)); + } + + @override + int get hashCode => Object.hash(runtimeType, ttl, prompt, tag); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RpcOptionsImplCopyWith<_$RpcOptionsImpl> get copyWith => + __$$RpcOptionsImplCopyWithImpl<_$RpcOptionsImpl>(this, _$identity); +} + +abstract class _RpcOptions implements RpcOptions { + const factory _RpcOptions( + {required final int ttl, + required final bool prompt, + required final int tag}) = _$RpcOptionsImpl; + + @override + int get ttl; + @override + bool get prompt; + @override + int get tag; + @override + @JsonKey(ignore: true) + _$$RpcOptionsImplCopyWith<_$RpcOptionsImpl> get copyWith => + throw _privateConstructorUsedError; +} + +JsonRpcError _$JsonRpcErrorFromJson(Map json) { + return _JsonRpcError.fromJson(json); +} + +/// @nodoc +mixin _$JsonRpcError { + int? get code => throw _privateConstructorUsedError; + String? get message => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $JsonRpcErrorCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $JsonRpcErrorCopyWith<$Res> { + factory $JsonRpcErrorCopyWith( + JsonRpcError value, $Res Function(JsonRpcError) then) = + _$JsonRpcErrorCopyWithImpl<$Res, JsonRpcError>; + @useResult + $Res call({int? code, String? message}); +} + +/// @nodoc +class _$JsonRpcErrorCopyWithImpl<$Res, $Val extends JsonRpcError> + implements $JsonRpcErrorCopyWith<$Res> { + _$JsonRpcErrorCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = freezed, + Object? message = freezed, + }) { + return _then(_value.copyWith( + code: freezed == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$JsonRpcErrorImplCopyWith<$Res> + implements $JsonRpcErrorCopyWith<$Res> { + factory _$$JsonRpcErrorImplCopyWith( + _$JsonRpcErrorImpl value, $Res Function(_$JsonRpcErrorImpl) then) = + __$$JsonRpcErrorImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int? code, String? message}); +} + +/// @nodoc +class __$$JsonRpcErrorImplCopyWithImpl<$Res> + extends _$JsonRpcErrorCopyWithImpl<$Res, _$JsonRpcErrorImpl> + implements _$$JsonRpcErrorImplCopyWith<$Res> { + __$$JsonRpcErrorImplCopyWithImpl( + _$JsonRpcErrorImpl _value, $Res Function(_$JsonRpcErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = freezed, + Object? message = freezed, + }) { + return _then(_$JsonRpcErrorImpl( + code: freezed == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$JsonRpcErrorImpl implements _JsonRpcError { + const _$JsonRpcErrorImpl({this.code, this.message}); + + factory _$JsonRpcErrorImpl.fromJson(Map json) => + _$$JsonRpcErrorImplFromJson(json); + + @override + final int? code; + @override + final String? message; + + @override + String toString() { + return 'JsonRpcError(code: $code, message: $message)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$JsonRpcErrorImpl && + (identical(other.code, code) || other.code == code) && + (identical(other.message, message) || other.message == message)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, code, message); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$JsonRpcErrorImplCopyWith<_$JsonRpcErrorImpl> get copyWith => + __$$JsonRpcErrorImplCopyWithImpl<_$JsonRpcErrorImpl>(this, _$identity); + + @override + Map toJson() { + return _$$JsonRpcErrorImplToJson( + this, + ); + } +} + +abstract class _JsonRpcError implements JsonRpcError { + const factory _JsonRpcError({final int? code, final String? message}) = + _$JsonRpcErrorImpl; + + factory _JsonRpcError.fromJson(Map json) = + _$JsonRpcErrorImpl.fromJson; + + @override + int? get code; + @override + String? get message; + @override + @JsonKey(ignore: true) + _$$JsonRpcErrorImplCopyWith<_$JsonRpcErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +JsonRpcRequest _$JsonRpcRequestFromJson(Map json) { + return _JsonRpcRequest.fromJson(json); +} + +/// @nodoc +mixin _$JsonRpcRequest { + int get id => throw _privateConstructorUsedError; + String get jsonrpc => throw _privateConstructorUsedError; + String get method => throw _privateConstructorUsedError; + dynamic get params => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $JsonRpcRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $JsonRpcRequestCopyWith<$Res> { + factory $JsonRpcRequestCopyWith( + JsonRpcRequest value, $Res Function(JsonRpcRequest) then) = + _$JsonRpcRequestCopyWithImpl<$Res, JsonRpcRequest>; + @useResult + $Res call({int id, String jsonrpc, String method, dynamic params}); +} + +/// @nodoc +class _$JsonRpcRequestCopyWithImpl<$Res, $Val extends JsonRpcRequest> + implements $JsonRpcRequestCopyWith<$Res> { + _$JsonRpcRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? jsonrpc = null, + Object? method = null, + Object? params = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + jsonrpc: null == jsonrpc + ? _value.jsonrpc + : jsonrpc // ignore: cast_nullable_to_non_nullable + as String, + method: null == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as dynamic, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$JsonRpcRequestImplCopyWith<$Res> + implements $JsonRpcRequestCopyWith<$Res> { + factory _$$JsonRpcRequestImplCopyWith(_$JsonRpcRequestImpl value, + $Res Function(_$JsonRpcRequestImpl) then) = + __$$JsonRpcRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id, String jsonrpc, String method, dynamic params}); +} + +/// @nodoc +class __$$JsonRpcRequestImplCopyWithImpl<$Res> + extends _$JsonRpcRequestCopyWithImpl<$Res, _$JsonRpcRequestImpl> + implements _$$JsonRpcRequestImplCopyWith<$Res> { + __$$JsonRpcRequestImplCopyWithImpl( + _$JsonRpcRequestImpl _value, $Res Function(_$JsonRpcRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? jsonrpc = null, + Object? method = null, + Object? params = freezed, + }) { + return _then(_$JsonRpcRequestImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + jsonrpc: null == jsonrpc + ? _value.jsonrpc + : jsonrpc // ignore: cast_nullable_to_non_nullable + as String, + method: null == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as dynamic, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$JsonRpcRequestImpl implements _JsonRpcRequest { + const _$JsonRpcRequestImpl( + {required this.id, + this.jsonrpc = '2.0', + required this.method, + this.params}); + + factory _$JsonRpcRequestImpl.fromJson(Map json) => + _$$JsonRpcRequestImplFromJson(json); + + @override + final int id; + @override + @JsonKey() + final String jsonrpc; + @override + final String method; + @override + final dynamic params; + + @override + String toString() { + return 'JsonRpcRequest(id: $id, jsonrpc: $jsonrpc, method: $method, params: $params)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$JsonRpcRequestImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.jsonrpc, jsonrpc) || other.jsonrpc == jsonrpc) && + (identical(other.method, method) || other.method == method) && + const DeepCollectionEquality().equals(other.params, params)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, jsonrpc, method, + const DeepCollectionEquality().hash(params)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$JsonRpcRequestImplCopyWith<_$JsonRpcRequestImpl> get copyWith => + __$$JsonRpcRequestImplCopyWithImpl<_$JsonRpcRequestImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$JsonRpcRequestImplToJson( + this, + ); + } +} + +abstract class _JsonRpcRequest implements JsonRpcRequest { + const factory _JsonRpcRequest( + {required final int id, + final String jsonrpc, + required final String method, + final dynamic params}) = _$JsonRpcRequestImpl; + + factory _JsonRpcRequest.fromJson(Map json) = + _$JsonRpcRequestImpl.fromJson; + + @override + int get id; + @override + String get jsonrpc; + @override + String get method; + @override + dynamic get params; + @override + @JsonKey(ignore: true) + _$$JsonRpcRequestImplCopyWith<_$JsonRpcRequestImpl> get copyWith => + throw _privateConstructorUsedError; +} + +JsonRpcResponse _$JsonRpcResponseFromJson( + Map json, T Function(Object?) fromJsonT) { + return _JsonRpcResponse.fromJson(json, fromJsonT); +} + +/// @nodoc +mixin _$JsonRpcResponse { + int get id => throw _privateConstructorUsedError; + String get jsonrpc => throw _privateConstructorUsedError; + JsonRpcError? get error => throw _privateConstructorUsedError; + T? get result => throw _privateConstructorUsedError; + + Map toJson(Object? Function(T) toJsonT) => + throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $JsonRpcResponseCopyWith> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $JsonRpcResponseCopyWith { + factory $JsonRpcResponseCopyWith( + JsonRpcResponse value, $Res Function(JsonRpcResponse) then) = + _$JsonRpcResponseCopyWithImpl>; + @useResult + $Res call({int id, String jsonrpc, JsonRpcError? error, T? result}); + + $JsonRpcErrorCopyWith<$Res>? get error; +} + +/// @nodoc +class _$JsonRpcResponseCopyWithImpl> + implements $JsonRpcResponseCopyWith { + _$JsonRpcResponseCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? jsonrpc = null, + Object? error = freezed, + Object? result = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + jsonrpc: null == jsonrpc + ? _value.jsonrpc + : jsonrpc // ignore: cast_nullable_to_non_nullable + as String, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as JsonRpcError?, + result: freezed == result + ? _value.result + : result // ignore: cast_nullable_to_non_nullable + as T?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $JsonRpcErrorCopyWith<$Res>? get error { + if (_value.error == null) { + return null; + } + + return $JsonRpcErrorCopyWith<$Res>(_value.error!, (value) { + return _then(_value.copyWith(error: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$JsonRpcResponseImplCopyWith + implements $JsonRpcResponseCopyWith { + factory _$$JsonRpcResponseImplCopyWith(_$JsonRpcResponseImpl value, + $Res Function(_$JsonRpcResponseImpl) then) = + __$$JsonRpcResponseImplCopyWithImpl; + @override + @useResult + $Res call({int id, String jsonrpc, JsonRpcError? error, T? result}); + + @override + $JsonRpcErrorCopyWith<$Res>? get error; +} + +/// @nodoc +class __$$JsonRpcResponseImplCopyWithImpl + extends _$JsonRpcResponseCopyWithImpl> + implements _$$JsonRpcResponseImplCopyWith { + __$$JsonRpcResponseImplCopyWithImpl(_$JsonRpcResponseImpl _value, + $Res Function(_$JsonRpcResponseImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? jsonrpc = null, + Object? error = freezed, + Object? result = freezed, + }) { + return _then(_$JsonRpcResponseImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + jsonrpc: null == jsonrpc + ? _value.jsonrpc + : jsonrpc // ignore: cast_nullable_to_non_nullable + as String, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as JsonRpcError?, + result: freezed == result + ? _value.result + : result // ignore: cast_nullable_to_non_nullable + as T?, + )); + } +} + +/// @nodoc +@JsonSerializable(genericArgumentFactories: true) +class _$JsonRpcResponseImpl implements _JsonRpcResponse { + const _$JsonRpcResponseImpl( + {required this.id, this.jsonrpc = '2.0', this.error, this.result}); + + factory _$JsonRpcResponseImpl.fromJson( + Map json, T Function(Object?) fromJsonT) => + _$$JsonRpcResponseImplFromJson(json, fromJsonT); + + @override + final int id; + @override + @JsonKey() + final String jsonrpc; + @override + final JsonRpcError? error; + @override + final T? result; + + @override + String toString() { + return 'JsonRpcResponse<$T>(id: $id, jsonrpc: $jsonrpc, error: $error, result: $result)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$JsonRpcResponseImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.jsonrpc, jsonrpc) || other.jsonrpc == jsonrpc) && + (identical(other.error, error) || other.error == error) && + const DeepCollectionEquality().equals(other.result, result)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, jsonrpc, error, + const DeepCollectionEquality().hash(result)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$JsonRpcResponseImplCopyWith> get copyWith => + __$$JsonRpcResponseImplCopyWithImpl>( + this, _$identity); + + @override + Map toJson(Object? Function(T) toJsonT) { + return _$$JsonRpcResponseImplToJson(this, toJsonT); + } +} + +abstract class _JsonRpcResponse implements JsonRpcResponse { + const factory _JsonRpcResponse( + {required final int id, + final String jsonrpc, + final JsonRpcError? error, + final T? result}) = _$JsonRpcResponseImpl; + + factory _JsonRpcResponse.fromJson( + Map json, T Function(Object?) fromJsonT) = + _$JsonRpcResponseImpl.fromJson; + + @override + int get id; + @override + String get jsonrpc; + @override + JsonRpcError? get error; + @override + T? get result; + @override + @JsonKey(ignore: true) + _$$JsonRpcResponseImplCopyWith> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_core/lib/models/json_rpc_models.g.dart b/packages/reown_core/lib/models/json_rpc_models.g.dart new file mode 100644 index 0000000..f1aac88 --- /dev/null +++ b/packages/reown_core/lib/models/json_rpc_models.g.dart @@ -0,0 +1,80 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'json_rpc_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$JsonRpcErrorImpl _$$JsonRpcErrorImplFromJson(Map json) => + _$JsonRpcErrorImpl( + code: (json['code'] as num?)?.toInt(), + message: json['message'] as String?, + ); + +Map _$$JsonRpcErrorImplToJson(_$JsonRpcErrorImpl instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('code', instance.code); + writeNotNull('message', instance.message); + return val; +} + +_$JsonRpcRequestImpl _$$JsonRpcRequestImplFromJson(Map json) => + _$JsonRpcRequestImpl( + id: (json['id'] as num).toInt(), + jsonrpc: json['jsonrpc'] as String? ?? '2.0', + method: json['method'] as String, + params: json['params'], + ); + +Map _$$JsonRpcRequestImplToJson( + _$JsonRpcRequestImpl instance) => + { + 'id': instance.id, + 'jsonrpc': instance.jsonrpc, + 'method': instance.method, + 'params': instance.params, + }; + +_$JsonRpcResponseImpl _$$JsonRpcResponseImplFromJson( + Map json, + T Function(Object? json) fromJsonT, +) => + _$JsonRpcResponseImpl( + id: (json['id'] as num).toInt(), + jsonrpc: json['jsonrpc'] as String? ?? '2.0', + error: json['error'] == null + ? null + : JsonRpcError.fromJson(json['error'] as Map), + result: _$nullableGenericFromJson(json['result'], fromJsonT), + ); + +Map _$$JsonRpcResponseImplToJson( + _$JsonRpcResponseImpl instance, + Object? Function(T value) toJsonT, +) => + { + 'id': instance.id, + 'jsonrpc': instance.jsonrpc, + 'error': instance.error?.toJson(), + 'result': _$nullableGenericToJson(instance.result, toJsonT), + }; + +T? _$nullableGenericFromJson( + Object? input, + T Function(Object? json) fromJson, +) => + input == null ? null : fromJson(input); + +Object? _$nullableGenericToJson( + T? input, + Object? Function(T value) toJson, +) => + input == null ? null : toJson(input); diff --git a/packages/reown_core/lib/models/uri_parse_result.dart b/packages/reown_core/lib/models/uri_parse_result.dart new file mode 100644 index 0000000..6fe17ca --- /dev/null +++ b/packages/reown_core/lib/models/uri_parse_result.dart @@ -0,0 +1,44 @@ +import 'package:reown_core/relay_client/relay_client_models.dart'; + +enum URIVersion { + v1, + v2, +} + +class URIParseResult { + final String protocol; + final String topic; + final URIVersion? version; + final URIV1ParsedData? v1Data; + final URIV2ParsedData? v2Data; + + URIParseResult({ + required this.protocol, + required this.version, + required this.topic, + this.v1Data, + this.v2Data, + }); +} + +class URIV1ParsedData { + final String key; + final String bridge; + + URIV1ParsedData({ + required this.key, + required this.bridge, + }); +} + +class URIV2ParsedData { + final String symKey; + final Relay relay; + final List methods; + + URIV2ParsedData({ + required this.symKey, + required this.relay, + required this.methods, + }); +} diff --git a/packages/reown_core/lib/pairing/expirer.dart b/packages/reown_core/lib/pairing/expirer.dart new file mode 100644 index 0000000..8c15985 --- /dev/null +++ b/packages/reown_core/lib/pairing/expirer.dart @@ -0,0 +1,64 @@ +import 'package:event/event.dart'; +import 'package:reown_core/pairing/i_expirer.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/utils/utils.dart'; + +class Expirer extends GenericStore implements IExpirer { + @override + final Event onExpire = Event(); + + Expirer({ + required super.storage, + required super.context, + required super.version, + required super.fromJson, + }); + + @override + Future checkExpiry(String key, int expiry) async { + checkInitialized(); + + if (ReownCoreUtils.isExpired(expiry)) { + await expire(key); + return true; + } + return false; + } + + /// Checks if the key has expired and deletes it if it has + /// Returns true if the key was deleted + /// Returns false if the key was not deleted + @override + Future checkAndExpire(String key) async { + checkInitialized(); + + if (data.containsKey(key)) { + int expiration = data[key]!; + if (ReownCoreUtils.isExpired(expiration)) { + await expire(key); + return true; + } + } + + return false; + } + + @override + Future expire(String key) async { + checkInitialized(); + + int? expiry = data.remove(key); + if (expiry == null) { + return; + } + // print('Expiring $key'); + onExpire.broadcast( + ExpirationEvent( + target: key, + expiry: expiry, + ), + ); + await persist(); + } +} diff --git a/packages/reown_core/lib/pairing/i_expirer.dart b/packages/reown_core/lib/pairing/i_expirer.dart new file mode 100644 index 0000000..ba33369 --- /dev/null +++ b/packages/reown_core/lib/pairing/i_expirer.dart @@ -0,0 +1,11 @@ +import 'package:event/event.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +abstract class IExpirer extends IGenericStore { + abstract final Event onExpire; + + Future checkExpiry(String key, int expiry); + Future checkAndExpire(String key); + Future expire(String key); +} diff --git a/packages/reown_core/lib/pairing/i_json_rpc_history.dart b/packages/reown_core/lib/pairing/i_json_rpc_history.dart new file mode 100644 index 0000000..b8cc85c --- /dev/null +++ b/packages/reown_core/lib/pairing/i_json_rpc_history.dart @@ -0,0 +1,6 @@ +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +abstract class IJsonRpcHistory extends IGenericStore { + Future resolve(Map response); +} diff --git a/packages/reown_core/lib/pairing/i_pairing.dart b/packages/reown_core/lib/pairing/i_pairing.dart new file mode 100644 index 0000000..f6c3bfb --- /dev/null +++ b/packages/reown_core/lib/pairing/i_pairing.dart @@ -0,0 +1,89 @@ +import 'package:event/event.dart'; +import 'package:reown_core/crypto/crypto_models.dart'; +import 'package:reown_core/models/json_rpc_models.dart'; + +import 'package:reown_core/pairing/i_pairing_store.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; + +abstract class IPairing { + abstract final Event onPairingCreate; + abstract final Event onPairingActivate; + abstract final Event onPairingPing; + abstract final Event onPairingInvalid; + abstract final Event onPairingDelete; + abstract final Event onPairingExpire; + + Future init(); + Future pair({ + required Uri uri, + bool activatePairing, + }); + Future create({ + List>? methods, + }); + Future activate({required String topic}); + void register({ + required String method, + required Function(String, JsonRpcRequest, [TransportType]) function, + required ProtocolType type, + }); + Future setReceiverPublicKey({ + required String topic, + required String publicKey, + int? expiry, + }); + Future updateExpiry({ + required String topic, + required int expiry, + }); + Future updateMetadata({ + required String topic, + required PairingMetadata metadata, + }); + Future checkAndExpire(); + List getPairings(); + PairingInfo? getPairing({required String topic}); + Future ping({required String topic}); + Future disconnect({required String topic}); + IPairingStore getStore(); + + Future sendRequest( + String topic, + String method, + dynamic params, { + int? id, + int? ttl, + EncodeOptions? encodeOptions, + String? appLink, + bool openUrl = true, + }); + + Future sendResult( + int id, + String topic, + String method, + dynamic result, { + EncodeOptions? encodeOptions, + String? appLink, + }); + + Future sendError( + int id, + String topic, + String method, + JsonRpcError error, { + EncodeOptions? encodeOptions, + RpcOptions? rpcOptions, + String? appLink, + }); + + Future isValidPairingTopic({ + required String topic, + }); + + void dispatchEnvelope({ + required String topic, + required String envelope, + }); +} diff --git a/packages/reown_core/lib/pairing/i_pairing_store.dart b/packages/reown_core/lib/pairing/i_pairing_store.dart new file mode 100644 index 0000000..da51814 --- /dev/null +++ b/packages/reown_core/lib/pairing/i_pairing_store.dart @@ -0,0 +1,11 @@ +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +abstract class IPairingStore extends IGenericStore { + Future update( + String topic, { + int? expiry, + bool? active, + PairingMetadata? metadata, + }); +} diff --git a/packages/reown_core/lib/pairing/json_rpc_history.dart b/packages/reown_core/lib/pairing/json_rpc_history.dart new file mode 100644 index 0000000..098e167 --- /dev/null +++ b/packages/reown_core/lib/pairing/json_rpc_history.dart @@ -0,0 +1,38 @@ +import 'package:reown_core/pairing/i_json_rpc_history.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/store/generic_store.dart'; + +class JsonRpcHistory extends GenericStore + implements IJsonRpcHistory { + JsonRpcHistory({ + required super.storage, + required super.context, + required super.version, + required super.fromJson, + }); + + @override + Future resolve(Map response) async { + checkInitialized(); + + // If we don't have a matching id, stop + String sId = response['id'].toString(); + if (!data.containsKey(sId)) { + return; + } + + JsonRpcRecord record = get(sId)!; + + // If we already recorded a response, stop + if (record.response != null) { + return; + } + + record = record.copyWith( + response: response.containsKey('result') + ? response['result'] + : response['error'], + ); + await set(sId, record); + } +} diff --git a/packages/reown_core/lib/pairing/pairing.dart b/packages/reown_core/lib/pairing/pairing.dart new file mode 100644 index 0000000..c613d22 --- /dev/null +++ b/packages/reown_core/lib/pairing/pairing.dart @@ -0,0 +1,886 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:event/event.dart'; +import 'package:reown_core/models/json_rpc_models.dart'; +import 'package:reown_core/pairing/i_json_rpc_history.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_core/crypto/crypto_models.dart'; +import 'package:reown_core/i_core_impl.dart'; +import 'package:reown_core/pairing/i_pairing.dart'; +import 'package:reown_core/pairing/i_pairing_store.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/pairing/utils/json_rpc_utils.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/utils/utils.dart'; +import 'package:reown_core/models/uri_parse_result.dart'; +import 'package:reown_core/models/basic_models.dart'; +import 'package:reown_core/utils/constants.dart'; +import 'package:reown_core/utils/errors.dart'; +import 'package:reown_core/utils/method_constants.dart'; + +class PendingRequestResponse { + Completer completer; + dynamic response; + JsonRpcError? error; + + PendingRequestResponse({ + required this.completer, + this.response, + this.error, + }); +} + +class Pairing implements IPairing { + bool _initialized = false; + + @override + final Event onPairingCreate = Event(); + @override + final Event onPairingActivate = + Event(); + @override + final Event onPairingPing = Event(); + @override + final Event onPairingInvalid = + Event(); + @override + final Event onPairingDelete = Event(); + @override + final Event onPairingExpire = Event(); + + /// Stores all the pending requests + Map pendingRequests = {}; + + final IReownCore core; + final IPairingStore pairings; + final IJsonRpcHistory history; + + /// Stores the public key of Type 1 Envelopes for a topic + /// Once a receiver public key has been used, it is removed from the store + /// Thus, this store works under the assumption that a public key will only be used once + final IGenericStore topicToReceiverPublicKey; + + Pairing({ + required this.core, + required this.pairings, + required this.history, + required this.topicToReceiverPublicKey, + }); + + @override + Future init() async { + if (_initialized) { + return; + } + + _registerRelayEvents(); + _registerExpirerEvents(); + _registerheartbeatSubscription(); + + await core.expirer.init(); + await pairings.init(); + await history.init(); + await topicToReceiverPublicKey.init(); + + await _cleanup(); + + await _resubscribeAll(); + + _initialized = true; + } + + @override + Future checkAndExpire() async { + for (var pairing in getPairings()) { + await core.expirer.checkAndExpire(pairing.topic); + } + } + + @override + Future create({List>? methods}) async { + _checkInitialized(); + final String symKey = core.crypto.getUtils().generateRandomBytes32(); + final String topic = await core.crypto.setSymKey(symKey); + final int expiry = ReownCoreUtils.calculateExpiry( + ReownConstants.FIVE_MINUTES, + ); + final Relay relay = Relay(ReownConstants.RELAYER_DEFAULT_PROTOCOL); + final PairingInfo pairing = PairingInfo( + topic: topic, + expiry: expiry, + relay: relay, + active: false, + methods: methods?.expand((e) => e).toList() ?? [], + ); + final Uri uri = ReownCoreUtils.formatUri( + protocol: core.protocol, + version: core.version, + topic: topic, + symKey: symKey, + relay: relay, + methods: methods, + expiry: expiry, + ); + + onPairingCreate.broadcast( + PairingEvent( + topic: topic, + ), + ); + + await pairings.set(topic, pairing); + await core.relayClient.subscribe(topic: topic); + await core.expirer.set(topic, expiry); + + return CreateResponse( + topic: topic, + uri: uri, + pairingInfo: pairing, + ); + } + + @override + Future pair({ + required Uri uri, + bool activatePairing = false, + }) async { + _checkInitialized(); + + // print(uri.queryParameters); + final int expiry = ReownCoreUtils.calculateExpiry( + ReownConstants.FIVE_MINUTES, + ); + final URIParseResult parsedUri = ReownCoreUtils.parseUri(uri); + if (parsedUri.version != URIVersion.v2) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'URI is not WalletConnect version 2 URI', + ); + } + + final String topic = parsedUri.topic; + final Relay relay = parsedUri.v2Data!.relay; + final String symKey = parsedUri.v2Data!.symKey; + final PairingInfo pairing = PairingInfo( + topic: topic, + expiry: expiry, + relay: relay, + active: false, + methods: parsedUri.v2Data!.methods, + ); + + try { + JsonRpcUtils.validateMethods( + parsedUri.v2Data!.methods, + routerMapRequest.values.toList(), + ); + } on ReownCoreError catch (e) { + // Tell people that the pairing is invalid + onPairingInvalid.broadcast( + PairingInvalidEvent( + message: e.message, + ), + ); + + // Delete the pairing: "publish internally with reason" + // await _deletePairing( + // topic, + // false, + // ); + + rethrow; + } + + try { + await pairings.set(topic, pairing); + await core.crypto.setSymKey(symKey, overrideTopic: topic); + await core.relayClient.subscribe(topic: topic).timeout( + const Duration(seconds: 15), + ); + await core.expirer.set(topic, expiry); + + onPairingCreate.broadcast( + PairingEvent( + topic: topic, + ), + ); + + if (activatePairing) { + await activate(topic: topic); + } + } catch (e) { + rethrow; + } + + return pairing; + } + + @override + Future activate({required String topic}) async { + _checkInitialized(); + final int expiry = ReownCoreUtils.calculateExpiry( + ReownConstants.THIRTY_DAYS, + ); + // print('Activating pairing with topic: $topic'); + + onPairingActivate.broadcast( + PairingActivateEvent( + topic: topic, + expiry: expiry, + ), + ); + + await pairings.update( + topic, + expiry: expiry, + active: true, + ); + await core.expirer.set(topic, expiry); + } + + @override + void register({ + required String method, + required Function(String, JsonRpcRequest, [TransportType]) function, + required ProtocolType type, + }) { + if (routerMapRequest.containsKey(method)) { + final registered = routerMapRequest[method]; + if (registered!.type == type) { + throw const ReownCoreError( + code: -1, + message: 'Method already exists', + ); + } + } + + routerMapRequest[method] = RegisteredFunction( + method: method, + function: function, + type: type, + ); + } + + @override + Future setReceiverPublicKey({ + required String topic, + required String publicKey, + int? expiry, + }) async { + _checkInitialized(); + await topicToReceiverPublicKey.set( + topic, + ReceiverPublicKey( + topic: topic, + publicKey: publicKey, + expiry: ReownCoreUtils.calculateExpiry( + expiry ?? ReownConstants.FIVE_MINUTES, + ), + ), + ); + } + + @override + Future updateExpiry({ + required String topic, + required int expiry, + }) async { + _checkInitialized(); + + // Validate the expiry is less than 30 days + if (expiry > + ReownCoreUtils.calculateExpiry( + ReownConstants.THIRTY_DAYS, + )) { + throw const ReownCoreError( + code: -1, + message: 'Expiry cannot be more than 30 days away', + ); + } + + await pairings.update( + topic, + expiry: expiry, + ); + await core.expirer.set( + topic, + expiry, + ); + } + + @override + Future updateMetadata({ + required String topic, + required PairingMetadata metadata, + }) async { + _checkInitialized(); + await pairings.update( + topic, + metadata: metadata, + ); + } + + @override + List getPairings() { + return pairings.getAll(); + } + + @override + PairingInfo? getPairing({required String topic}) { + return pairings.get(topic); + } + + @override + Future ping({required String topic}) async { + _checkInitialized(); + + await _isValidPing(topic); + + if (pairings.has(topic)) { + // try { + final bool _ = await sendRequest( + topic, + MethodConstants.WC_PAIRING_PING, + {}, + ); + } + } + + @override + Future disconnect({required String topic}) async { + _checkInitialized(); + + await _isValidDisconnect(topic); + if (pairings.has(topic)) { + // Send the request to delete the pairing, we don't care if it fails + try { + sendRequest( + topic, + MethodConstants.WC_PAIRING_DELETE, + Errors.getSdkError(Errors.USER_DISCONNECTED).toJson(), + ); + } catch (_) {} + + // Delete the pairing + await pairings.delete(topic); + + onPairingDelete.broadcast( + PairingEvent( + topic: topic, + ), + ); + } + } + + @override + IPairingStore getStore() { + return pairings; + } + + @override + Future isValidPairingTopic({required String topic}) async { + if (!pairings.has(topic)) { + throw Errors.getInternalError( + Errors.NO_MATCHING_KEY, + context: "pairing topic doesn't exist: $topic", + ); + } + + if (await core.expirer.checkAndExpire(topic)) { + throw Errors.getInternalError( + Errors.EXPIRED, + context: 'pairing topic: $topic', + ); + } + } + + // RELAY COMMUNICATION HELPERS + + @override + Future sendRequest( + String topic, + String method, + dynamic params, { + int? id, + int? ttl, + EncodeOptions? encodeOptions, + String? appLink, + bool openUrl = true, + }) async { + final payload = JsonRpcUtils.formatJsonRpcRequest( + method, + params, + id: id, + ); + + final message = await core.crypto.encode( + topic, + payload, + options: encodeOptions, + ); + + if (message == null) { + return; + } + + // print('adding payload to pending requests: ${payload['id']}'); + final resp = PendingRequestResponse(completer: Completer()); + resp.completer.future.catchError((err) { + // Catch the error so that it won't throw an uncaught error + }); + pendingRequests[payload['id']] = resp; + + if ((appLink ?? '').isNotEmpty) { + // during wc_sessionAuthenticate we don't need to openURL as it will be done by the host dapp + core.logger.t( + 'pairing sendRequest LinkMode, ' + 'id: $id topic: $topic, method: $method, params: $params, ttl: $ttl', + ); + if (openUrl) { + final redirectURL = ReownCoreUtils.getLinkModeURL( + appLink!, + topic, + message, + ); + await ReownCoreUtils.openURL(redirectURL); + } + } else { + core.logger.t( + 'pairing sendRequest Relay, ' + 'id: $id topic: $topic, method: $method, params: $params, ttl: $ttl', + ); + // TODO CHANGED THIS + // RpcOptions opts = MethodConstants.RPC_OPTS[method]!['req']!; + RpcOptions opts = MethodConstants.RPC_OPTS[method]!['req']!; + if (ttl != null) { + opts = opts.copyWith(ttl: ttl); + } + // + await core.relayClient.publish( + topic: topic, + message: message, + ttl: ttl ?? opts.ttl, + tag: opts.tag, + ); + } + + // Get the result from the completer, if it's an error, throw it + try { + if (resp.error != null) { + throw resp.error!; + } + + // print('checking if completed'); + if (resp.completer.isCompleted) { + return resp.response; + } + + return await resp.completer.future; + } catch (e) { + rethrow; + } + } + + @override + Future sendResult( + int id, + String topic, + String method, + dynamic result, { + EncodeOptions? encodeOptions, + String? appLink, + }) async { + final payload = JsonRpcUtils.formatJsonRpcResponse( + id, + result, + ); + + final String? message = await core.crypto.encode( + topic, + payload, + options: encodeOptions, + ); + + if (message == null) { + return; + } + + if ((appLink ?? '').isNotEmpty) { + final redirectURL = ReownCoreUtils.getLinkModeURL( + appLink!, + topic, + message, + ); + core.logger.t( + 'pairing sendResult LinkMode, ' + 'id: $id topic: $topic, method: $method, result: $result', + ); + await ReownCoreUtils.openURL(redirectURL); + } else { + final RpcOptions opts = MethodConstants.RPC_OPTS[method]!['res']!; + core.logger.t( + 'pairing sendResult Relay, ' + 'id: $id topic: $topic, method: $method, result: $result', + ); + await core.relayClient.publish( + topic: topic, + message: message, + ttl: opts.ttl, + tag: opts.tag, + ); + } + } + + @override + Future sendError( + int id, + String topic, + String method, + JsonRpcError error, { + EncodeOptions? encodeOptions, + RpcOptions? rpcOptions, + String? appLink, + }) async { + final Map payload = JsonRpcUtils.formatJsonRpcError( + id, + error, + ); + + final String? message = await core.crypto.encode( + topic, + payload, + options: encodeOptions, + ); + + if (message == null) { + return; + } + + if ((appLink ?? '').isNotEmpty) { + final redirectURL = ReownCoreUtils.getLinkModeURL( + appLink!, + topic, + message, + ); + core.logger.t( + 'pairing sendError LinkMode, ' + 'id: $id topic: $topic, method: $method, error: $error', + ); + await ReownCoreUtils.openURL(redirectURL); + } else { + final fallbackMethod = MethodConstants.UNREGISTERED_METHOD; + final methodOpts = MethodConstants.RPC_OPTS[method]; + final fallbackMethodOpts = MethodConstants.RPC_OPTS[fallbackMethod]!; + final relayOpts = methodOpts ?? fallbackMethodOpts; + final fallbackOpts = relayOpts['reject'] ?? relayOpts['res']!; + core.logger.t( + 'pairing sendError Relay, ' + 'id: $id topic: $topic, method: $method, error: $error', + ); + await core.relayClient.publish( + topic: topic, + message: message, + ttl: (rpcOptions ?? fallbackOpts).ttl, + tag: (rpcOptions ?? fallbackOpts).tag, + ); + } + } + + /// ---- Private Helpers ---- /// + + Future _resubscribeAll() async { + // If the relay is not active, stop here + if (!core.relayClient.isConnected) { + return; + } + + // Resubscribe to all active pairings + final List activePairings = pairings.getAll(); + for (final PairingInfo pairing in activePairings) { + if (pairing.active) { + // print('Resubscribing to topic: ${pairing.topic}'); + await core.relayClient.subscribe(topic: pairing.topic); + } + } + } + + Future _deletePairing(String topic, bool expirerHasDeleted) async { + await core.relayClient.unsubscribe(topic: topic); + await pairings.delete(topic); + await core.crypto.deleteSymKey(topic); + if (expirerHasDeleted) { + await core.expirer.delete(topic); + } + } + + Future _cleanup() async { + final List expiredPairings = getPairings() + .where( + (PairingInfo info) => ReownCoreUtils.isExpired(info.expiry), + ) + .toList(); + for (final PairingInfo pairing in expiredPairings) { + // print('deleting expired pairing: ${pairing.topic}'); + await _deletePairing(pairing.topic, true); + } + + // Cleanup all history records + final List expiredHistory = history + .getAll() + .where( + (record) => ReownCoreUtils.isExpired(record.expiry ?? -1), + ) + .toList(); + // Loop through the expired records and delete them + for (final JsonRpcRecord record in expiredHistory) { + // print('deleting expired history record: ${record.id}'); + await history.delete(record.id.toString()); + } + + // Cleanup all of the expired receiver public keys + final List expiredReceiverPublicKeys = + topicToReceiverPublicKey + .getAll() + .where((receiver) => ReownCoreUtils.isExpired(receiver.expiry)) + .toList(); + // Loop through the expired receiver public keys and delete them + for (final ReceiverPublicKey receiver in expiredReceiverPublicKeys) { + // print('deleting expired receiver public key: $receiver'); + await topicToReceiverPublicKey.delete(receiver.topic); + } + } + + void _checkInitialized() { + if (!_initialized) { + throw Errors.getInternalError(Errors.NOT_INITIALIZED); + } + } + + /// ---- Relay Event Router ---- /// + + Map routerMapRequest = {}; + + void _registerRelayEvents() { + core.relayClient.onRelayClientConnect.subscribe(_onRelayConnect); + core.relayClient.onRelayClientMessage.subscribe(_onMessageEvent); + core.relayClient.onLinkModeMessage.subscribe(_onMessageEvent); + + register( + method: MethodConstants.WC_PAIRING_PING, + function: _onPairingPingRequest, + type: ProtocolType.pair, + ); + register( + method: MethodConstants.WC_PAIRING_DELETE, + function: _onPairingDeleteRequest, + type: ProtocolType.pair, + ); + } + + Future _onRelayConnect(EventArgs? args) async { + // print('Pairing: Relay connected'); + await _resubscribeAll(); + } + + void _onMessageEvent(MessageEvent? event) async { + if (event == null) { + return; + } + + // If we have a reciever public key for the topic, use it + ReceiverPublicKey? receiverPublicKey = + topicToReceiverPublicKey.get(event.topic); + // If there was a public key, delete it. One use. + if (receiverPublicKey != null) { + await topicToReceiverPublicKey.delete(event.topic); + } + + // Decode the message + String? payloadString = await core.crypto.decode( + event.topic, + event.message, + options: DecodeOptions( + receiverPublicKey: receiverPublicKey?.publicKey, + ), + ); + + if (payloadString == null) { + return; + } + + Map data = jsonDecode(payloadString); + core.logger.i('Pairing _onMessageEvent, Received data: $data'); + + // If it's an rpc request, handle it + if (data.containsKey('method')) { + final request = JsonRpcRequest.fromJson(data); + + if (routerMapRequest.containsKey(request.method)) { + routerMapRequest[request.method]!.function( + event.topic, + request, + event.transportType, + ); + } else { + _onUnkownRpcMethodRequest(event.topic, request); + } + // Otherwise handle it as a response + } else { + final response = JsonRpcResponse.fromJson(data); + core.logger.d('[$runtimeType] Relay event response ${jsonEncode(data)}'); + + if (pendingRequests.containsKey(response.id)) { + if (response.error != null) { + pendingRequests[response.id]!.error = response.error; + pendingRequests[response.id]!.completer.completeError( + response.error!, + ); + } else { + pendingRequests[response.id]!.response = response.result; + pendingRequests[response.id]!.completer.complete(response.result); + } + } + } + } + + Future _onPairingPingRequest( + String topic, + JsonRpcRequest request, [ + _, + ]) async { + final int id = request.id; + try { + // print('ping req'); + await _isValidPing(topic); + await sendResult( + id, + topic, + request.method, + true, + ); + onPairingPing.broadcast( + PairingEvent( + id: id, + topic: topic, + ), + ); + } on JsonRpcError catch (e) { + // print(e); + await sendError( + id, + topic, + request.method, + e, + ); + } + } + + Future _onPairingDeleteRequest( + String topic, + JsonRpcRequest request, [ + _, + ]) async { + // print('delete'); + final int id = request.id; + try { + await _isValidDisconnect(topic); + await sendResult( + id, + topic, + request.method, + true, + ); + await pairings.delete(topic); + onPairingDelete.broadcast( + PairingEvent( + id: id, + topic: topic, + ), + ); + } on JsonRpcError catch (e) { + await sendError( + id, + topic, + request.method, + e, + ); + } + } + + Future _onUnkownRpcMethodRequest( + String topic, + JsonRpcRequest request, + ) async { + final int id = request.id; + final String method = request.method; + try { + if (routerMapRequest.containsKey(method)) { + return; + } + final String message = Errors.getSdkError( + Errors.WC_METHOD_UNSUPPORTED, + context: method, + ).message; + await sendError( + id, + topic, + request.method, + JsonRpcError.methodNotFound(message), + ); + } on JsonRpcError catch (e) { + await sendError(id, topic, request.method, e); + } + } + + /// ---- Expirer Events ---- /// + + void _registerExpirerEvents() { + core.expirer.onExpire.subscribe(_onExpired); + } + + void _registerheartbeatSubscription() { + core.heartbeat.onPulse.subscribe(_heartbeatSubscription); + } + + Future _onExpired(ExpirationEvent? event) async { + if (event == null) { + return; + } + + if (pairings.has(event.target)) { + // Clean up the pairing + await _deletePairing(event.target, true); + onPairingExpire.broadcast( + PairingEvent( + topic: event.target, + ), + ); + } + } + + void _heartbeatSubscription(EventArgs? args) async { + await checkAndExpire(); + } + + /// ---- Validators ---- /// + + Future _isValidPing(String topic) async { + await isValidPairingTopic(topic: topic); + } + + Future _isValidDisconnect(String topic) async { + await isValidPairingTopic(topic: topic); + } + + @override + void dispatchEnvelope({ + required String topic, + required String envelope, + }) async { + core.logger.i('[$runtimeType] dispatchEnvelope $topic, $envelope'); + + final message = Uri.decodeComponent(envelope); + await core.relayClient.handleLinkModeMessage(topic, message); + } +} diff --git a/packages/reown_core/lib/pairing/pairing_store.dart b/packages/reown_core/lib/pairing/pairing_store.dart new file mode 100644 index 0000000..d0c89be --- /dev/null +++ b/packages/reown_core/lib/pairing/pairing_store.dart @@ -0,0 +1,39 @@ +import 'package:reown_core/pairing/i_pairing_store.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/store/generic_store.dart'; + +class PairingStore extends GenericStore implements IPairingStore { + PairingStore({ + required super.storage, + required super.context, + required super.version, + required super.fromJson, + }); + + @override + Future update( + String topic, { + int? expiry, + bool? active, + PairingMetadata? metadata, + }) async { + checkInitialized(); + + PairingInfo? info = get(topic); + if (info == null) { + return; + } + + if (expiry != null) { + info = info.copyWith(expiry: expiry); + } + if (active != null) { + info = info.copyWith(active: active); + } + if (metadata != null) { + info = info.copyWith(peerMetadata: metadata); + } + + await set(topic, info); + } +} diff --git a/packages/reown_core/lib/pairing/utils/json_rpc_utils.dart b/packages/reown_core/lib/pairing/utils/json_rpc_utils.dart new file mode 100644 index 0000000..43a0d13 --- /dev/null +++ b/packages/reown_core/lib/pairing/utils/json_rpc_utils.dart @@ -0,0 +1,74 @@ +import 'dart:math'; + +import 'package:reown_core/models/json_rpc_models.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/utils/errors.dart'; + +class JsonRpcUtils { + static int payloadId({int entropy = 3}) { + int addedZeroes = (pow(10, entropy) as int); + int date = DateTime.now().millisecondsSinceEpoch * addedZeroes; + int extra = (Random().nextDouble() * addedZeroes).floor(); + return date + extra; + } + + static Map formatJsonRpcRequest( + String method, + dynamic params, { + int? id, + }) { + return { + 'id': id ??= payloadId(), + 'jsonrpc': '2.0', + 'method': method, + 'params': params, + }; + } + + static Map formatJsonRpcResponse( + int id, + T result, + ) { + return { + 'id': id, + 'jsonrpc': '2.0', + 'result': result, + }; + } + + static Map formatJsonRpcError(int id, JsonRpcError error) { + return { + 'id': id, + 'jsonrpc': '2.0', + 'error': error.toJson(), + }; + } + + static bool validateMethods( + List methods, + List registeredMethods, + ) { + List unsupportedMethods = []; + + // Loop through the methods, and validate that each one exists in the registered methods + for (String method in methods) { + if (!registeredMethods.any((element) => element.method == method)) { + // print("Adding method: $method"); + unsupportedMethods.add(method); + } + } + + // If there are any unsupported methods, throw an error + if (unsupportedMethods.isNotEmpty) { + // print( + // 'Unsupported Methods: $unsupportedMethods, Length: ${unsupportedMethods.length}'); + throw Errors.getSdkError( + Errors.WC_METHOD_UNSUPPORTED, + context: + 'The following methods are not registered: ${unsupportedMethods.join(', ')}.', + ); + } + + return true; + } +} diff --git a/packages/reown_core/lib/pairing/utils/pairing_models.dart b/packages/reown_core/lib/pairing/utils/pairing_models.dart new file mode 100644 index 0000000..8400987 --- /dev/null +++ b/packages/reown_core/lib/pairing/utils/pairing_models.dart @@ -0,0 +1,199 @@ +import 'package:event/event.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reown_core/models/json_rpc_models.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; + +part 'pairing_models.g.dart'; +part 'pairing_models.freezed.dart'; + +enum ProtocolType { + pair, + sign, +} + +@freezed +class PairingInfo with _$PairingInfo { + @JsonSerializable() + const factory PairingInfo({ + required String topic, + required int expiry, + required Relay relay, + required bool active, + List? methods, + PairingMetadata? peerMetadata, + }) = _PairingInfo; + + factory PairingInfo.fromJson(Map json) => + _$PairingInfoFromJson(json); +} + +@freezed +class PairingMetadata with _$PairingMetadata { + @JsonSerializable(includeIfNull: false) + const factory PairingMetadata({ + required String name, + required String description, + required String url, + required List icons, + String? verifyUrl, + Redirect? redirect, + }) = _PairingMetadata; + + factory PairingMetadata.empty() => const PairingMetadata( + name: '', + description: '', + url: '', + icons: [], + ); + + factory PairingMetadata.fromJson(Map json) => + _$PairingMetadataFromJson(json); +} + +@freezed +class Redirect with _$Redirect { + @JsonSerializable() + const factory Redirect({ + String? native, + String? universal, + @Default(false) bool linkMode, + }) = _Redirect; + + factory Redirect.fromJson(Map json) => + _$RedirectFromJson(json); +} + +class CreateResponse { + String topic; + Uri uri; + PairingInfo pairingInfo; + + CreateResponse({ + required this.topic, + required this.uri, + required this.pairingInfo, + }); + + @override + String toString() { + return 'CreateResponse(topic: $topic, uri: $uri, pairingInfo: ${pairingInfo.toJson()})'; + } +} + +class ExpirationEvent extends EventArgs { + String target; + int expiry; + + ExpirationEvent({ + required this.target, + required this.expiry, + }); + + @override + String toString() { + return 'ExpirationEvent(target: $target, expiry: $expiry)'; + } +} + +class HistoryEvent extends EventArgs { + JsonRpcRecord record; + + HistoryEvent({required this.record}); + + @override + String toString() { + return 'HistoryEvent(record: $record)'; + } +} + +class PairingInvalidEvent extends EventArgs { + String message; + + PairingInvalidEvent({ + required this.message, + }); + + @override + String toString() { + return 'PairingInvalidEvent(message: $message)'; + } +} + +class PairingEvent extends EventArgs { + int? id; + String? topic; + JsonRpcError? error; + + PairingEvent({ + this.id, + this.topic, + this.error, + }); + + @override + String toString() { + return 'PairingEvent(id: $id, topic: $topic, error: $error)'; + } +} + +class PairingActivateEvent extends EventArgs { + String topic; + int expiry; + + PairingActivateEvent({ + required this.topic, + required this.expiry, + }); + + @override + String toString() { + return 'PairingActivateEvent(topic: $topic, expiry: $expiry)'; + } +} + +@freezed +class JsonRpcRecord with _$JsonRpcRecord { + @JsonSerializable(includeIfNull: false) + const factory JsonRpcRecord({ + required int id, + required String topic, + required String method, + required dynamic params, + String? chainId, + int? expiry, + dynamic response, + }) = _JsonRpcRecord; + + factory JsonRpcRecord.fromJson(Map json) => + _$JsonRpcRecordFromJson(json); +} + +@freezed +class ReceiverPublicKey with _$ReceiverPublicKey { + @JsonSerializable(includeIfNull: false) + const factory ReceiverPublicKey({ + required String topic, + required String publicKey, + required int expiry, + }) = _ReceiverPublicKey; + + factory ReceiverPublicKey.fromJson(Map json) => + _$ReceiverPublicKeyFromJson(json); +} + +class RegisteredFunction { + String method; + Function(String, JsonRpcRequest, [TransportType]) function; + ProtocolType type; + + RegisteredFunction({ + required this.method, + required this.function, + required this.type, + }); + + @override + String toString() { + return 'RegisteredFunction(method: $method, function: $function, type: $type)'; + } +} diff --git a/packages/reown_core/lib/pairing/utils/pairing_models.freezed.dart b/packages/reown_core/lib/pairing/utils/pairing_models.freezed.dart new file mode 100644 index 0000000..5defc74 --- /dev/null +++ b/packages/reown_core/lib/pairing/utils/pairing_models.freezed.dart @@ -0,0 +1,1172 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'pairing_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +PairingInfo _$PairingInfoFromJson(Map json) { + return _PairingInfo.fromJson(json); +} + +/// @nodoc +mixin _$PairingInfo { + String get topic => throw _privateConstructorUsedError; + int get expiry => throw _privateConstructorUsedError; + Relay get relay => throw _privateConstructorUsedError; + bool get active => throw _privateConstructorUsedError; + List? get methods => throw _privateConstructorUsedError; + PairingMetadata? get peerMetadata => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PairingInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PairingInfoCopyWith<$Res> { + factory $PairingInfoCopyWith( + PairingInfo value, $Res Function(PairingInfo) then) = + _$PairingInfoCopyWithImpl<$Res, PairingInfo>; + @useResult + $Res call( + {String topic, + int expiry, + Relay relay, + bool active, + List? methods, + PairingMetadata? peerMetadata}); + + $PairingMetadataCopyWith<$Res>? get peerMetadata; +} + +/// @nodoc +class _$PairingInfoCopyWithImpl<$Res, $Val extends PairingInfo> + implements $PairingInfoCopyWith<$Res> { + _$PairingInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? topic = null, + Object? expiry = null, + Object? relay = null, + Object? active = null, + Object? methods = freezed, + Object? peerMetadata = freezed, + }) { + return _then(_value.copyWith( + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + relay: null == relay + ? _value.relay + : relay // ignore: cast_nullable_to_non_nullable + as Relay, + active: null == active + ? _value.active + : active // ignore: cast_nullable_to_non_nullable + as bool, + methods: freezed == methods + ? _value.methods + : methods // ignore: cast_nullable_to_non_nullable + as List?, + peerMetadata: freezed == peerMetadata + ? _value.peerMetadata + : peerMetadata // ignore: cast_nullable_to_non_nullable + as PairingMetadata?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $PairingMetadataCopyWith<$Res>? get peerMetadata { + if (_value.peerMetadata == null) { + return null; + } + + return $PairingMetadataCopyWith<$Res>(_value.peerMetadata!, (value) { + return _then(_value.copyWith(peerMetadata: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$PairingInfoImplCopyWith<$Res> + implements $PairingInfoCopyWith<$Res> { + factory _$$PairingInfoImplCopyWith( + _$PairingInfoImpl value, $Res Function(_$PairingInfoImpl) then) = + __$$PairingInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String topic, + int expiry, + Relay relay, + bool active, + List? methods, + PairingMetadata? peerMetadata}); + + @override + $PairingMetadataCopyWith<$Res>? get peerMetadata; +} + +/// @nodoc +class __$$PairingInfoImplCopyWithImpl<$Res> + extends _$PairingInfoCopyWithImpl<$Res, _$PairingInfoImpl> + implements _$$PairingInfoImplCopyWith<$Res> { + __$$PairingInfoImplCopyWithImpl( + _$PairingInfoImpl _value, $Res Function(_$PairingInfoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? topic = null, + Object? expiry = null, + Object? relay = null, + Object? active = null, + Object? methods = freezed, + Object? peerMetadata = freezed, + }) { + return _then(_$PairingInfoImpl( + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + relay: null == relay + ? _value.relay + : relay // ignore: cast_nullable_to_non_nullable + as Relay, + active: null == active + ? _value.active + : active // ignore: cast_nullable_to_non_nullable + as bool, + methods: freezed == methods + ? _value._methods + : methods // ignore: cast_nullable_to_non_nullable + as List?, + peerMetadata: freezed == peerMetadata + ? _value.peerMetadata + : peerMetadata // ignore: cast_nullable_to_non_nullable + as PairingMetadata?, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$PairingInfoImpl implements _PairingInfo { + const _$PairingInfoImpl( + {required this.topic, + required this.expiry, + required this.relay, + required this.active, + final List? methods, + this.peerMetadata}) + : _methods = methods; + + factory _$PairingInfoImpl.fromJson(Map json) => + _$$PairingInfoImplFromJson(json); + + @override + final String topic; + @override + final int expiry; + @override + final Relay relay; + @override + final bool active; + final List? _methods; + @override + List? get methods { + final value = _methods; + if (value == null) return null; + if (_methods is EqualUnmodifiableListView) return _methods; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + final PairingMetadata? peerMetadata; + + @override + String toString() { + return 'PairingInfo(topic: $topic, expiry: $expiry, relay: $relay, active: $active, methods: $methods, peerMetadata: $peerMetadata)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PairingInfoImpl && + (identical(other.topic, topic) || other.topic == topic) && + (identical(other.expiry, expiry) || other.expiry == expiry) && + (identical(other.relay, relay) || other.relay == relay) && + (identical(other.active, active) || other.active == active) && + const DeepCollectionEquality().equals(other._methods, _methods) && + (identical(other.peerMetadata, peerMetadata) || + other.peerMetadata == peerMetadata)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, topic, expiry, relay, active, + const DeepCollectionEquality().hash(_methods), peerMetadata); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PairingInfoImplCopyWith<_$PairingInfoImpl> get copyWith => + __$$PairingInfoImplCopyWithImpl<_$PairingInfoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$PairingInfoImplToJson( + this, + ); + } +} + +abstract class _PairingInfo implements PairingInfo { + const factory _PairingInfo( + {required final String topic, + required final int expiry, + required final Relay relay, + required final bool active, + final List? methods, + final PairingMetadata? peerMetadata}) = _$PairingInfoImpl; + + factory _PairingInfo.fromJson(Map json) = + _$PairingInfoImpl.fromJson; + + @override + String get topic; + @override + int get expiry; + @override + Relay get relay; + @override + bool get active; + @override + List? get methods; + @override + PairingMetadata? get peerMetadata; + @override + @JsonKey(ignore: true) + _$$PairingInfoImplCopyWith<_$PairingInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +PairingMetadata _$PairingMetadataFromJson(Map json) { + return _PairingMetadata.fromJson(json); +} + +/// @nodoc +mixin _$PairingMetadata { + String get name => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get url => throw _privateConstructorUsedError; + List get icons => throw _privateConstructorUsedError; + String? get verifyUrl => throw _privateConstructorUsedError; + Redirect? get redirect => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PairingMetadataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PairingMetadataCopyWith<$Res> { + factory $PairingMetadataCopyWith( + PairingMetadata value, $Res Function(PairingMetadata) then) = + _$PairingMetadataCopyWithImpl<$Res, PairingMetadata>; + @useResult + $Res call( + {String name, + String description, + String url, + List icons, + String? verifyUrl, + Redirect? redirect}); + + $RedirectCopyWith<$Res>? get redirect; +} + +/// @nodoc +class _$PairingMetadataCopyWithImpl<$Res, $Val extends PairingMetadata> + implements $PairingMetadataCopyWith<$Res> { + _$PairingMetadataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? description = null, + Object? url = null, + Object? icons = null, + Object? verifyUrl = freezed, + Object? redirect = freezed, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + icons: null == icons + ? _value.icons + : icons // ignore: cast_nullable_to_non_nullable + as List, + verifyUrl: freezed == verifyUrl + ? _value.verifyUrl + : verifyUrl // ignore: cast_nullable_to_non_nullable + as String?, + redirect: freezed == redirect + ? _value.redirect + : redirect // ignore: cast_nullable_to_non_nullable + as Redirect?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $RedirectCopyWith<$Res>? get redirect { + if (_value.redirect == null) { + return null; + } + + return $RedirectCopyWith<$Res>(_value.redirect!, (value) { + return _then(_value.copyWith(redirect: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$PairingMetadataImplCopyWith<$Res> + implements $PairingMetadataCopyWith<$Res> { + factory _$$PairingMetadataImplCopyWith(_$PairingMetadataImpl value, + $Res Function(_$PairingMetadataImpl) then) = + __$$PairingMetadataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String name, + String description, + String url, + List icons, + String? verifyUrl, + Redirect? redirect}); + + @override + $RedirectCopyWith<$Res>? get redirect; +} + +/// @nodoc +class __$$PairingMetadataImplCopyWithImpl<$Res> + extends _$PairingMetadataCopyWithImpl<$Res, _$PairingMetadataImpl> + implements _$$PairingMetadataImplCopyWith<$Res> { + __$$PairingMetadataImplCopyWithImpl( + _$PairingMetadataImpl _value, $Res Function(_$PairingMetadataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? description = null, + Object? url = null, + Object? icons = null, + Object? verifyUrl = freezed, + Object? redirect = freezed, + }) { + return _then(_$PairingMetadataImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + icons: null == icons + ? _value._icons + : icons // ignore: cast_nullable_to_non_nullable + as List, + verifyUrl: freezed == verifyUrl + ? _value.verifyUrl + : verifyUrl // ignore: cast_nullable_to_non_nullable + as String?, + redirect: freezed == redirect + ? _value.redirect + : redirect // ignore: cast_nullable_to_non_nullable + as Redirect?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$PairingMetadataImpl implements _PairingMetadata { + const _$PairingMetadataImpl( + {required this.name, + required this.description, + required this.url, + required final List icons, + this.verifyUrl, + this.redirect}) + : _icons = icons; + + factory _$PairingMetadataImpl.fromJson(Map json) => + _$$PairingMetadataImplFromJson(json); + + @override + final String name; + @override + final String description; + @override + final String url; + final List _icons; + @override + List get icons { + if (_icons is EqualUnmodifiableListView) return _icons; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_icons); + } + + @override + final String? verifyUrl; + @override + final Redirect? redirect; + + @override + String toString() { + return 'PairingMetadata(name: $name, description: $description, url: $url, icons: $icons, verifyUrl: $verifyUrl, redirect: $redirect)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PairingMetadataImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.url, url) || other.url == url) && + const DeepCollectionEquality().equals(other._icons, _icons) && + (identical(other.verifyUrl, verifyUrl) || + other.verifyUrl == verifyUrl) && + (identical(other.redirect, redirect) || + other.redirect == redirect)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, name, description, url, + const DeepCollectionEquality().hash(_icons), verifyUrl, redirect); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PairingMetadataImplCopyWith<_$PairingMetadataImpl> get copyWith => + __$$PairingMetadataImplCopyWithImpl<_$PairingMetadataImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$PairingMetadataImplToJson( + this, + ); + } +} + +abstract class _PairingMetadata implements PairingMetadata { + const factory _PairingMetadata( + {required final String name, + required final String description, + required final String url, + required final List icons, + final String? verifyUrl, + final Redirect? redirect}) = _$PairingMetadataImpl; + + factory _PairingMetadata.fromJson(Map json) = + _$PairingMetadataImpl.fromJson; + + @override + String get name; + @override + String get description; + @override + String get url; + @override + List get icons; + @override + String? get verifyUrl; + @override + Redirect? get redirect; + @override + @JsonKey(ignore: true) + _$$PairingMetadataImplCopyWith<_$PairingMetadataImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Redirect _$RedirectFromJson(Map json) { + return _Redirect.fromJson(json); +} + +/// @nodoc +mixin _$Redirect { + String? get native => throw _privateConstructorUsedError; + String? get universal => throw _privateConstructorUsedError; + bool get linkMode => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $RedirectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RedirectCopyWith<$Res> { + factory $RedirectCopyWith(Redirect value, $Res Function(Redirect) then) = + _$RedirectCopyWithImpl<$Res, Redirect>; + @useResult + $Res call({String? native, String? universal, bool linkMode}); +} + +/// @nodoc +class _$RedirectCopyWithImpl<$Res, $Val extends Redirect> + implements $RedirectCopyWith<$Res> { + _$RedirectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? native = freezed, + Object? universal = freezed, + Object? linkMode = null, + }) { + return _then(_value.copyWith( + native: freezed == native + ? _value.native + : native // ignore: cast_nullable_to_non_nullable + as String?, + universal: freezed == universal + ? _value.universal + : universal // ignore: cast_nullable_to_non_nullable + as String?, + linkMode: null == linkMode + ? _value.linkMode + : linkMode // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RedirectImplCopyWith<$Res> + implements $RedirectCopyWith<$Res> { + factory _$$RedirectImplCopyWith( + _$RedirectImpl value, $Res Function(_$RedirectImpl) then) = + __$$RedirectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? native, String? universal, bool linkMode}); +} + +/// @nodoc +class __$$RedirectImplCopyWithImpl<$Res> + extends _$RedirectCopyWithImpl<$Res, _$RedirectImpl> + implements _$$RedirectImplCopyWith<$Res> { + __$$RedirectImplCopyWithImpl( + _$RedirectImpl _value, $Res Function(_$RedirectImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? native = freezed, + Object? universal = freezed, + Object? linkMode = null, + }) { + return _then(_$RedirectImpl( + native: freezed == native + ? _value.native + : native // ignore: cast_nullable_to_non_nullable + as String?, + universal: freezed == universal + ? _value.universal + : universal // ignore: cast_nullable_to_non_nullable + as String?, + linkMode: null == linkMode + ? _value.linkMode + : linkMode // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$RedirectImpl implements _Redirect { + const _$RedirectImpl({this.native, this.universal, this.linkMode = false}); + + factory _$RedirectImpl.fromJson(Map json) => + _$$RedirectImplFromJson(json); + + @override + final String? native; + @override + final String? universal; + @override + @JsonKey() + final bool linkMode; + + @override + String toString() { + return 'Redirect(native: $native, universal: $universal, linkMode: $linkMode)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RedirectImpl && + (identical(other.native, native) || other.native == native) && + (identical(other.universal, universal) || + other.universal == universal) && + (identical(other.linkMode, linkMode) || + other.linkMode == linkMode)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, native, universal, linkMode); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RedirectImplCopyWith<_$RedirectImpl> get copyWith => + __$$RedirectImplCopyWithImpl<_$RedirectImpl>(this, _$identity); + + @override + Map toJson() { + return _$$RedirectImplToJson( + this, + ); + } +} + +abstract class _Redirect implements Redirect { + const factory _Redirect( + {final String? native, + final String? universal, + final bool linkMode}) = _$RedirectImpl; + + factory _Redirect.fromJson(Map json) = + _$RedirectImpl.fromJson; + + @override + String? get native; + @override + String? get universal; + @override + bool get linkMode; + @override + @JsonKey(ignore: true) + _$$RedirectImplCopyWith<_$RedirectImpl> get copyWith => + throw _privateConstructorUsedError; +} + +JsonRpcRecord _$JsonRpcRecordFromJson(Map json) { + return _JsonRpcRecord.fromJson(json); +} + +/// @nodoc +mixin _$JsonRpcRecord { + int get id => throw _privateConstructorUsedError; + String get topic => throw _privateConstructorUsedError; + String get method => throw _privateConstructorUsedError; + dynamic get params => throw _privateConstructorUsedError; + String? get chainId => throw _privateConstructorUsedError; + int? get expiry => throw _privateConstructorUsedError; + dynamic get response => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $JsonRpcRecordCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $JsonRpcRecordCopyWith<$Res> { + factory $JsonRpcRecordCopyWith( + JsonRpcRecord value, $Res Function(JsonRpcRecord) then) = + _$JsonRpcRecordCopyWithImpl<$Res, JsonRpcRecord>; + @useResult + $Res call( + {int id, + String topic, + String method, + dynamic params, + String? chainId, + int? expiry, + dynamic response}); +} + +/// @nodoc +class _$JsonRpcRecordCopyWithImpl<$Res, $Val extends JsonRpcRecord> + implements $JsonRpcRecordCopyWith<$Res> { + _$JsonRpcRecordCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? topic = null, + Object? method = null, + Object? params = freezed, + Object? chainId = freezed, + Object? expiry = freezed, + Object? response = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + method: null == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as dynamic, + chainId: freezed == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String?, + expiry: freezed == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int?, + response: freezed == response + ? _value.response + : response // ignore: cast_nullable_to_non_nullable + as dynamic, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$JsonRpcRecordImplCopyWith<$Res> + implements $JsonRpcRecordCopyWith<$Res> { + factory _$$JsonRpcRecordImplCopyWith( + _$JsonRpcRecordImpl value, $Res Function(_$JsonRpcRecordImpl) then) = + __$$JsonRpcRecordImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String topic, + String method, + dynamic params, + String? chainId, + int? expiry, + dynamic response}); +} + +/// @nodoc +class __$$JsonRpcRecordImplCopyWithImpl<$Res> + extends _$JsonRpcRecordCopyWithImpl<$Res, _$JsonRpcRecordImpl> + implements _$$JsonRpcRecordImplCopyWith<$Res> { + __$$JsonRpcRecordImplCopyWithImpl( + _$JsonRpcRecordImpl _value, $Res Function(_$JsonRpcRecordImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? topic = null, + Object? method = null, + Object? params = freezed, + Object? chainId = freezed, + Object? expiry = freezed, + Object? response = freezed, + }) { + return _then(_$JsonRpcRecordImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + method: null == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as dynamic, + chainId: freezed == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String?, + expiry: freezed == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int?, + response: freezed == response + ? _value.response + : response // ignore: cast_nullable_to_non_nullable + as dynamic, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$JsonRpcRecordImpl implements _JsonRpcRecord { + const _$JsonRpcRecordImpl( + {required this.id, + required this.topic, + required this.method, + required this.params, + this.chainId, + this.expiry, + this.response}); + + factory _$JsonRpcRecordImpl.fromJson(Map json) => + _$$JsonRpcRecordImplFromJson(json); + + @override + final int id; + @override + final String topic; + @override + final String method; + @override + final dynamic params; + @override + final String? chainId; + @override + final int? expiry; + @override + final dynamic response; + + @override + String toString() { + return 'JsonRpcRecord(id: $id, topic: $topic, method: $method, params: $params, chainId: $chainId, expiry: $expiry, response: $response)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$JsonRpcRecordImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.topic, topic) || other.topic == topic) && + (identical(other.method, method) || other.method == method) && + const DeepCollectionEquality().equals(other.params, params) && + (identical(other.chainId, chainId) || other.chainId == chainId) && + (identical(other.expiry, expiry) || other.expiry == expiry) && + const DeepCollectionEquality().equals(other.response, response)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + id, + topic, + method, + const DeepCollectionEquality().hash(params), + chainId, + expiry, + const DeepCollectionEquality().hash(response)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$JsonRpcRecordImplCopyWith<_$JsonRpcRecordImpl> get copyWith => + __$$JsonRpcRecordImplCopyWithImpl<_$JsonRpcRecordImpl>(this, _$identity); + + @override + Map toJson() { + return _$$JsonRpcRecordImplToJson( + this, + ); + } +} + +abstract class _JsonRpcRecord implements JsonRpcRecord { + const factory _JsonRpcRecord( + {required final int id, + required final String topic, + required final String method, + required final dynamic params, + final String? chainId, + final int? expiry, + final dynamic response}) = _$JsonRpcRecordImpl; + + factory _JsonRpcRecord.fromJson(Map json) = + _$JsonRpcRecordImpl.fromJson; + + @override + int get id; + @override + String get topic; + @override + String get method; + @override + dynamic get params; + @override + String? get chainId; + @override + int? get expiry; + @override + dynamic get response; + @override + @JsonKey(ignore: true) + _$$JsonRpcRecordImplCopyWith<_$JsonRpcRecordImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ReceiverPublicKey _$ReceiverPublicKeyFromJson(Map json) { + return _ReceiverPublicKey.fromJson(json); +} + +/// @nodoc +mixin _$ReceiverPublicKey { + String get topic => throw _privateConstructorUsedError; + String get publicKey => throw _privateConstructorUsedError; + int get expiry => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ReceiverPublicKeyCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ReceiverPublicKeyCopyWith<$Res> { + factory $ReceiverPublicKeyCopyWith( + ReceiverPublicKey value, $Res Function(ReceiverPublicKey) then) = + _$ReceiverPublicKeyCopyWithImpl<$Res, ReceiverPublicKey>; + @useResult + $Res call({String topic, String publicKey, int expiry}); +} + +/// @nodoc +class _$ReceiverPublicKeyCopyWithImpl<$Res, $Val extends ReceiverPublicKey> + implements $ReceiverPublicKeyCopyWith<$Res> { + _$ReceiverPublicKeyCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? topic = null, + Object? publicKey = null, + Object? expiry = null, + }) { + return _then(_value.copyWith( + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as String, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ReceiverPublicKeyImplCopyWith<$Res> + implements $ReceiverPublicKeyCopyWith<$Res> { + factory _$$ReceiverPublicKeyImplCopyWith(_$ReceiverPublicKeyImpl value, + $Res Function(_$ReceiverPublicKeyImpl) then) = + __$$ReceiverPublicKeyImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String topic, String publicKey, int expiry}); +} + +/// @nodoc +class __$$ReceiverPublicKeyImplCopyWithImpl<$Res> + extends _$ReceiverPublicKeyCopyWithImpl<$Res, _$ReceiverPublicKeyImpl> + implements _$$ReceiverPublicKeyImplCopyWith<$Res> { + __$$ReceiverPublicKeyImplCopyWithImpl(_$ReceiverPublicKeyImpl _value, + $Res Function(_$ReceiverPublicKeyImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? topic = null, + Object? publicKey = null, + Object? expiry = null, + }) { + return _then(_$ReceiverPublicKeyImpl( + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as String, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$ReceiverPublicKeyImpl implements _ReceiverPublicKey { + const _$ReceiverPublicKeyImpl( + {required this.topic, required this.publicKey, required this.expiry}); + + factory _$ReceiverPublicKeyImpl.fromJson(Map json) => + _$$ReceiverPublicKeyImplFromJson(json); + + @override + final String topic; + @override + final String publicKey; + @override + final int expiry; + + @override + String toString() { + return 'ReceiverPublicKey(topic: $topic, publicKey: $publicKey, expiry: $expiry)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ReceiverPublicKeyImpl && + (identical(other.topic, topic) || other.topic == topic) && + (identical(other.publicKey, publicKey) || + other.publicKey == publicKey) && + (identical(other.expiry, expiry) || other.expiry == expiry)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, topic, publicKey, expiry); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ReceiverPublicKeyImplCopyWith<_$ReceiverPublicKeyImpl> get copyWith => + __$$ReceiverPublicKeyImplCopyWithImpl<_$ReceiverPublicKeyImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ReceiverPublicKeyImplToJson( + this, + ); + } +} + +abstract class _ReceiverPublicKey implements ReceiverPublicKey { + const factory _ReceiverPublicKey( + {required final String topic, + required final String publicKey, + required final int expiry}) = _$ReceiverPublicKeyImpl; + + factory _ReceiverPublicKey.fromJson(Map json) = + _$ReceiverPublicKeyImpl.fromJson; + + @override + String get topic; + @override + String get publicKey; + @override + int get expiry; + @override + @JsonKey(ignore: true) + _$$ReceiverPublicKeyImplCopyWith<_$ReceiverPublicKeyImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_core/lib/pairing/utils/pairing_models.g.dart b/packages/reown_core/lib/pairing/utils/pairing_models.g.dart new file mode 100644 index 0000000..3a5bb70 --- /dev/null +++ b/packages/reown_core/lib/pairing/utils/pairing_models.g.dart @@ -0,0 +1,125 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pairing_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$PairingInfoImpl _$$PairingInfoImplFromJson(Map json) => + _$PairingInfoImpl( + topic: json['topic'] as String, + expiry: (json['expiry'] as num).toInt(), + relay: Relay.fromJson(json['relay'] as Map), + active: json['active'] as bool, + methods: + (json['methods'] as List?)?.map((e) => e as String).toList(), + peerMetadata: json['peerMetadata'] == null + ? null + : PairingMetadata.fromJson( + json['peerMetadata'] as Map), + ); + +Map _$$PairingInfoImplToJson(_$PairingInfoImpl instance) => + { + 'topic': instance.topic, + 'expiry': instance.expiry, + 'relay': instance.relay.toJson(), + 'active': instance.active, + 'methods': instance.methods, + 'peerMetadata': instance.peerMetadata?.toJson(), + }; + +_$PairingMetadataImpl _$$PairingMetadataImplFromJson( + Map json) => + _$PairingMetadataImpl( + name: json['name'] as String, + description: json['description'] as String, + url: json['url'] as String, + icons: (json['icons'] as List).map((e) => e as String).toList(), + verifyUrl: json['verifyUrl'] as String?, + redirect: json['redirect'] == null + ? null + : Redirect.fromJson(json['redirect'] as Map), + ); + +Map _$$PairingMetadataImplToJson( + _$PairingMetadataImpl instance) { + final val = { + 'name': instance.name, + 'description': instance.description, + 'url': instance.url, + 'icons': instance.icons, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('verifyUrl', instance.verifyUrl); + writeNotNull('redirect', instance.redirect?.toJson()); + return val; +} + +_$RedirectImpl _$$RedirectImplFromJson(Map json) => + _$RedirectImpl( + native: json['native'] as String?, + universal: json['universal'] as String?, + linkMode: json['linkMode'] as bool? ?? false, + ); + +Map _$$RedirectImplToJson(_$RedirectImpl instance) => + { + 'native': instance.native, + 'universal': instance.universal, + 'linkMode': instance.linkMode, + }; + +_$JsonRpcRecordImpl _$$JsonRpcRecordImplFromJson(Map json) => + _$JsonRpcRecordImpl( + id: (json['id'] as num).toInt(), + topic: json['topic'] as String, + method: json['method'] as String, + params: json['params'], + chainId: json['chainId'] as String?, + expiry: (json['expiry'] as num?)?.toInt(), + response: json['response'], + ); + +Map _$$JsonRpcRecordImplToJson(_$JsonRpcRecordImpl instance) { + final val = { + 'id': instance.id, + 'topic': instance.topic, + 'method': instance.method, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('params', instance.params); + writeNotNull('chainId', instance.chainId); + writeNotNull('expiry', instance.expiry); + writeNotNull('response', instance.response); + return val; +} + +_$ReceiverPublicKeyImpl _$$ReceiverPublicKeyImplFromJson( + Map json) => + _$ReceiverPublicKeyImpl( + topic: json['topic'] as String, + publicKey: json['publicKey'] as String, + expiry: (json['expiry'] as num).toInt(), + ); + +Map _$$ReceiverPublicKeyImplToJson( + _$ReceiverPublicKeyImpl instance) => + { + 'topic': instance.topic, + 'publicKey': instance.publicKey, + 'expiry': instance.expiry, + }; diff --git a/packages/reown_core/lib/relay_auth/i_relay_auth.dart b/packages/reown_core/lib/relay_auth/i_relay_auth.dart new file mode 100644 index 0000000..c637cd5 --- /dev/null +++ b/packages/reown_core/lib/relay_auth/i_relay_auth.dart @@ -0,0 +1,32 @@ +import 'dart:typed_data'; + +import 'package:reown_core/relay_auth/relay_auth_models.dart'; + +abstract class IRelayAuth { + // API + Future generateKeyPair([Uint8List? seed]); + Future signJWT({ + required String sub, + required String aud, + required int ttl, + required RelayAuthKeyPair keyPair, + int? iat, + }); + Future verifyJWT(String jwt); + + // Auth + Map decodeJson(String s); + String encodeJson(Map value); + + String encodeIss(Uint8List publicKey); + Uint8List decodeIss(String issuer); + + String encodeSig(Uint8List bytes); + Uint8List decodeSig(String encoded); + + Uint8List encodeData(JWTData params); + JWTData decodeData(Uint8List data); + + String encodeJWT(JWTSigned params); + JWTDecoded decodeJWT(String encoded); +} diff --git a/packages/reown_core/lib/relay_auth/relay_auth.dart b/packages/reown_core/lib/relay_auth/relay_auth.dart new file mode 100644 index 0000000..24c0ec7 --- /dev/null +++ b/packages/reown_core/lib/relay_auth/relay_auth.dart @@ -0,0 +1,236 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:bs58/bs58.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; +import 'package:reown_core/relay_auth/i_relay_auth.dart'; +import 'package:reown_core/relay_auth/relay_auth_models.dart'; + +class RelayAuth implements IRelayAuth { + static const String multicodecEd25519Header = 'K36'; + static const String multicodecEd25519Base = 'z'; + static const int multicodecEd25519Length = 32; + + static const String JWT_DELIMITER = '.'; + + static const String DID_DELIMITER = ':'; + static const String DID_PREFIX = 'did'; + static const String DID_METHOD = 'key'; + + @override + Future generateKeyPair([Uint8List? seed]) async { + ed.PrivateKey privateKey; + ed.PublicKey publicKey; + if (seed == null) { + final keyPair = ed.generateKey(); + privateKey = keyPair.privateKey; + publicKey = keyPair.publicKey; + } else { + privateKey = ed.newKeyFromSeed(seed); + publicKey = ed.public(privateKey); + } + + return RelayAuthKeyPair( + Uint8List.fromList(privateKey.bytes), + Uint8List.fromList(publicKey.bytes), + ); + } + + @override + Future signJWT({ + required String sub, + required String aud, + required int ttl, + required RelayAuthKeyPair keyPair, + int? iat, + }) async { + iat ??= DateTime.now().millisecondsSinceEpoch ~/ 1000 - 60; + final JWTHeader header = JWTHeader(); + final String iss = encodeIss(keyPair.publicKeyBytes); + final int exp = iat + ttl; + final JWTPayload payload = JWTPayload( + iss, + sub, + aud, + iat, + exp, + ); + final Uint8List data = encodeData( + JWTData( + header, + payload, + ), + ); + Uint8List signature = ed.sign( + ed.PrivateKey(keyPair.privateKeyBytes), + data, + ); + // List signature = keyPair.sign(data); + return encodeJWT(JWTSigned(signature, payload)); + } + + @override + Future verifyJWT(String jwt) async { + JWTDecoded decoded = decodeJWT(jwt); + + // Check the header + if (decoded.header.alg != JWTHeader.JWT_ALG || + decoded.header.typ != JWTHeader.JWT_TYP) { + throw VerifyJWTError( + jwt, + 'JWT must use EdDSA algorithm', + ); + } + + final Uint8List publicKey = decodeIss(decoded.payload.iss); + return ed.verify( + ed.PublicKey(publicKey), + Uint8List.fromList(decoded.data), + Uint8List.fromList(decoded.signature), + ); + // final VerifyKey vKey = VerifyKey(publicKey); + // final SignedMessage signedMessage = SignedMessage.fromList( + // signedMessage: Uint8List.fromList( + // decoded.signature, + // ), + // ); + // return vKey.verify( + // signature: signedMessage.signature, + // message: Uint8List.fromList(decoded.data), + // ); + } + + String stripEquals(String s) { + return s.replaceAll('=', ''); + } + + @override + String encodeJson(Map value) { + return stripEquals( + base64Url.encode( + jsonEncode( + value, + ).codeUnits, + ), + ); + } + + @override + Map decodeJson(String s) { + return jsonDecode( + utf8.decode( + base64Url.decode( + base64Url.normalize( + s, + ), + ), + ), + ); + } + + /// Encodes the public key into a multicodec issuer + @override + String encodeIss(Uint8List publicKey) { + Uint8List header = base58.decode(multicodecEd25519Header); + final String multicodec = + '$multicodecEd25519Base${base58.encode(Uint8List.fromList(header + publicKey))}'; + return [ + DID_PREFIX, + DID_METHOD, + multicodec, + ].join(DID_DELIMITER); + } + + /// Gets the public key from the issuer + @override + Uint8List decodeIss(String issuer) { + List split = issuer.split(DID_DELIMITER); + if (split[0] != DID_PREFIX || split[1] != DID_METHOD) { + throw IssuerDecodeError(issuer, 'Issuer must be a DID with method "key"'); + } + final String multicodec = split[2]; + + // Check the base + String base = multicodec[0]; + if (base != multicodecEd25519Base) { + throw IssuerDecodeError( + issuer, + 'Issuer must be a key in the multicodec format', + ); + } + + // Decode + final Uint8List bytes = base58.decode(multicodec.substring(1)); + + // Check the header + String header = base58.encode(bytes.sublist(0, 2)); + if (header != multicodecEd25519Header) { + throw IssuerDecodeError( + issuer, + 'Issuer must be a public key with type "Ed25519', + ); + } + + // Slice off the public key and validate the length + final Uint8List publicKey = bytes.sublist(2); + if (publicKey.length != multicodecEd25519Length) { + throw IssuerDecodeError( + issuer, + 'Issuer must be public key with length 32 bytes', + ); + } + + return publicKey; + } + + @override + Uint8List encodeData(JWTData params) { + final String data = [ + encodeJson(params.header.toJson()), + encodeJson(params.payload.toJson()), + ].join(JWT_DELIMITER); + + return Uint8List.fromList(utf8.encode(data)); + } + + @override + JWTData decodeData(Uint8List data) { + final List params = utf8.decode(data).split(JWT_DELIMITER); + + JWTHeader header = JWTHeader.fromJson(jsonDecode(params[0])); + JWTPayload payload = JWTPayload.fromJson(jsonDecode(params[1])); + + return JWTData(header, payload); + } + + @override + String encodeSig(Uint8List bytes) { + return stripEquals(base64Url.encode(bytes)); + } + + @override + Uint8List decodeSig(String encoded) { + return Uint8List.fromList(base64Url.decode(base64Url.normalize(encoded))); + } + + @override + String encodeJWT(JWTSigned params) { + return [ + encodeJson(params.header.toJson()), + encodeJson(params.payload.toJson()), + encodeSig(Uint8List.fromList(params.signature)), + ].join(JWT_DELIMITER); + } + + @override + JWTDecoded decodeJWT(String encoded) { + final List params = encoded.split(JWT_DELIMITER); + + JWTHeader header = JWTHeader.fromJson(decodeJson(params[0])); + JWTPayload payload = JWTPayload.fromJson(decodeJson(params[1])); + Uint8List signature = decodeSig(params[2]); + List data = utf8.encode(params.sublist(0, 2).join(JWT_DELIMITER)); + + return JWTDecoded(data, signature, payload, header: header); + } +} diff --git a/packages/reown_core/lib/relay_auth/relay_auth_models.dart b/packages/reown_core/lib/relay_auth/relay_auth_models.dart new file mode 100644 index 0000000..a3de36a --- /dev/null +++ b/packages/reown_core/lib/relay_auth/relay_auth_models.dart @@ -0,0 +1,114 @@ +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'relay_auth_models.g.dart'; + +class RelayAuthKeyPair { + final Uint8List privateKeyBytes; + final Uint8List publicKeyBytes; + final String privateKey; + final String publicKey; + + RelayAuthKeyPair( + this.privateKeyBytes, + this.publicKeyBytes, + ) : privateKey = hex.encode(privateKeyBytes), + publicKey = hex.encode(publicKeyBytes); + + RelayAuthKeyPair.fromStrings( + this.privateKey, + this.publicKey, + ) : privateKeyBytes = Uint8List.fromList(hex.decode(privateKey)), + publicKeyBytes = Uint8List.fromList(hex.decode(publicKey)); +} + +@JsonSerializable() +class JWTHeader { + static const JWT_ALG = 'EdDSA'; + static const JWT_TYP = 'JWT'; + + String alg; + String typ; + + JWTHeader({ + this.alg = 'EdDSA', + this.typ = 'JWT', + }); + + factory JWTHeader.fromJson(Map json) => + _$JWTHeaderFromJson(json); + + Map toJson() => _$JWTHeaderToJson(this); +} + +@JsonSerializable() +class JWTPayload { + String iss; + String sub; + String aud; + int iat; + int exp; + + JWTPayload( + this.iss, + this.sub, + this.aud, + this.iat, + this.exp, + ); + + factory JWTPayload.fromJson(Map json) => + _$JWTPayloadFromJson(json); + + Map toJson() => _$JWTPayloadToJson(this); +} + +class JWTData { + JWTHeader header; + JWTPayload payload; + + JWTData(this.header, this.payload); +} + +class JWTSigned extends JWTData { + List signature; + + JWTSigned( + this.signature, + JWTPayload payload, { + JWTHeader? header, + }) : super(header ?? JWTHeader(), payload); +} + +class JWTDecoded extends JWTSigned { + List data; + + JWTDecoded( + this.data, + List signature, + JWTPayload payload, { + JWTHeader? header, + }) : super(signature, payload, header: header); +} + +class IssuerDecodeError { + String received; + String message; + + IssuerDecodeError( + this.received, + this.message, + ); +} + +class VerifyJWTError { + String jwt; + String message; + + VerifyJWTError( + this.jwt, + this.message, + ); +} diff --git a/packages/reown_core/lib/relay_auth/relay_auth_models.g.dart b/packages/reown_core/lib/relay_auth/relay_auth_models.g.dart new file mode 100644 index 0000000..3f2975f --- /dev/null +++ b/packages/reown_core/lib/relay_auth/relay_auth_models.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'relay_auth_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +JWTHeader _$JWTHeaderFromJson(Map json) => JWTHeader( + alg: json['alg'] as String? ?? 'EdDSA', + typ: json['typ'] as String? ?? 'JWT', + ); + +Map _$JWTHeaderToJson(JWTHeader instance) => { + 'alg': instance.alg, + 'typ': instance.typ, + }; + +JWTPayload _$JWTPayloadFromJson(Map json) => JWTPayload( + json['iss'] as String, + json['sub'] as String, + json['aud'] as String, + (json['iat'] as num).toInt(), + (json['exp'] as num).toInt(), + ); + +Map _$JWTPayloadToJson(JWTPayload instance) => + { + 'iss': instance.iss, + 'sub': instance.sub, + 'aud': instance.aud, + 'iat': instance.iat, + 'exp': instance.exp, + }; diff --git a/packages/reown_core/lib/relay_client/i_message_tracker.dart b/packages/reown_core/lib/relay_client/i_message_tracker.dart new file mode 100644 index 0000000..0a9f0f9 --- /dev/null +++ b/packages/reown_core/lib/relay_client/i_message_tracker.dart @@ -0,0 +1,6 @@ +import 'package:reown_core/store/i_generic_store.dart'; + +abstract class IMessageTracker extends IGenericStore> { + Future recordMessageEvent(String topic, String message); + bool messageIsRecorded(String topic, String message); +} diff --git a/packages/reown_core/lib/relay_client/i_relay_client.dart b/packages/reown_core/lib/relay_client/i_relay_client.dart new file mode 100644 index 0000000..cfc3a2e --- /dev/null +++ b/packages/reown_core/lib/relay_client/i_relay_client.dart @@ -0,0 +1,58 @@ +import 'package:event/event.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; + +class PublishOptions { + final Relay? relay; + final int? ttl; + final bool? prompt; + final int? tag; + + PublishOptions(this.relay, this.ttl, this.prompt, this.tag); +} + +abstract class IRelayClient { + /// Relay Client Events + abstract final Event onRelayClientConnect; + abstract final Event onRelayClientDisconnect; + abstract final Event onRelayClientError; + abstract final Event onRelayClientMessage; + + /// LinkMode Events + abstract final Event onLinkModeMessage; + + /// JSON RPC Events + // Event onJsonRpcPayload(); + // Event onJsonRpcConnect(); + // Event onJsonRpcDisconnect(); + // Event onJsonRpcError(); + + /// Subscriber Events + abstract final Event onSubscriptionCreated; + abstract final Event onSubscriptionDeleted; + // Event onSubscriptionExpired(); + // Event onSubscriptionDisabled(); + abstract final Event onSubscriptionSync; + abstract final Event onSubscriptionResubscribed; + + /// Returns true if the client is connected to a relay server + bool get isConnected; + + Future init(); + + Future publish({ + required String topic, + required String message, + required int ttl, + required int tag, + }); + + Future subscribe({required String topic}); + + Future unsubscribe({required String topic}); + + Future connect({String? relayUrl}); + + Future handleLinkModeMessage(String topic, String message); + + Future disconnect(); +} diff --git a/packages/reown_core/lib/relay_client/json_rpc_2/LICENSE b/packages/reown_core/lib/relay_client/json_rpc_2/LICENSE new file mode 100644 index 0000000..000cd7b --- /dev/null +++ b/packages/reown_core/lib/relay_client/json_rpc_2/LICENSE @@ -0,0 +1,27 @@ +Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/reown_core/lib/relay_client/json_rpc_2/error_code.dart b/packages/reown_core/lib/relay_client/json_rpc_2/error_code.dart new file mode 100644 index 0000000..14e0543 --- /dev/null +++ b/packages/reown_core/lib/relay_client/json_rpc_2/error_code.dart @@ -0,0 +1,57 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// ignore_for_file: constant_identifier_names + +/// Error codes defined in the [JSON-RPC 2.0 specificiation][spec]. +/// +/// These codes are generally used for protocol-level communication. Most of +/// them shouldn't be used by the application. Those that should have +/// convenience constructors in [RpcException]. +/// +/// [spec]: http://www.jsonrpc.org/specification#error_object +/// An error code indicating that invalid JSON was received by the server. +const PARSE_ERROR = -32700; + +/// An error code indicating that the request JSON was invalid according to the +/// JSON-RPC 2.0 spec. +const INVALID_REQUEST = -32600; + +/// An error code indicating that the requested method does not exist or is +/// unavailable. +const METHOD_NOT_FOUND = -32601; + +/// An error code indicating that the request parameters are invalid for the +/// requested method. +const INVALID_PARAMS = -32602; + +/// An internal JSON-RPC error. +const INTERNAL_ERROR = -32603; + +/// An unexpected error occurred on the server. +/// +/// The spec reserves the range from -32000 to -32099 for implementation-defined +/// server exceptions, but for now we only use one of those values. +const SERVER_ERROR = -32000; + +/// Returns a human-readable name for [errorCode] if it's one specified by the +/// JSON-RPC 2.0 spec. +/// +/// If [errorCode] isn't defined in the JSON-RPC 2.0 spec, returns null. +String? name(int errorCode) { + switch (errorCode) { + case PARSE_ERROR: + return 'parse error'; + case INVALID_REQUEST: + return 'invalid request'; + case METHOD_NOT_FOUND: + return 'method not found'; + case INVALID_PARAMS: + return 'invalid parameters'; + case INTERNAL_ERROR: + return 'internal error'; + default: + return null; + } +} diff --git a/packages/reown_core/lib/relay_client/json_rpc_2/src/client.dart b/packages/reown_core/lib/relay_client/json_rpc_2/src/client.dart new file mode 100644 index 0000000..70bde91 --- /dev/null +++ b/packages/reown_core/lib/relay_client/json_rpc_2/src/client.dart @@ -0,0 +1,244 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:stack_trace/stack_trace.dart'; +import 'package:stream_channel/stream_channel.dart'; + +import 'exception.dart'; +import 'utils.dart'; + +/// A JSON-RPC 2.0 client. +/// +/// A client calls methods on a server and handles the server's responses to +/// those method calls. Methods can be called with [sendRequest], or with +/// [sendNotification] if no response is expected. +class Client { + final StreamChannel _channel; + + /// The next request id. + var _id = 0; + + /// The current batch of requests to be sent together. + /// + /// Each element is a JSON RPC spec compliant message. + List>? _batch; + + /// The map of request ids to pending requests. + final _pendingRequests = {}; + + final _done = Completer(); + + /// Returns a [Future] that completes when the underlying connection is + /// closed. + /// + /// This is the same future that's returned by [listen] and [close]. It may + /// complete before [close] is called if the remote endpoint closes the + /// connection. + Future get done => _done.future; + + /// Whether the underlying connection is closed. + /// + /// Note that this will be `true` before [close] is called if the remote + /// endpoint closes the connection. + bool get isClosed => _done.isCompleted; + + /// Creates a [Client] that communicates over [channel]. + /// + /// Note that the client won't begin listening to [responses] until + /// [Client.listen] is called. + Client(StreamChannel channel) + : this.withoutJson( + jsonDocument.bind(channel).transformStream(ignoreFormatExceptions)); + + /// Creates a [Client] that communicates using decoded messages over + /// [channel]. + /// + /// Unlike [Client], this doesn't read or write JSON strings. Instead, it + /// reads and writes decoded maps or lists. + /// + /// Note that the client won't begin listening to [responses] until + /// [Client.listen] is called. + Client.withoutJson(this._channel) { + done.whenComplete(() { + for (var request in _pendingRequests.values) { + request.completer.completeError(StateError( + 'The client closed with pending request "${request.method}".')); + } + _pendingRequests.clear(); + }).catchError((_) { + // Avoid an unhandled error. + }); + } + + /// Starts listening to the underlying stream. + /// + /// Returns a [Future] that will complete when the connection is closed or + /// when it has an error. This is the same as [done]. + /// + /// [listen] may only be called once. + Future listen() { + _channel.stream.listen(_handleResponse, onError: (error, stackTrace) { + _done.completeError(error, stackTrace); + _channel.sink.close(); + }, onDone: () { + if (!_done.isCompleted) _done.complete(); + close(); + }); + return done; + } + + /// Closes the underlying connection. + /// + /// Returns a [Future] that completes when all resources have been released. + /// This is the same as [done]. + Future close() { + _channel.sink.close(); + if (!_done.isCompleted) _done.complete(); + return done; + } + + /// Sends a JSON-RPC 2 request to invoke the given [method]. + /// + /// If passed, [parameters] is the parameters for the method. This must be + /// either an [Iterable] (to pass parameters by position) or a [Map] with + /// [String] keys (to pass parameters by name). Either way, it must be + /// JSON-serializable. + /// + /// If the request succeeds, this returns the response result as a decoded + /// JSON-serializable object. If it fails, it throws an [RpcException] + /// describing the failure. + /// + /// Throws a [StateError] if the client is closed while the request is in + /// flight, or if the client is closed when this method is called. + Future sendRequest(String method, [parameters, int? id]) { + var idAct = id ?? _id++; + _send(method, parameters, idAct); + + var completer = Completer.sync(); + _pendingRequests[idAct] = _Request(method, completer, Chain.current()); + return completer.future; + } + + /// Sends a JSON-RPC 2 request to invoke the given [method] without expecting + /// a response. + /// + /// If passed, [parameters] is the parameters for the method. This must be + /// either an [Iterable] (to pass parameters by position) or a [Map] with + /// [String] keys (to pass parameters by name). Either way, it must be + /// JSON-serializable. + /// + /// Since this is just a notification to which the server isn't expected to + /// send a response, it has no return value. + /// + /// Throws a [StateError] if the client is closed when this method is called. + void sendNotification(String method, [parameters]) => + _send(method, parameters); + + /// A helper method for [sendRequest] and [sendNotification]. + /// + /// Sends a request to invoke [method] with [parameters]. If [id] is given, + /// the request uses that id. + void _send(String method, parameters, [int? id]) { + if (parameters is Iterable) parameters = parameters.toList(); + if (parameters is! Map && parameters is! List && parameters != null) { + throw ArgumentError('Only maps and lists may be used as JSON-RPC ' + 'parameters, was "$parameters".'); + } + if (isClosed) throw StateError('The client is closed.'); + + var message = {'jsonrpc': '2.0', 'method': method}; + if (id != null) message['id'] = id; + if (parameters != null) message['params'] = parameters; + + if (_batch != null) { + _batch!.add(message); + } else { + _channel.sink.add(message); + } + } + + /// Runs [callback] and batches any requests sent until it returns. + /// + /// A batch of requests is sent in a single message on the underlying stream, + /// and the responses are likewise sent back in a single message. + /// + /// [callback] may be synchronous or asynchronous. If it returns a [Future], + /// requests will be batched until that Future returns; otherwise, requests + /// will only be batched while synchronously executing [callback]. + /// + /// If this is called in the context of another [withBatch] call, it just + /// invokes [callback] without creating another batch. This means that + /// responses are batched until the first batch ends. + void withBatch(Function() callback) { + if (_batch != null) { + callback(); + return; + } + + _batch = []; + return tryFinally(callback, () { + _channel.sink.add(_batch); + _batch = null; + }); + } + + /// Handles a decoded response from the server. + void _handleResponse(response) { + if (response is List) { + response.forEach(_handleSingleResponse); + } else { + _handleSingleResponse(response); + } + } + + /// Handles a decoded response from the server after batches have been + /// resolved. + void _handleSingleResponse(response) { + if (!_isResponseValid(response)) return; + var id = response['id']; + id = (id is String) ? int.parse(id) : id; + var request = _pendingRequests.remove(id)!; + if (response.containsKey('result')) { + request.completer.complete(response['result']); + } else { + request.completer.completeError( + RpcException(response['error']['code'], response['error']['message'], + data: response['error']['data']), + request.chain); + } + } + + /// Determines whether the server's response is valid per the spec. + bool _isResponseValid(response) { + if (response is! Map) return false; + if (response['jsonrpc'] != '2.0') return false; + var id = response['id']; + id = (id is String) ? int.parse(id) : id; + if (!_pendingRequests.containsKey(id)) return false; + if (response.containsKey('result')) return true; + + if (!response.containsKey('error')) return false; + var error = response['error']; + if (error is! Map) return false; + if (error['code'] is! int) return false; + if (error['message'] is! String) return false; + return true; + } +} + +/// A pending request to the server. +class _Request { + /// THe method that was sent. + final String method; + + /// The completer to use to complete the response future. + final Completer completer; + + /// The stack chain from where the request was made. + final Chain chain; + + _Request(this.method, this.completer, this.chain); +} diff --git a/packages/reown_core/lib/relay_client/json_rpc_2/src/exception.dart b/packages/reown_core/lib/relay_client/json_rpc_2/src/exception.dart new file mode 100644 index 0000000..ad39ff2 --- /dev/null +++ b/packages/reown_core/lib/relay_client/json_rpc_2/src/exception.dart @@ -0,0 +1,73 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import '../error_code.dart' as error_code; + +/// An exception from a JSON-RPC server that can be translated into an error +/// response. +class RpcException implements Exception { + /// The error code. + /// + /// All non-negative error codes are available for use by application + /// developers. + final int code; + + /// The error message. + /// + /// This should be limited to a concise single sentence. Further information + /// should be supplied via [data]. + final String message; + + /// Extra application-defined information about the error. + /// + /// This must be a JSON-serializable object. If it's a [Map] without a + /// `"request"` key, a copy of the request that caused the error will + /// automatically be injected. + final Object? data; + + RpcException(this.code, this.message, {this.data}); + + /// An exception indicating that the method named [methodName] was not found. + /// + /// This should usually be used only by fallback handlers. + RpcException.methodNotFound(String methodName) + : this(error_code.METHOD_NOT_FOUND, 'Unknown method "$methodName".'); + + /// An exception indicating that the parameters for the requested method were + /// invalid. + /// + /// Methods can use this to reject requests with invalid parameters. + RpcException.invalidParams(String message) + : this(error_code.INVALID_PARAMS, message); + + /// Converts this exception into a JSON-serializable object that's a valid + /// JSON-RPC 2.0 error response. + Map serialize(request) { + dynamic modifiedData; + if (data is Map && !(data as Map).containsKey('request')) { + modifiedData = Map.from(data as Map); + modifiedData['request'] = request; + } else if (data == null) { + modifiedData = {'request': request}; + } else { + modifiedData = data; + } + + var id = request is Map ? request['id'] : null; + if (id is! String && id is! num) id = null; + return { + 'jsonrpc': '2.0', + 'error': {'code': code, 'message': message, 'data': modifiedData}, + 'id': id + }; + } + + @override + String toString() { + var prefix = 'JSON-RPC error $code'; + var errorName = error_code.name(code); + if (errorName != null) prefix += ' ($errorName)'; + return '$prefix: $message'; + } +} diff --git a/packages/reown_core/lib/relay_client/json_rpc_2/src/parameters.dart b/packages/reown_core/lib/relay_client/json_rpc_2/src/parameters.dart new file mode 100644 index 0000000..7a80dea --- /dev/null +++ b/packages/reown_core/lib/relay_client/json_rpc_2/src/parameters.dart @@ -0,0 +1,348 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; + +import 'exception.dart'; + +/// A wrapper for the parameters to a server method. +/// +/// JSON-RPC 2.0 allows parameters that are either a list or a map. This class +/// provides functions that not only assert that the parameters object is the +/// correct type, but also that the expected arguments exist and are themselves +/// the correct type. +/// +/// Example usage: +/// +/// server.registerMethod("subtract", (params) { +/// return params["minuend"].asNum - params["subtrahend"].asNum; +/// }); +class Parameters { + /// The name of the method that this request called. + final String method; + + /// The underlying value of the parameters object. + /// + /// If this is accessed for a [Parameter] that was not passed, the request + /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. + final dynamic value; + + Parameters(this.method, this.value); + + /// Returns a single parameter. + /// + /// If [key] is a [String], the request is expected to provide named + /// parameters. If it's an [int], the request is expected to provide + /// positional parameters. Requests that don't do so will be rejected + /// automatically. + /// + /// Whether or not the given parameter exists, this returns a [Parameter] + /// object. If a parameter's value is accessed through a getter like [value] + /// or [Parameter.asNum], the request will be rejected if that parameter + /// doesn't exist. On the other hand, if it's accessed through a method with a + /// default value like [Parameter.valueOr] or [Parameter.asNumOr], the default + /// value will be returned. + Parameter operator [](key) { + if (key is int) { + _assertPositional(); + if (key < value.length) { + return Parameter._(method, value[key], this, key); + } else { + return _MissingParameter(method, this, key); + } + } else if (key is String) { + _assertNamed(); + if (value.containsKey(key)) { + return Parameter._(method, value[key], this, key); + } else { + return _MissingParameter(method, this, key); + } + } else { + throw ArgumentError('Parameters[] only takes an int or a string, was ' + '"$key".'); + } + } + + /// Asserts that [value] exists and is a [List] and returns it. + List get asList { + _assertPositional(); + return value; + } + + /// Asserts that [value] exists and is a [Map] and returns it. + Map get asMap { + _assertNamed(); + return value; + } + + /// Asserts that [value] is a positional argument list. + void _assertPositional() { + if (value is List) return; + throw RpcException.invalidParams('Parameters for method "$method" ' + 'must be passed by position.'); + } + + /// Asserts that [value] is a named argument map. + void _assertNamed() { + if (value is Map) return; + throw RpcException.invalidParams('Parameters for method "$method" ' + 'must be passed by name.'); + } +} + +/// A wrapper for a single parameter to a server method. +/// +/// This provides numerous functions for asserting the type of the parameter in +/// question. These functions each have a version that asserts that the +/// parameter exists (for example, [asNum] and [asString]) and a version that +/// returns a default value if the parameter doesn't exist (for example, +/// [asNumOr] and [asStringOr]). If an assertion fails, the request is +/// automatically rejected. +/// +/// This extends [Parameters] to make it easy to access nested parameters. For +/// example: +/// +/// // "params.value" is "{'scores': {'home': [5, 10, 17]}}" +/// params['scores']['home'][2].asInt // => 17 +class Parameter extends Parameters { + // The parent parameters, used to construct [_path]. + final Parameters _parent; + + /// The key used to access [this], used to construct [_path]. + final dynamic _key; + + /// A human-readable representation of the path of getters used to get this. + /// + /// Named parameters are represented as `.name`, whereas positional parameters + /// are represented as `[index]`. For example: `"foo[0].bar.baz"`. Named + /// parameters that contain characters that are neither alphanumeric, + /// underscores, or hyphens will be JSON-encoded. For example: `"foo + /// bar"."baz.bang"`. If quotes are used for an individual component, they + /// won't be used for the entire string. + /// + /// An exception is made for single-level parameters. A single-level + /// positional parameter is just represented by the index plus one, because + /// "parameter 1" is clearer than "parameter [0]". A single-level named + /// parameter is represented by that name in quotes. + String get _path { + if (_parent is! Parameter) { + return _key is int ? (_key + 1).toString() : jsonEncode(_key); + } + + String quoteKey(key) { + if (key.contains(RegExp(r'[^a-zA-Z0-9_-]'))) return jsonEncode(key); + return key; + } + + String computePath(params) { + if (params._parent is! Parameter) { + return params._key is int ? '[${params._key}]' : quoteKey(params._key); + } + + var path = computePath(params._parent); + return params._key is int + ? '$path[${params._key}]' + : '$path.${quoteKey(params._key)}'; + } + + return computePath(this); + } + + /// Whether this parameter exists. + bool get exists => true; + + Parameter._(String method, value, this._parent, this._key) + : super(method, value); + + /// Returns [value], or [defaultValue] if this parameter wasn't passed. + dynamic valueOr(defaultValue) => value; + + /// Asserts that [value] exists and is a number and returns it. + /// + /// [asNumOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + num get asNum => _getTyped('a number', (value) => value is num); + + /// Asserts that [value] is a number and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + num asNumOr(num defaultValue) => asNum; + + /// Asserts that [value] exists and is an integer and returns it. + /// + /// [asIntOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + /// + /// Note that which values count as integers varies between the Dart VM and + /// dart2js. The value `1.0` will be considered an integer under dart2js but + /// not under the VM. + int get asInt => _getTyped('an integer', (value) => value is int); + + /// Asserts that [value] is an integer and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + /// + /// Note that which values count as integers varies between the Dart VM and + /// dart2js. The value `1.0` will be considered an integer under dart2js but + /// not under the VM. + int asIntOr(int defaultValue) => asInt; + + /// Asserts that [value] exists and is a boolean and returns it. + /// + /// [asBoolOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + bool get asBool => _getTyped('a boolean', (value) => value is bool); + + /// Asserts that [value] is a boolean and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + bool asBoolOr(bool defaultValue) => asBool; + + /// Asserts that [value] exists and is a string and returns it. + /// + /// [asStringOr] may be used to provide a default value instead of rejecting + /// the request if [value] doesn't exist. + String get asString => _getTyped('a string', (value) => value is String); + + /// Asserts that [value] is a string and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + String asStringOr(String defaultValue) => asString; + + /// Asserts that [value] exists and is a [List] and returns it. + /// + /// [asListOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + @override + List get asList => _getTyped('an Array', (value) => value is List); + + /// Asserts that [value] is a [List] and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + List asListOr(List defaultValue) => asList; + + /// Asserts that [value] exists and is a [Map] and returns it. + /// + /// [asMapOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + @override + Map get asMap => _getTyped('an Object', (value) => value is Map); + + /// Asserts that [value] is a [Map] and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + Map asMapOr(Map defaultValue) => asMap; + + /// Asserts that [value] exists, is a string, and can be parsed as a + /// [DateTime] and returns it. + /// + /// [asDateTimeOr] may be used to provide a default value instead of rejecting + /// the request if [value] doesn't exist. + DateTime get asDateTime => _getParsed('date/time', DateTime.parse); + + /// Asserts that [value] exists, is a string, and can be parsed as a + /// [DateTime] and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + DateTime asDateTimeOr(DateTime defaultValue) => asDateTime; + + /// Asserts that [value] exists, is a string, and can be parsed as a + /// [Uri] and returns it. + /// + /// [asUriOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + Uri get asUri => _getParsed('URI', Uri.parse); + + /// Asserts that [value] exists, is a string, and can be parsed as a + /// [Uri] and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + Uri asUriOr(Uri defaultValue) => asUri; + + /// Get a parameter named [named] that matches [test], or the value of calling + /// [orElse]. + /// + /// [type] is used for the error message. It should begin with an indefinite + /// article. + dynamic _getTyped(String type, bool Function(dynamic) test) { + if (test(value)) return value; + throw RpcException.invalidParams('Parameter $_path for method ' + '"$method" must be $type, but was ${jsonEncode(value)}.'); + } + + dynamic _getParsed(String description, Function(String) parse) { + var string = asString; + try { + return parse(string); + } on FormatException catch (error) { + // DateTime.parse doesn't actually include any useful information in the + // FormatException, just the string that was being parsed. There's no use + // in including that in the RPC exception. See issue 17753. + var message = error.message; + if (message == string) { + message = ''; + } else { + message = '\n$message'; + } + + throw RpcException.invalidParams('Parameter $_path for method ' + '"$method" must be a valid $description, but was ' + '${jsonEncode(string)}.$message'); + } + } + + @override + void _assertPositional() { + // Throw the standard exception for a mis-typed list. + asList; + } + + @override + void _assertNamed() { + // Throw the standard exception for a mis-typed map. + asMap; + } +} + +/// A subclass of [Parameter] representing a missing parameter. +class _MissingParameter extends Parameter { + @override + dynamic get value { + throw RpcException.invalidParams('Request for method "$method" is ' + 'missing required parameter $_path.'); + } + + @override + bool get exists => false; + + _MissingParameter(String method, Parameters parent, key) + : super._(method, null, parent, key); + + @override + dynamic valueOr(defaultValue) => defaultValue; + + @override + num asNumOr(num defaultValue) => defaultValue; + + @override + int asIntOr(int defaultValue) => defaultValue; + + @override + bool asBoolOr(bool defaultValue) => defaultValue; + + @override + String asStringOr(String defaultValue) => defaultValue; + + @override + List asListOr(List defaultValue) => defaultValue; + + @override + Map asMapOr(Map defaultValue) => defaultValue; + + @override + DateTime asDateTimeOr(DateTime defaultValue) => defaultValue; + + @override + Uri asUriOr(Uri defaultValue) => defaultValue; +} diff --git a/packages/reown_core/lib/relay_client/json_rpc_2/src/peer.dart b/packages/reown_core/lib/relay_client/json_rpc_2/src/peer.dart new file mode 100644 index 0000000..b9fada4 --- /dev/null +++ b/packages/reown_core/lib/relay_client/json_rpc_2/src/peer.dart @@ -0,0 +1,156 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:stream_channel/stream_channel.dart'; + +import 'client.dart'; +import 'parameters.dart'; +import 'server.dart'; +import 'utils.dart'; + +/// A JSON-RPC 2.0 client *and* server. +/// +/// This supports bidirectional peer-to-peer communication with another JSON-RPC +/// 2.0 endpoint. It sends both requests and responses across the same +/// communication channel and expects to connect to a peer that does the same. +class Peer implements Client, Server { + final StreamChannel _channel; + + /// The underlying client that handles request-sending and response-receiving + /// logic. + late final Client _client; + + /// The underlying server that handles request-receiving and response-sending + /// logic. + late final Server _server; + + /// A stream controller that forwards incoming messages to [_server] if + /// they're requests. + final _serverIncomingForwarder = StreamController(sync: true); + + /// A stream controller that forwards incoming messages to [_client] if + /// they're responses. + final _clientIncomingForwarder = StreamController(sync: true); + + @override + late final Future done = Future.wait([_client.done, _server.done]); + + @override + bool get isClosed => _client.isClosed || _server.isClosed; + + @override + ErrorCallback? get onUnhandledError => _server.onUnhandledError; + + @override + bool get strictProtocolChecks => _server.strictProtocolChecks; + + /// Creates a [Peer] that communicates over [channel]. + /// + /// Note that the peer won't begin listening to [channel] until [Peer.listen] + /// is called. + /// + /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. + /// If this is not provided, unhandled exceptions will be swallowed. + /// + /// If [strictProtocolChecks] is false, the underlying [Server] will accept + /// some requests which are not conformant with the JSON-RPC 2.0 + /// specification. In particular, requests missing the `jsonrpc` parameter + /// will be accepted. + Peer(StreamChannel channel, + {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) + : this.withoutJson( + jsonDocument.bind(channel).transform(respondToFormatExceptions), + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); + + /// Creates a [Peer] that communicates using decoded messages over [channel]. + /// + /// Unlike [Peer], this doesn't read or write JSON strings. Instead, it + /// reads and writes decoded maps or lists. + /// + /// Note that the peer won't begin listening to [channel] until + /// [Peer.listen] is called. + /// + /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. + /// If this is not provided, unhandled exceptions will be swallowed. + /// + /// If [strictProtocolChecks] is false, the underlying [Server] will accept + /// some requests which are not conformant with the JSON-RPC 2.0 + /// specification. In particular, requests missing the `jsonrpc` parameter + /// will be accepted. + Peer.withoutJson(this._channel, + {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) { + _server = Server.withoutJson( + StreamChannel(_serverIncomingForwarder.stream, _channel.sink), + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); + _client = Client.withoutJson( + StreamChannel(_clientIncomingForwarder.stream, _channel.sink)); + } + + // Client methods. + + @override + Future sendRequest(String method, [parameters, int? id]) => + _client.sendRequest(method, parameters, id); + + @override + void sendNotification(String method, [parameters]) => + _client.sendNotification(method, parameters); + + @override + void withBatch(Function() callback) => _client.withBatch(callback); + + // Server methods. + + @override + void registerMethod(String name, Function callback) => + _server.registerMethod(name, callback); + + @override + void registerFallback(Function(Parameters parameters) callback) => + _server.registerFallback(callback); + + // Shared methods. + + @override + Future listen() { + _client.listen(); + _server.listen(); + _channel.stream.listen((message) { + if (message is Map) { + if (message.containsKey('result') || message.containsKey('error')) { + _clientIncomingForwarder.add(message); + } else { + _serverIncomingForwarder.add(message); + } + } else if (message is List && + message.isNotEmpty && + message.first is Map) { + if (message.first.containsKey('result') || + message.first.containsKey('error')) { + _clientIncomingForwarder.add(message); + } else { + _serverIncomingForwarder.add(message); + } + } else { + // Non-Map and -List messages are ill-formed, so we pass them to the + // server since it knows how to send error responses. + _serverIncomingForwarder.add(message); + } + }, onError: (error, stackTrace) { + _serverIncomingForwarder.addError(error, stackTrace); + }, onDone: close); + return done; + } + + @override + Future close() { + _client.close(); + _server.close(); + return done; + } +} diff --git a/packages/reown_core/lib/relay_client/json_rpc_2/src/server.dart b/packages/reown_core/lib/relay_client/json_rpc_2/src/server.dart new file mode 100644 index 0000000..7d8202e --- /dev/null +++ b/packages/reown_core/lib/relay_client/json_rpc_2/src/server.dart @@ -0,0 +1,319 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert'; + +import 'package:stack_trace/stack_trace.dart'; +import 'package:stream_channel/stream_channel.dart'; + +import '../error_code.dart' as error_code; +import 'exception.dart'; +import 'parameters.dart'; +import 'utils.dart'; + +/// A callback for unhandled exceptions. +typedef ErrorCallback = void Function(dynamic error, dynamic stackTrace); + +/// A JSON-RPC 2.0 server. +/// +/// A server exposes methods that are called by requests, to which it provides +/// responses. Methods can be registered using [registerMethod] and +/// [registerFallback]. Requests can be handled using [handleRequest] and +/// [parseRequest]. +/// +/// Note that since requests can arrive asynchronously and methods can run +/// asynchronously, it's possible for multiple methods to be invoked at the same +/// time, or even for a single method to be invoked multiple times at once. +class Server { + final StreamChannel _channel; + + /// The methods registered for this server. + final _methods = {}; + + /// The fallback methods for this server. + /// + /// These are tried in order until one of them doesn't throw a + /// [RpcException.methodNotFound] exception. + final _fallbacks = Queue(); + + final _done = Completer(); + + /// Returns a [Future] that completes when the underlying connection is + /// closed. + /// + /// This is the same future that's returned by [listen] and [close]. It may + /// complete before [close] is called if the remote endpoint closes the + /// connection. + Future get done => _done.future; + + /// Whether the underlying connection is closed. + /// + /// Note that this will be `true` before [close] is called if the remote + /// endpoint closes the connection. + bool get isClosed => _done.isCompleted; + + /// A callback that is fired on unhandled exceptions. + /// + /// In the case where a user provided callback results in an exception that + /// cannot be properly routed back to the client, this handler will be + /// invoked. If it is not set, the exception will be swallowed. + final ErrorCallback? onUnhandledError; + + /// Whether to strictly enforce the JSON-RPC 2.0 specification for received + /// messages. + /// + /// If `false`, this [Server] will accept some requests which are not + /// conformant with the JSON-RPC 2.0 specification. In particular, requests + /// missing the `jsonrpc` parameter will be accepted. + final bool strictProtocolChecks; + + /// Creates a [Server] that communicates over [channel]. + /// + /// Note that the server won't begin listening to [requests] until + /// [Server.listen] is called. + /// + /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. + /// If this is not provided, unhandled exceptions will be swallowed. + /// + /// If [strictProtocolChecks] is false, this [Server] will accept some + /// requests which are not conformant with the JSON-RPC 2.0 specification. In + /// particular, requests missing the `jsonrpc` parameter will be accepted. + Server(StreamChannel channel, + {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) + : this.withoutJson( + jsonDocument.bind(channel).transform(respondToFormatExceptions), + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); + + /// Creates a [Server] that communicates using decoded messages over + /// [channel]. + /// + /// Unlike [Server], this doesn't read or write JSON strings. Instead, it + /// reads and writes decoded maps or lists. + /// + /// Note that the server won't begin listening to [requests] until + /// [Server.listen] is called. + /// + /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. + /// If this is not provided, unhandled exceptions will be swallowed. + /// + /// If [strictProtocolChecks] is false, this [Server] will accept some + /// requests which are not conformant with the JSON-RPC 2.0 specification. In + /// particular, requests missing the `jsonrpc` parameter will be accepted. + Server.withoutJson(this._channel, + {this.onUnhandledError, this.strictProtocolChecks = true}); + + /// Starts listening to the underlying stream. + /// + /// Returns a [Future] that will complete when the connection is closed or + /// when it has an error. This is the same as [done]. + /// + /// [listen] may only be called once. + Future listen() { + _channel.stream.listen(_handleRequest, onError: (error, stackTrace) { + _done.completeError(error, stackTrace); + _channel.sink.close(); + }, onDone: () { + if (!_done.isCompleted) _done.complete(); + }); + return done; + } + + /// Closes the underlying connection. + /// + /// Returns a [Future] that completes when all resources have been released. + /// This is the same as [done]. + Future close() { + _channel.sink.close(); + if (!_done.isCompleted) _done.complete(); + return done; + } + + /// Registers a method named [name] on this server. + /// + /// [callback] can take either zero or one arguments. If it takes zero, any + /// requests for that method that include parameters will be rejected. If it + /// takes one, it will be passed a [Parameters] object. + /// + /// [callback] can return either a JSON-serializable object or a Future that + /// completes to a JSON-serializable object. Any errors in [callback] will be + /// reported to the client as JSON-RPC 2.0 errors. + void registerMethod(String name, Function callback) { + if (_methods.containsKey(name)) { + throw ArgumentError('There\'s already a method named "$name".'); + } + + _methods[name] = callback; + } + + /// Registers a fallback method on this server. + /// + /// A server may have any number of fallback methods. When a request comes in + /// that doesn't match any named methods, each fallback is tried in order. A + /// fallback can pass on handling a request by throwing a + /// [RpcException.methodNotFound] exception. + /// + /// [callback] can return either a JSON-serializable object or a Future that + /// completes to a JSON-serializable object. Any errors in [callback] will be + /// reported to the client as JSON-RPC 2.0 errors. [callback] may send custom + /// errors by throwing an [RpcException]. + void registerFallback(Function(Parameters parameters) callback) { + _fallbacks.add(callback); + } + + /// Handle a request. + /// + /// [request] is expected to be a JSON-serializable object representing a + /// request sent by a client. This calls the appropriate method or methods for + /// handling that request and returns a JSON-serializable response, or `null` + /// if no response should be sent. [callback] may send custom + /// errors by throwing an [RpcException]. + Future _handleRequest(request) async { + dynamic response; + if (request is List) { + if (request.isEmpty) { + response = RpcException(error_code.INVALID_REQUEST, + 'A batch must contain at least one request.') + .serialize(request); + } else { + var results = await Future.wait(request.map(_handleSingleRequest)); + var nonNull = results.where((result) => result != null); + if (nonNull.isEmpty) return; + response = nonNull.toList(); + } + } else { + response = await _handleSingleRequest(request); + if (response == null) return; + } + + if (!isClosed) _channel.sink.add(response); + } + + /// Handles an individual parsed request. + Future _handleSingleRequest(request) async { + try { + _validateRequest(request); + + var name = request['method']; + var method = _methods[name]; + method ??= _tryFallbacks; + + Object? result; + if (method is ZeroArgumentFunction) { + if (request.containsKey('params')) { + throw RpcException.invalidParams('No parameters are allowed for ' + 'method "$name".'); + } + result = await method(); + } else { + result = await method(Parameters(name, request['params'])); + } + + // A request without an id is a notification, which should not be sent a + // response, even if one is generated on the server. + if (!request.containsKey('id')) return null; + + return {'jsonrpc': '2.0', 'result': result, 'id': request['id']}; + } catch (error, stackTrace) { + if (error is RpcException) { + if (error.code == error_code.INVALID_REQUEST || + request.containsKey('id')) { + return error.serialize(request); + } else { + onUnhandledError?.call(error, stackTrace); + return null; + } + } else if (!request.containsKey('id')) { + onUnhandledError?.call(error, stackTrace); + return null; + } + final chain = Chain.forTrace(stackTrace); + return RpcException(error_code.SERVER_ERROR, getErrorMessage(error), + data: { + 'full': '$error', + 'stack': '$chain', + }).serialize(request); + } + } + + /// Validates that [request] matches the JSON-RPC spec. + void _validateRequest(request) { + if (request is! Map) { + throw RpcException( + error_code.INVALID_REQUEST, + 'Request must be ' + 'an Array or an Object.'); + } + + if (strictProtocolChecks && !request.containsKey('jsonrpc')) { + throw RpcException( + error_code.INVALID_REQUEST, + 'Request must ' + 'contain a "jsonrpc" key.'); + } + + if ((strictProtocolChecks || request.containsKey('jsonrpc')) && + request['jsonrpc'] != '2.0') { + throw RpcException( + error_code.INVALID_REQUEST, + 'Invalid JSON-RPC ' + 'version ${jsonEncode(request['jsonrpc'])}, expected "2.0".'); + } + + if (!request.containsKey('method')) { + throw RpcException( + error_code.INVALID_REQUEST, + 'Request must ' + 'contain a "method" key.'); + } + + var method = request['method']; + if (request['method'] is! String) { + throw RpcException( + error_code.INVALID_REQUEST, + 'Request method must ' + 'be a string, but was ${jsonEncode(method)}.'); + } + + if (request.containsKey('params')) { + var params = request['params']; + if (params is! List && params is! Map) { + throw RpcException( + error_code.INVALID_REQUEST, + 'Request params must ' + 'be an Array or an Object, but was ${jsonEncode(params)}.'); + } + } + + var id = request['id']; + if (id != null && id is! String && id is! num) { + throw RpcException( + error_code.INVALID_REQUEST, + 'Request id must be a ' + 'string, number, or null, but was ${jsonEncode(id)}.'); + } + } + + /// Try all the fallback methods in order. + Future _tryFallbacks(Parameters params) { + var iterator = _fallbacks.toList().iterator; + + Future tryNext() async { + if (!iterator.moveNext()) { + throw RpcException.methodNotFound(params.method); + } + + try { + return await iterator.current(params); + } on RpcException catch (error) { + if (error.code != error_code.METHOD_NOT_FOUND) rethrow; + return tryNext(); + } + } + + return tryNext(); + } +} diff --git a/packages/reown_core/lib/relay_client/json_rpc_2/src/utils.dart b/packages/reown_core/lib/relay_client/json_rpc_2/src/utils.dart new file mode 100644 index 0000000..305945c --- /dev/null +++ b/packages/reown_core/lib/relay_client/json_rpc_2/src/utils.dart @@ -0,0 +1,70 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:stream_channel/stream_channel.dart'; + +import '../error_code.dart' as error_code; +import 'exception.dart'; + +typedef ZeroArgumentFunction = Function(); + +/// A regular expression to match the exception prefix that some exceptions' +/// [Object.toString] values contain. +final _exceptionPrefix = RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); + +/// Get a string description of an exception. +/// +/// Many exceptions include the exception class name at the beginning of their +/// [toString], so we remove that if it exists. +String getErrorMessage(error) => + error.toString().replaceFirst(_exceptionPrefix, ''); + +/// Like `try`/`finally`, run [body] and ensure that [whenComplete] runs +/// afterwards, regardless of whether [body] succeeded. +/// +/// This is synchronicity-agnostic relative to [body]. If [body] returns a +/// [Future], this wil run asynchronously; otherwise it will run synchronously. +void tryFinally(Function() body, Function() whenComplete) { + dynamic result; + try { + result = body(); + } catch (_) { + whenComplete(); + rethrow; + } + + if (result is! Future) { + whenComplete(); + } else { + result.whenComplete(whenComplete); + } +} + +/// A transformer that silently drops [FormatException]s. +final ignoreFormatExceptions = StreamTransformer.fromHandlers( + handleError: (error, stackTrace, sink) { + if (error is FormatException) return; + sink.addError(error, stackTrace); +}); + +/// A transformer that sends error responses on [FormatException]s. +final StreamChannelTransformer respondToFormatExceptions = + _RespondToFormatExceptionsTransformer(); + +class _RespondToFormatExceptionsTransformer + implements StreamChannelTransformer { + @override + StreamChannel bind(StreamChannel channel) { + return channel.changeStream((stream) { + return stream.handleError((dynamic error) { + final formatException = error as FormatException; + var exception = RpcException( + error_code.PARSE_ERROR, 'Invalid JSON: ${formatException.message}'); + channel.sink.add(exception.serialize(formatException.source)); + }, test: (error) => error is FormatException); + }); + } +} diff --git a/packages/reown_core/lib/relay_client/message_tracker.dart b/packages/reown_core/lib/relay_client/message_tracker.dart new file mode 100644 index 0000000..f2c058e --- /dev/null +++ b/packages/reown_core/lib/relay_client/message_tracker.dart @@ -0,0 +1,52 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:pointycastle/digests/sha256.dart'; +import 'package:reown_core/relay_client/i_message_tracker.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/store_models.dart'; + +class MessageTracker extends GenericStore> + implements IMessageTracker { + MessageTracker({ + required super.storage, + required super.context, + required super.version, + required super.fromJson, + }); + + String hashMessage(String message) { + return hex.encode( + SHA256Digest().process( + Uint8List.fromList( + utf8.encode(message), + ), + ), + ); + } + + @override + Future recordMessageEvent(String topic, String message) async { + final String hash = hashMessage(message); + + onCreate.broadcast( + StoreCreateEvent( + topic, + {hash: message}, + ), + ); + + if (!data.containsKey(topic)) { + data[topic] = {}; + } + data[topic]![hash] = message; + await persist(); + } + + @override + bool messageIsRecorded(String topic, String message) { + final String hash = hashMessage(message); + return data.containsKey(topic) && data[topic]!.containsKey(hash); + } +} diff --git a/packages/reown_core/lib/relay_client/relay_client.dart b/packages/reown_core/lib/relay_client/relay_client.dart new file mode 100644 index 0000000..63f9f7e --- /dev/null +++ b/packages/reown_core/lib/relay_client/relay_client.dart @@ -0,0 +1,500 @@ +import 'dart:async'; + +import 'package:event/event.dart'; +import 'package:reown_core/i_core_impl.dart'; +import 'package:reown_core/pairing/utils/json_rpc_utils.dart'; +import 'package:reown_core/relay_client/i_message_tracker.dart'; +import 'package:reown_core/relay_client/i_relay_client.dart'; +import 'package:reown_core/relay_client/json_rpc_2/src/parameters.dart'; +import 'package:reown_core/relay_client/json_rpc_2/src/peer.dart'; +import 'package:reown_core/relay_client/websocket/i_websocket_handler.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_core/utils/utils.dart'; + +import 'package:reown_core/models/basic_models.dart'; +import 'package:reown_core/utils/constants.dart'; +import 'package:reown_core/utils/errors.dart'; + +class RelayClient implements IRelayClient { + static const JSON_RPC_PUBLISH = 'publish'; + static const JSON_RPC_SUBSCRIPTION = 'subscription'; + static const JSON_RPC_SUBSCRIBE = 'subscribe'; + static const JSON_RPC_UNSUBSCRIBE = 'unsubscribe'; + + /// Events /// + /// Relay Client + + @override + final Event onRelayClientConnect = Event(); + + @override + final Event onRelayClientDisconnect = Event(); + + @override + final Event onRelayClientError = Event(); + + @override + final Event onRelayClientMessage = Event(); + + @override + final Event onLinkModeMessage = Event(); + + /// Subscriptions + @override + final Event onSubscriptionCreated = + Event(); + + @override + final Event onSubscriptionDeleted = + Event(); + + @override + final Event onSubscriptionResubscribed = Event(); + + @override + final Event onSubscriptionSync = Event(); + + @override + bool get isConnected => jsonRPC != null && !jsonRPC!.isClosed; + + bool get _relayIsClosed => jsonRPC != null && jsonRPC!.isClosed; + + bool _initialized = false; + bool _active = true; + bool _connecting = false; + Future _connectingFuture = Future.value(); + bool _handledClose = false; + + // late WebSocketChannel socket; + // IWebSocketHandler? socket; + Peer? jsonRPC; + + /// Stores all the subs that haven't been completed + Map> pendingSubscriptions = {}; + + IMessageTracker messageTracker; + IGenericStore topicMap; + final IWebSocketHandler socketHandler; + + IReownCore core; + + bool _subscribedToHeartbeat = false; + + RelayClient({ + required this.core, + required this.messageTracker, + required this.topicMap, + IWebSocketHandler? socketHandler, + }) : socketHandler = socketHandler ?? WebSocketHandler(); + + @override + Future init() async { + if (_initialized) { + return; + } + + await messageTracker.init(); + await topicMap.init(); + + // Setup the json RPC server + await _connect(); + _subscribeToHeartbeat(); + + _initialized = true; + } + + @override + Future publish({ + required String topic, + required String message, + required int ttl, + required int tag, + }) async { + _checkInitialized(); + + Map data = { + 'message': message, + 'ttl': ttl, + 'topic': topic, + 'tag': tag, + }; + + try { + await messageTracker.recordMessageEvent(topic, message); + var _ = await _sendJsonRpcRequest( + _buildMethod(JSON_RPC_PUBLISH), + data, + JsonRpcUtils.payloadId(entropy: 6), + ); + } catch (e) { + // print(e); + onRelayClientError.broadcast(ErrorEvent(e)); + } + } + + @override + Future subscribe({required String topic}) async { + _checkInitialized(); + + pendingSubscriptions[topic] = _onSubscribe(topic); + + return await pendingSubscriptions[topic]; + } + + @override + Future unsubscribe({required String topic}) async { + _checkInitialized(); + + String id = topicMap.get(topic) ?? ''; + + try { + await _sendJsonRpcRequest( + _buildMethod(JSON_RPC_UNSUBSCRIBE), + { + 'topic': topic, + 'id': id, + }, + JsonRpcUtils.payloadId(entropy: 6), + ); + } catch (e) { + onRelayClientError.broadcast(ErrorEvent(e)); + } + + // Remove the subscription + pendingSubscriptions.remove(topic); + await topicMap.delete(topic); + + // Delete all the messages + await messageTracker.delete(topic); + } + + @override + Future connect({String? relayUrl}) async { + _checkInitialized(); + + core.logger.i('[$runtimeType]: Connecting to relay'); + + await _connect(relayUrl: relayUrl); + } + + @override + Future disconnect() async { + _checkInitialized(); + + core.logger.i('[$runtimeType]: Disconnecting from relay'); + + await _disconnect(); + } + + /// PRIVATE FUNCTIONS /// + + Future _connect({String? relayUrl}) async { + core.logger + .t('[$runtimeType]: _connect $relayUrl, isConnected: $isConnected'); + if (isConnected) { + return; + } + + core.relayUrl = relayUrl ?? core.relayUrl; + core.logger.d('[$runtimeType] Connecting to relay url ${core.relayUrl}'); + + // If we have tried connecting to the relay before, disconnect + if (_active) { + await _disconnect(); + } + + try { + // Connect and track the connection progress, then start the heartbeat + _connectingFuture = _createJsonRPCProvider(); + await _connectingFuture; + _connecting = false; + _subscribeToHeartbeat(); + // + } on TimeoutException catch (e) { + core.logger.d('[$runtimeType]: Connect timeout: $e'); + onRelayClientError.broadcast(ErrorEvent('Connection to relay timeout')); + _connecting = false; + _connect(); + } catch (e) { + core.logger.d('[$runtimeType]: Connect error: $e'); + onRelayClientError.broadcast(ErrorEvent(e)); + _connecting = false; + } + } + + Future _disconnect() async { + core.logger.t('[$runtimeType]: _disconnecting from relay'); + _active = false; + + final bool shouldBroadcastDisonnect = isConnected; + + await jsonRPC?.close(); + jsonRPC = null; + await socketHandler.close(); + _unsubscribeToHeartbeat(); + + if (shouldBroadcastDisonnect) { + onRelayClientDisconnect.broadcast(); + } + } + + Future _createJsonRPCProvider() async { + _connecting = true; + _active = true; + final auth = await core.crypto.signJWT(core.relayUrl); + core.logger.t('[$runtimeType]: Signed JWT: $auth'); + final url = ReownCoreUtils.formatRelayRpcUrl( + protocol: ReownConstants.CORE_PROTOCOL, + version: ReownConstants.CORE_VERSION, + relayUrl: core.relayUrl, + sdkVersion: ReownConstants.SDK_VERSION, + auth: auth, + projectId: core.projectId, + packageName: (await ReownCoreUtils.getPackageName()), + ); + + if (jsonRPC != null) { + await jsonRPC!.close(); + jsonRPC = null; + } + + core.logger.t('[$runtimeType]: Initializing WebSocket with $url'); + await socketHandler.setup(url: url); + await socketHandler.connect().timeout(Duration(seconds: 5)); + + jsonRPC = Peer(socketHandler.channel!); + + jsonRPC!.registerMethod( + _buildMethod(JSON_RPC_SUBSCRIPTION), + _handleSubscription, + ); + jsonRPC!.registerMethod( + _buildMethod(JSON_RPC_SUBSCRIBE), + _handleSubscribe, + ); + jsonRPC!.registerMethod( + _buildMethod(JSON_RPC_UNSUBSCRIBE), + _handleUnsubscribe, + ); + + if (jsonRPC!.isClosed) { + throw const ReownCoreError( + code: 0, + message: 'WebSocket closed', + ); + } + + jsonRPC!.listen(); + + // When jsonRPC closes, emit the event + _handledClose = false; + jsonRPC!.done.then( + (value) { + _handleRelayClose( + socketHandler.closeCode, + socketHandler.closeReason, + ); + }, + ); + + onRelayClientConnect.broadcast(); + core.logger.d('[$runtimeType]: Connected to relay ${core.relayUrl}'); + } + + Future _handleRelayClose(int? code, String? reason) async { + if (_handledClose) { + core.logger.i('[$runtimeType]: Relay close already handled'); + return; + } + _handledClose = true; + + core.logger.i( + '[$runtimeType]: Handling relay close, code: $code, reason: $reason'); + // If the relay isn't active (Disconnected manually), don't do anything + if (!_active) { + return; + } + + // If the code requires reconnect, do so + final reconnectCodes = [1001, 4008, 4010, 1002, 1005, 10002]; + if (code != null) { + if (reconnectCodes.contains(code)) { + await connect(); + } else { + await disconnect(); + final errorReason = code == 3000 + ? reason ?? WebSocketErrors.INVALID_PROJECT_ID_OR_JWT + : ''; + onRelayClientError.broadcast( + ErrorEvent(ReownCoreError( + code: code, + message: errorReason, + )), + ); + } + } + } + + void _subscribeToHeartbeat() { + if (!_subscribedToHeartbeat) { + core.heartbeat.onPulse.subscribe(_heartbeatSubscription); + _subscribedToHeartbeat = true; + } + } + + void _unsubscribeToHeartbeat() { + core.heartbeat.onPulse.unsubscribe(_heartbeatSubscription); + _subscribedToHeartbeat = false; + } + + void _heartbeatSubscription(EventArgs? args) async { + if (_relayIsClosed) { + await _handleRelayClose(10002, null); + } + } + + String _buildMethod(String method) { + return '${ReownConstants.RELAYER_DEFAULT_PROTOCOL}_$method'; + } + + // This method could be placed directly into pairings API but it's place here for consistency with onRelayClientMessage + @override + Future handleLinkModeMessage(String topic, String message) async { + core.logger.t('[$runtimeType]: handleLinkModeMessage: $topic, $message'); + + // if client calls dispatchEnvelope with the same message more than once we do nothing. + final recorded = messageTracker.messageIsRecorded(topic, message); + if (recorded) return true; + + // Broadcast the message + onLinkModeMessage.broadcast( + MessageEvent( + topic, + message, + DateTime.now().millisecondsSinceEpoch, + TransportType.linkMode, + ), + ); + return true; + } + + /// JSON RPC MESSAGE HANDLERS + + Future handlePublish(String topic, String message) async { + core.logger.t('[$runtimeType]: Handling Publish Message: $topic, $message'); + // If we want to ignore the message, stop + if (await _shouldIgnoreMessageEvent(topic, message)) { + core.logger.w('[$runtimeType]: Ignoring Message: $topic, $message'); + return false; + } + + // Record a message event + await messageTracker.recordMessageEvent(topic, message); + + // Broadcast the message + onRelayClientMessage.broadcast( + MessageEvent( + topic, + message, + DateTime.now().millisecondsSinceEpoch, + TransportType.relay, + ), + ); + return true; + } + + Future _handleSubscription(Parameters params) async { + String topic = params['data']['topic'].value; + String message = params['data']['message'].value; + return await handlePublish(topic, message); + } + + int _handleSubscribe(Parameters params) { + return params.hashCode; + } + + void _handleUnsubscribe(Parameters params) { + core.logger.i('[$runtimeType]: handle unsubscribe $params'); + } + + /// MESSAGE HANDLING + + Future _shouldIgnoreMessageEvent(String topic, String message) async { + if (!await _isSubscribed(topic)) return true; + return messageTracker.messageIsRecorded(topic, message); + } + + /// SUBSCRIPTION HANDLING + + Future _sendJsonRpcRequest( + String method, [ + dynamic parameters, + int? id, + ]) async { + // If we are connected and we know it send the message! + if (isConnected) { + // Here so we dont return null + } + // If we are connecting, then wait for the connection to establish and then send the message + else if (_connecting) { + await _connectingFuture; + } + // If we aren't connected but should be (active), try to (re)connect and then send the message + else if (!isConnected && _active) { + await connect(); + } + // In all other cases return null + else { + return null; + } + return await jsonRPC!.sendRequest( + method, + parameters, + id, + ); + } + + Future _onSubscribe(String topic) async { + String? requestId; + try { + requestId = await _sendJsonRpcRequest( + _buildMethod(JSON_RPC_SUBSCRIBE), + {'topic': topic}, + JsonRpcUtils.payloadId(entropy: 6), + ); + } catch (e) { + core.logger.w('RelayClient, onSubscribe error. Topic: $topic, Error: $e'); + onRelayClientError.broadcast(ErrorEvent(e)); + } + + if (requestId == null) { + return ''; + } + + await topicMap.set(topic, requestId.toString()); + pendingSubscriptions.remove(topic); + + return requestId; + } + + Future _isSubscribed(String topic) async { + if (topicMap.has(topic)) { + return true; + } + + if (pendingSubscriptions.containsKey(topic)) { + await pendingSubscriptions[topic]; + return topicMap.has(topic); + } + + return false; + } + + void _checkInitialized() { + if (!_initialized) { + throw Errors.getInternalError(Errors.NOT_INITIALIZED); + } + } +} diff --git a/packages/reown_core/lib/relay_client/relay_client_models.dart b/packages/reown_core/lib/relay_client/relay_client_models.dart new file mode 100644 index 0000000..85295da --- /dev/null +++ b/packages/reown_core/lib/relay_client/relay_client_models.dart @@ -0,0 +1,77 @@ +import 'dart:convert'; + +import 'package:event/event.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'relay_client_models.g.dart'; + +enum TransportType { + relay, + linkMode; + + bool get isLinkMode => this == linkMode; +} + +@JsonSerializable() +class Relay { + final String protocol; + final String? data; + + Relay( + this.protocol, { + this.data, + }); + + factory Relay.fromJson(Map json) => _$RelayFromJson(json); + + Map toJson() => _$RelayToJson(this); +} + +class MessageEvent extends EventArgs { + String topic; + String message; + int publishedAt; + TransportType transportType; + + MessageEvent( + this.topic, + this.message, + this.publishedAt, + this.transportType, + ); + + Map toJson() => { + 'topic': topic, + 'message': message, + 'publishedAt': publishedAt, + 'transportType': transportType.name, + }; + + @override + String toString() => 'MessageEvent(${jsonEncode(toJson())})'; +} + +class ErrorEvent extends EventArgs { + dynamic error; + + ErrorEvent( + this.error, + ); +} + +class SubscriptionEvent extends EventArgs { + String id; + + SubscriptionEvent( + this.id, + ); +} + +class SubscriptionDeletionEvent extends SubscriptionEvent { + String reason; + + SubscriptionDeletionEvent( + id, + this.reason, + ) : super(id); +} diff --git a/packages/reown_core/lib/relay_client/relay_client_models.g.dart b/packages/reown_core/lib/relay_client/relay_client_models.g.dart new file mode 100644 index 0000000..4d076e8 --- /dev/null +++ b/packages/reown_core/lib/relay_client/relay_client_models.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'relay_client_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Relay _$RelayFromJson(Map json) => Relay( + json['protocol'] as String, + data: json['data'] as String?, + ); + +Map _$RelayToJson(Relay instance) => { + 'protocol': instance.protocol, + 'data': instance.data, + }; diff --git a/packages/reown_core/lib/relay_client/websocket/http_client.dart b/packages/reown_core/lib/relay_client/websocket/http_client.dart new file mode 100644 index 0000000..1d6a54d --- /dev/null +++ b/packages/reown_core/lib/relay_client/websocket/http_client.dart @@ -0,0 +1,28 @@ +import 'package:http/http.dart' as http; +import 'package:reown_core/relay_client/websocket/i_http_client.dart'; + +class HttpWrapper extends IHttpClient { + const HttpWrapper(); + + @override + Future get( + Uri url, { + Map? headers, + }) async { + return await http.get(url, headers: headers); + } + + @override + Future delete(Uri url, {Map? headers}) async { + return await http.delete(url, headers: headers); + } + + @override + Future post( + Uri url, { + Map? headers, + Object? body, + }) async { + return await http.post(url, headers: headers, body: body); + } +} diff --git a/packages/reown_core/lib/relay_client/websocket/i_http_client.dart b/packages/reown_core/lib/relay_client/websocket/i_http_client.dart new file mode 100644 index 0000000..824589c --- /dev/null +++ b/packages/reown_core/lib/relay_client/websocket/i_http_client.dart @@ -0,0 +1,21 @@ +import 'package:http/http.dart' as http; + +abstract class IHttpClient { + const IHttpClient(); + + Future get( + Uri url, { + Map? headers, + }); + + Future post( + Uri url, { + Map? headers, + Object? body, + }); + + Future delete( + Uri url, { + Map? headers, + }); +} diff --git a/packages/reown_core/lib/relay_client/websocket/i_websocket_handler.dart b/packages/reown_core/lib/relay_client/websocket/i_websocket_handler.dart new file mode 100644 index 0000000..3ba7472 --- /dev/null +++ b/packages/reown_core/lib/relay_client/websocket/i_websocket_handler.dart @@ -0,0 +1,20 @@ +import 'dart:async'; + +import 'package:stream_channel/stream_channel.dart'; + +abstract class IWebSocketHandler { + String? get url; + + int? get closeCode; + String? get closeReason; + + StreamChannel? get channel; + + Future get ready; + + Future setup({required String url}); + + Future connect(); + + Future close(); +} diff --git a/packages/reown_core/lib/relay_client/websocket/websocket_handler.dart b/packages/reown_core/lib/relay_client/websocket/websocket_handler.dart new file mode 100644 index 0000000..e41733d --- /dev/null +++ b/packages/reown_core/lib/relay_client/websocket/websocket_handler.dart @@ -0,0 +1,90 @@ +import 'dart:async'; + +import 'package:stream_channel/stream_channel.dart'; +import 'package:reown_core/relay_client/websocket/i_websocket_handler.dart'; +import 'package:reown_core/models/basic_models.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +class WebSocketHandler implements IWebSocketHandler { + String? _url; + @override + String? get url => _url; + + WebSocketChannel? _socket; + + @override + int? get closeCode => _socket?.closeCode; + @override + String? get closeReason => _socket?.closeReason; + + StreamChannel? _channel; + @override + StreamChannel? get channel => _channel; + + @override + Future get ready => _socket!.ready; + + // const WebSocketHandler(); + + @override + Future setup({ + required String url, + }) async { + _url = url; + + await close(); + } + + @override + Future connect() async { + // print('connecting'); + try { + _socket = WebSocketChannel.connect( + Uri.parse( + '$url&useOnCloseEvent=true', + ), + ); + } catch (e) { + throw ReownCoreError( + code: -1, + message: 'No internet connection: ${e.toString()}', + ); + } + + _channel = _socket!.cast(); + + if (_channel == null) { + // print('Socket channel is null, waiting...'); + await Future.delayed(const Duration(milliseconds: 500)); + if (_channel == null) { + // print('Socket channel is still null, throwing '); + throw Exception('Socket channel is null'); + } + } + + await _socket!.ready; + + // Check if the request was successful (status code 200) + // try {} catch (e) { + // throw ReownCoreError( + // code: 400, + // message: 'WebSocket connection failed, missing or invalid project id.', + // ); + // } + } + + @override + Future close() async { + try { + if (_socket != null) { + await _socket?.sink.close(); + } + } catch (_) {} + _socket = null; + } + + @override + String toString() { + return 'WebSocketHandler{url: $url, _socket: $_socket, _channel: $_channel}'; + } +} diff --git a/packages/reown_core/lib/reown_core.dart b/packages/reown_core/lib/reown_core.dart new file mode 100644 index 0000000..5735e42 --- /dev/null +++ b/packages/reown_core/lib/reown_core.dart @@ -0,0 +1,20 @@ +library reown_core; + +export 'version.dart'; +export 'crypto/crypto_models.dart'; +export 'models/basic_models.dart'; +export 'models/json_rpc_models.dart'; +export 'models/uri_parse_result.dart'; +export 'pairing/i_pairing_store.dart'; +export 'pairing/utils/pairing_models.dart'; +export 'relay_client/relay_client_models.dart'; +export 'store/store_models.dart'; +export 'utils/errors.dart'; +export 'utils/utils.dart'; +export 'utils/constants.dart'; +export 'utils/method_constants.dart'; +export 'utils/log_level.dart'; +export 'verify/models/verify_context.dart'; +// +export 'i_core_impl.dart'; +export 'core_impl.dart'; diff --git a/packages/reown_core/lib/store/generic_store.dart b/packages/reown_core/lib/store/generic_store.dart new file mode 100644 index 0000000..e9df241 --- /dev/null +++ b/packages/reown_core/lib/store/generic_store.dart @@ -0,0 +1,171 @@ +import 'package:event/event.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_core/store/i_store.dart'; +import 'package:reown_core/store/store_models.dart'; +import 'package:reown_core/utils/errors.dart'; + +class GenericStore implements IGenericStore { + @override + final String context; + @override + final String version; + + @override + String get storageKey => '$version//$context'; + @override + final IStore storage; + + @override + final Event> onCreate = Event(); + @override + final Event> onUpdate = Event(); + @override + final Event> onDelete = Event(); + @override + final Event onSync = Event(); + + bool _initialized = false; + + /// Stores map of key to pairing info + Map data = {}; + + @override + final T Function(dynamic) fromJson; + + GenericStore({ + required this.storage, + required this.context, + required this.version, + required this.fromJson, + }); + + @override + Future init() async { + if (_initialized) { + return; + } + + await storage.init(); + await restore(); + + _initialized = true; + } + + @override + bool has(String key) { + checkInitialized(); + return data.containsKey(key); + } + + @override + T? get(String key) { + checkInitialized(); + if (data.containsKey(key)) { + return data[key]!; + } + return null; + } + + @override + List getAll() { + return data.values.toList(); + } + + @override + Future set(String key, T value) async { + checkInitialized(); + + if (data.containsKey(key)) { + onUpdate.broadcast( + StoreUpdateEvent( + key, + value, + ), + ); + } else { + onCreate.broadcast( + StoreCreateEvent( + key, + value, + ), + ); + } + + data[key] = value; + + await persist(); + } + + @override + Future delete(String key) async { + checkInitialized(); + + if (!data.containsKey(key)) { + return; + } + + onDelete.broadcast( + StoreDeleteEvent( + key, + data.remove(key) as T, + ), + ); + + await persist(); + } + + @override + Future persist() async { + checkInitialized(); + + onSync.broadcast( + StoreSyncEvent(), + ); + + await storage.set(storageKey, data); + } + + @override + Future restore() async { + // If we haven't stored our version yet, we need to store it and stop + if (!storage.has(context)) { + // print('Storing $context'); + await storage.set(context, {'version': version}); + await storage.set(storageKey, {}); + return; + } + + // If we have stored our version, but it doesn't match, we need to delete the previous data, + // create a new version, and stop + final String storedVersion = storage.get(context)['version']; + if (storedVersion != version) { + // print('Updating storage from $storedVersion to $version'); + await storage.delete('$storedVersion//$context'); + await storage.set(context, {'version': version}); + await storage.set(storageKey, {}); + return; + } + + if (storage.has(storageKey)) { + // If there is invalid data, delete the stored data + // print('Restoring $storageKey'); + try { + for (var entry in storage.get(storageKey).entries) { + // print(entry); + data[entry.key] = fromJson(entry.value); + } + } catch (e) { + // print('Error restoring $storageKey: $e'); + await storage.delete(storedVersion); + } + } + } + + @protected + void checkInitialized() { + if (!_initialized) { + throw Errors.getInternalError(Errors.NOT_INITIALIZED); + } + } +} diff --git a/packages/reown_core/lib/store/i_generic_store.dart b/packages/reown_core/lib/store/i_generic_store.dart new file mode 100644 index 0000000..112d235 --- /dev/null +++ b/packages/reown_core/lib/store/i_generic_store.dart @@ -0,0 +1,39 @@ +import 'package:event/event.dart'; +import 'package:reown_core/store/i_store.dart'; +import 'package:reown_core/store/store_models.dart'; + +abstract class IGenericStore { + abstract final String version; + abstract final String context; + + abstract final IStore storage; + abstract final String storageKey; + + // abstract final dynamic Function(T) toJson; + abstract final T Function(dynamic) fromJson; + + /// Emitted when a new key is added to the store. + /// The event contains the key and the value of type [T]. + abstract final Event> onCreate; + + /// Emitted when a key is updated in some way: overwritten, some piece changes, etc. + /// The event contains the key and the value of type [T]. + abstract final Event> onUpdate; + + /// Emitted when a key is deleted from the store. + /// The event contains the key and the value of type [T]. + abstract final Event> onDelete; + + /// Emitted when the store is persisted to storage. + /// This event can be used as a catchall for any creations, updates, or deletions. + abstract final Event onSync; + + Future init(); + bool has(String key); + Future set(String key, T value); + T? get(String key); + List getAll(); + Future delete(String key); + Future restore(); + Future persist(); +} diff --git a/packages/reown_core/lib/store/i_store.dart b/packages/reown_core/lib/store/i_store.dart new file mode 100644 index 0000000..96cfdef --- /dev/null +++ b/packages/reown_core/lib/store/i_store.dart @@ -0,0 +1,15 @@ +abstract class IStore { + abstract final Map map; + abstract final List keys; + abstract final List values; + abstract final String storagePrefix; + + Future init(); + Future set(String key, T value); + T? get(String key); + bool has(String key); + List getAll(); + Future update(String key, T value); + Future delete(String key); + Future deleteAll(); +} diff --git a/packages/reown_core/lib/store/link_mode_store.dart b/packages/reown_core/lib/store/link_mode_store.dart new file mode 100644 index 0000000..74299e4 --- /dev/null +++ b/packages/reown_core/lib/store/link_mode_store.dart @@ -0,0 +1,46 @@ +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_core/utils/constants.dart'; + +abstract class ILinkModeStore extends IGenericStore> { + Future update(String url); + List getList(); +} + +class LinkModeStore extends GenericStore> + implements ILinkModeStore { + // + LinkModeStore({ + required super.storage, + required super.context, + required super.version, + required super.fromJson, + }); + + static const _key = ReownConstants.REOWN_LINK_MODE_APPS; + + @override + Future update(String url) async { + checkInitialized(); + + final currentList = getList(); + if (!currentList.contains(url)) { + final newList = List.from([...currentList, url]); + await storage.set(_key, {_key: newList}); + } + } + + @override + List getList() { + checkInitialized(); + + if (storage.has(_key)) { + final storageValue = storage.get(_key)!; + if (storageValue.keys.contains(_key)) { + final currentList = (storageValue[_key] as List); + return currentList.map((e) => '$e').toList(); + } + } + return []; + } +} diff --git a/packages/reown_core/lib/store/shared_prefs_store.dart b/packages/reown_core/lib/store/shared_prefs_store.dart new file mode 100644 index 0000000..9c51e24 --- /dev/null +++ b/packages/reown_core/lib/store/shared_prefs_store.dart @@ -0,0 +1,170 @@ +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:reown_core/store/i_store.dart'; +import 'package:reown_core/utils/constants.dart'; +import 'package:reown_core/utils/errors.dart'; + +class SharedPrefsStores implements IStore> { + late SharedPreferences prefs; + bool _initialized = false; + + final Map> _map; + + @override + Map> get map => _map; + + @override + List get keys => map.keys.toList(); + + @override + List> get values => map.values.toList(); + + @override + String get storagePrefix => ReownConstants.CORE_STORAGE_PREFIX; + + final bool memoryStore; + + SharedPrefsStores({ + Map>? defaultValue, + this.memoryStore = false, + }) : _map = defaultValue ?? {}; + + /// Initializes the store, loading all persistent values into memory. + @override + Future init() async { + if (_initialized) { + return; + } + + if (!memoryStore) { + prefs = await SharedPreferences.getInstance(); + } + + _initialized = true; + } + + /// Gets the value of the specified key, if it hasn't been cached yet, it caches it. + /// If the key doesn't exist it throws an error. + @override + Map? get(String key) { + _checkInitialized(); + + final String keyWithPrefix = _addPrefix(key); + if (_map.containsKey(keyWithPrefix)) { + return _map[keyWithPrefix]; + } + + Map? value = _getPref(keyWithPrefix); + if (value != null) { + _map[keyWithPrefix] = value; + } + return value; + } + + @override + bool has(String key) { + final String keyWithPrefix = _addPrefix(key); + if (memoryStore) { + return _map.containsKey(keyWithPrefix); + } + return prefs.containsKey(keyWithPrefix); + } + + /// Gets all of the values of the store + @override + List> getAll() { + _checkInitialized(); + return values; + } + + /// Sets the value of a key within the store, overwriting the value if it exists. + @override + Future set(String key, Map value) async { + _checkInitialized(); + + final String keyWithPrefix = _addPrefix(key); + _map[keyWithPrefix] = value; + await _updatePref(keyWithPrefix, value); + } + + /// Updates the value of a key. Fails if it does not exist. + @override + Future update(String key, Map value) async { + _checkInitialized(); + + final String keyWithPrefix = _addPrefix(key); + if (!map.containsKey(keyWithPrefix)) { + throw Errors.getInternalError(Errors.NO_MATCHING_KEY); + } else { + _map[keyWithPrefix] = value; + await _updatePref(keyWithPrefix, value); + } + } + + /// Removes the key from the persistent store + @override + Future delete(String key) async { + _checkInitialized(); + + final String keyWithPrefix = _addPrefix(key); + _map.remove(keyWithPrefix); + await _removePref(keyWithPrefix); + } + + @override + Future deleteAll() async { + final keys = prefs.getKeys(); + for (var key in keys) { + if (key.startsWith(storagePrefix)) { + await delete(key.replaceFirst(storagePrefix, '')); + } + } + } + + Map? _getPref(String key) { + if (memoryStore) { + return null; + } + + if (prefs.containsKey(key)) { + final value = prefs.getString(key)!; + return jsonDecode(value); + } else { + throw Errors.getInternalError(Errors.NO_MATCHING_KEY); + } + } + + Future _updatePref(String key, Map value) async { + if (memoryStore) { + return; + } + + try { + final stringValue = jsonEncode(value); + await prefs.setString(key, stringValue); + } on Exception catch (e) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: e.toString(), + ); + } + } + + Future _removePref(String key) async { + if (memoryStore) { + return; + } + await prefs.remove(key); + } + + String _addPrefix(String key) { + return '$storagePrefix$key'; + } + + void _checkInitialized() { + if (!_initialized) { + throw Errors.getInternalError(Errors.NOT_INITIALIZED); + } + } +} diff --git a/packages/reown_core/lib/store/store_models.dart b/packages/reown_core/lib/store/store_models.dart new file mode 100644 index 0000000..98bd956 --- /dev/null +++ b/packages/reown_core/lib/store/store_models.dart @@ -0,0 +1,35 @@ +import 'package:event/event.dart'; + +class StoreCreateEvent extends EventArgs { + final String key; + final T value; + + StoreCreateEvent( + this.key, + this.value, + ); +} + +class StoreUpdateEvent extends EventArgs { + final String key; + final T value; + + StoreUpdateEvent( + this.key, + this.value, + ); +} + +class StoreDeleteEvent extends EventArgs { + final String key; + final T value; + + StoreDeleteEvent( + this.key, + this.value, + ); +} + +class StoreSyncEvent extends EventArgs { + StoreSyncEvent(); +} diff --git a/packages/reown_core/lib/utils/constants.dart b/packages/reown_core/lib/utils/constants.dart new file mode 100644 index 0000000..3765ad1 --- /dev/null +++ b/packages/reown_core/lib/utils/constants.dart @@ -0,0 +1,111 @@ +import 'package:reown_core/version.dart'; + +class ReownConstants { + static const SDK_VERSION = packageVersion; + + static const CORE_PROTOCOL = 'wc'; + static const CORE_VERSION = 2; + static const CORE_CONTEXT = 'core'; + + static const CORE_STORAGE_PREFIX = + '$CORE_PROTOCOL@$CORE_VERSION:$CORE_CONTEXT:'; + + static const DEFAULT_RELAY_URL = 'wss://relay.walletconnect.org'; + static const DEFAULT_PUSH_URL = 'https://echo.walletconnect.org'; + static const VERIFY_SERVER = 'https://verify.walletconnect.org'; + static const TRUSTED_VERIFY_URLS = [VERIFY_SERVER]; + + static const RELAYER_DEFAULT_PROTOCOL = 'irn'; + + static const THIRTY_SECONDS = 30; + static const ONE_MINUTE = 60; + static const FIVE_MINUTES = ONE_MINUTE * 5; + static const ONE_HOUR = ONE_MINUTE * 60; + static const ONE_DAY = ONE_MINUTE * 24 * 60; + static const SEVEN_DAYS = ONE_DAY * 7; + static const THIRTY_DAYS = ONE_DAY * 30; + + static const REOWN_LINK_MODE_APPS = 'REOWN_LINK_MODE_APPS'; +} + +class StoreVersions { + // Core + static const CONTEXT_KEYCHAIN = 'keychain'; + static const VERSION_KEYCHAIN = '0.3'; + static const CONTEXT_JSON_RPC_HISTORY = 'jsonRpcHistory'; + static const VERSION_JSON_RPC_HISTORY = '1.0'; + static const CONTEXT_PAIRINGS = 'pairings'; + static const VERSION_PAIRINGS = '1.0'; + static const CONTEXT_EXPIRER = 'expirer'; + static const VERSION_EXPIRER = '0.3'; + static const CONTEXT_MESSAGE_TRACKER = 'messageTracker'; + static const VERSION_MESSAGE_TRACKER = '1.0'; + static const CONTEXT_TOPIC_MAP = 'topicMap'; + static const VERSION_TOPIC_MAP = '1.0'; + static const CONTEXT_TOPIC_TO_RECEIVER_PUBLIC_KEY = + 'topicToReceiverPublicKey'; + static const VERSION_TOPIC_TO_RECEIVER_PUBLIC_KEY = '1.1'; + static const CONTEXT_LINKMODE = 'linkMode'; + static const VERSION_LINKMODE = '1.0'; + + // Sign + static const CONTEXT_PROPOSALS = 'proposals'; + static const VERSION_PROPOSALS = '1.1'; + static const CONTEXT_SESSIONS = 'sessions'; + static const VERSION_SESSIONS = '1.1'; + static const CONTEXT_PENDING_REQUESTS = 'pendingRequests'; + static const VERSION_PENDING_REQUESTS = '1.0'; + + // Auth + static const CONTEXT_AUTH_KEYS = 'authKeys'; + static const VERSION_AUTH_KEYS = '2.0'; + static const CONTEXT_PAIRING_TOPICS = 'authPairingTopics'; + static const VERSION_PAIRING_TOPICS = '2.0'; + static const CONTEXT_AUTH_REQUESTS = 'authRequests'; + static const VERSION_AUTH_REQUESTS = '2.0'; + static const CONTEXT_COMPLETE_REQUESTS = 'completeRequests'; + static const VERSION_COMPLETE_REQUESTS = '2.1'; +} + +class MethodsConstants { + static const walletSwitchEthChain = 'wallet_switchEthereumChain'; + static const walletAddEthChain = 'wallet_addEthereumChain'; + static const ethSendTransaction = 'eth_sendTransaction'; + static const requiredMethods = [ + ethSendTransaction, + 'personal_sign', + ]; + static const optionalMethods = [ + 'eth_accounts', + 'eth_requestAccounts', + 'eth_sendRawTransaction', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + walletSwitchEthChain, + walletAddEthChain, + 'wallet_getPermissions', + 'wallet_requestPermissions', + 'wallet_registerOnboarding', + 'wallet_watchAsset', + 'wallet_scanQRCode', + ]; + static const allMethods = [...requiredMethods, ...optionalMethods]; +} + +class EventsConstants { + static const chainChanged = 'chainChanged'; + static const accountsChanged = 'accountsChanged'; + static const requiredEvents = [ + chainChanged, + accountsChanged, + ]; + static const optionalEvents = [ + 'message', + 'disconnect', + 'connect', + ]; + static const allEvents = [...requiredEvents, ...optionalEvents]; +} diff --git a/packages/reown_core/lib/utils/errors.dart b/packages/reown_core/lib/utils/errors.dart new file mode 100644 index 0000000..7e23f4c --- /dev/null +++ b/packages/reown_core/lib/utils/errors.dart @@ -0,0 +1,276 @@ +import 'package:reown_core/models/basic_models.dart'; + +class Errors { + static const INVALID_METHOD = 'INVALID_METHOD'; + static const INVALID_EVENT = 'INVALID_EVENT'; + static const INVALID_UPDATE_REQUEST = 'INVALID_UPDATE_REQUEST'; + static const INVALID_EXTEND_REQUEST = 'INVALID_EXTEND_REQUEST'; + static const INVALID_SESSION_SETTLE_REQUEST = + 'INVALID_SESSION_SETTLE_REQUEST'; + static const UNAUTHORIZED_METHOD = 'UNAUTHORIZED_METHOD'; + static const UNAUTHORIZED_EVENT = 'UNAUTHORIZED_EVENT'; + static const UNAUTHORIZED_UPDATE_REQUEST = 'UNAUTHORIZED_UPDATE_REQUEST'; + static const UNAUTHORIZED_EXTEND_REQUEST = 'UNAUTHORIZED_EXTEND_REQUEST'; + static const USER_REJECTED_SIGN = 'USER_REJECTED_SIGN'; + static const USER_REJECTED = 'USER_REJECTED'; + static const USER_REJECTED_CHAINS = 'USER_REJECTED_CHAINS'; + static const USER_REJECTED_METHODS = 'USER_REJECTED_METHODS'; + static const USER_REJECTED_EVENTS = 'USER_REJECTED_EVENTS'; + static const UNSUPPORTED_CHAINS = 'UNSUPPORTED_CHAINS'; + static const UNSUPPORTED_METHODS = 'UNSUPPORTED_METHODS'; + static const UNSUPPORTED_EVENTS = 'UNSUPPORTED_EVENTS'; + static const UNSUPPORTED_ACCOUNTS = 'UNSUPPORTED_ACCOUNTS'; + static const UNSUPPORTED_NAMESPACE_KEY = 'UNSUPPORTED_NAMESPACE_KEY'; + static const USER_DISCONNECTED = 'USER_DISCONNECTED'; + static const SESSION_SETTLEMENT_FAILED = 'SESSION_SETTLEMENT_FAILED'; + static const NO_SESSION_FOR_TOPIC = 'NO_SESSION_FOR_TOPIC'; + static const REQUEST_EXPIRED_SESSION = 'SESSION_REQUEST_EXPIRED'; + static const WC_METHOD_UNSUPPORTED = 'WC_METHOD_UNSUPPORTED'; + + // AUTH + static const MALFORMED_RESPONSE_PARAMS = 'MALFORMED_RESPONSE_PARAMS'; + static const MALFORMED_REQUEST_PARAMS = 'MALFORMED_REQUEST_PARAMS'; + static const MESSAGE_COMPROMISED = 'MESSAGE_COMPROMISED'; + static const SIGNATURE_VERIFICATION_FAILED = 'SIGNATURE_VERIFICATION_FAILED'; + static const REQUEST_EXPIRED_AUTH = 'AUTH_REQUEST_EXPIRED'; + static const MISSING_ISSUER_AUTH = 'AUTH_MISSING_ISSUER'; + static const USER_REJECTED_AUTH = 'AUTH_USER_REJECTED'; + static const USER_DISCONNECTED_AUTH = 'AUTH_USER_DISCONNECTED'; + + static const SDK_ERRORS = { + /* ----- INVALID (1xxx) ----- */ + INVALID_METHOD: { + 'message': 'Invalid method.', + 'code': 1001, + }, + INVALID_EVENT: { + 'message': 'Invalid event.', + 'code': 1002, + }, + INVALID_UPDATE_REQUEST: { + 'message': 'Invalid update request.', + 'code': 1003, + }, + INVALID_EXTEND_REQUEST: { + 'message': 'Invalid extend request.', + 'code': 1004, + }, + INVALID_SESSION_SETTLE_REQUEST: { + 'message': 'Invalid session settle request.', + 'code': 1005, + }, + /* ----- UNAUTHORIZED (3xxx) ----- */ + UNAUTHORIZED_METHOD: { + 'message': 'Unauthorized method.', + 'code': 3001, + }, + UNAUTHORIZED_EVENT: { + 'message': 'Unauthorized event.', + 'code': 3002, + }, + UNAUTHORIZED_UPDATE_REQUEST: { + 'message': 'Unauthorized update request.', + 'code': 3003, + }, + UNAUTHORIZED_EXTEND_REQUEST: { + 'message': 'Unauthorized extend request.', + 'code': 3004, + }, + /* ----- REJECTED (5xxx) ----- */ + USER_REJECTED_SIGN: { + 'message': 'User rejected.', + 'code': 4001, + }, + /* ----- REJECTED (5xxx) ----- */ + USER_REJECTED: { + 'message': 'User rejected.', + 'code': 5000, + }, + USER_REJECTED_CHAINS: { + 'message': 'User rejected chains.', + 'code': 5001, + }, + USER_REJECTED_METHODS: { + 'message': 'User rejected methods.', + 'code': 5002, + }, + USER_REJECTED_EVENTS: { + 'message': 'User rejected events.', + 'code': 5003, + }, + UNSUPPORTED_CHAINS: { + 'message': 'Unsupported chains.', + 'code': 5100, + }, + UNSUPPORTED_METHODS: { + 'message': 'Unsupported methods.', + 'code': 5101, + }, + UNSUPPORTED_EVENTS: { + 'message': 'Unsupported events.', + 'code': 5102, + }, + UNSUPPORTED_ACCOUNTS: { + 'message': 'Unsupported accounts.', + 'code': 5103, + }, + UNSUPPORTED_NAMESPACE_KEY: { + 'message': 'Unsupported namespace key.', + 'code': 5104, + }, + /* ----- REASON (6xxx) ----- */ + USER_DISCONNECTED: { + 'message': 'User disconnected.', + 'code': 6000, + }, + /* ----- FAILURE (7xxx) ----- */ + SESSION_SETTLEMENT_FAILED: { + 'message': 'Session settlement failed.', + 'code': 7000, + }, + NO_SESSION_FOR_TOPIC: { + 'message': 'No session for topic.', + 'code': 7001, + }, + /* ----- FAILURE (8xxx) ----- */ + REQUEST_EXPIRED_SESSION: { + 'message': 'Session request expired.', + 'code': 8000, + }, + /* ----- PAIRING (10xxx) ----- */ + WC_METHOD_UNSUPPORTED: { + 'message': 'Unsupported wc_ method.', + 'code': 10001, + }, + /* ----- AUTH VALIDATION (11xxx) ----- */ + MALFORMED_RESPONSE_PARAMS: { + 'message': 'Malformed response parameters.', + 'code': 11001, + }, + MALFORMED_REQUEST_PARAMS: { + 'message': 'Malformed request parameters.', + 'code': 11002, + }, + MESSAGE_COMPROMISED: { + 'message': 'Message compromised.', + 'code': 11003, + }, + SIGNATURE_VERIFICATION_FAILED: { + 'message': 'Signature verification failed.', + 'code': 11004, + }, + REQUEST_EXPIRED_AUTH: { + 'message': 'Auth request expired.', + 'code': 11005, + }, + MISSING_ISSUER_AUTH: { + 'message': 'Missing Issuer.', + 'code': 11006, + }, + /* ----- AUTH REJECTED (12xxx) ----- */ + USER_REJECTED_AUTH: { + 'message': 'User rejected auth request.', + 'code': 12001, + }, + USER_DISCONNECTED_AUTH: { + 'message': 'User disconnect auth.', + 'code': 12002, + }, + }; + + static const NOT_INITIALIZED = 'NOT_INITIALIZED'; + static const NO_MATCHING_KEY = 'NO_MATCHING_KEY'; + static const RESTORE_WILL_OVERRIDE = 'RESTORE_WILL_OVERRIDE'; + static const RESUBSCRIBED = 'RESUBSCRIBED'; + static const MISSING_OR_INVALID = 'MISSING_OR_INVALID'; + static const EXPIRED = 'EXPIRED'; + static const UNKNOWN_TYPE = 'UNKNOWN_TYPE'; + static const MISMATCHED_TOPIC = 'MISMATCHED_TOPIC'; + static const NON_CONFORMING_NAMESPACES = 'NON_CONFORMING_NAMESPACES'; + + static const INTERNAL_ERRORS = { + NOT_INITIALIZED: { + 'message': 'Not initialized.', + 'code': 1, + }, + NO_MATCHING_KEY: { + 'message': 'No matching key.', + 'code': 2, + }, + RESTORE_WILL_OVERRIDE: { + 'message': 'Restore will override.', + 'code': 3, + }, + RESUBSCRIBED: { + 'message': 'Resubscribed.', + 'code': 4, + }, + MISSING_OR_INVALID: { + 'message': 'Missing or invalid.', + 'code': 5, + }, + EXPIRED: { + 'message': 'Expired.', + 'code': 6, + }, + UNKNOWN_TYPE: { + 'message': 'Unknown type.', + 'code': 7, + }, + MISMATCHED_TOPIC: { + 'message': 'Mismatched topic.', + 'code': 8, + }, + NON_CONFORMING_NAMESPACES: { + 'message': 'Non conforming namespaces.', + 'code': 9, + }, + }; + + static ReownCoreError getInternalError( + String key, { + String context = '', + }) { + if (INTERNAL_ERRORS.containsKey(key)) { + return ReownCoreError( + code: INTERNAL_ERRORS[key]!['code']! as int, + message: context != '' + ? "${INTERNAL_ERRORS[key]!['message']! as String} $context" + : INTERNAL_ERRORS[key]!['message']! as String, + ); + } + return const ReownCoreError(code: -1, message: 'UNKNOWN INTERNAL ERROR'); + } + + static ReownCoreError getSdkError( + String key, { + String context = '', + }) { + if (SDK_ERRORS.containsKey(key)) { + return ReownCoreError( + code: SDK_ERRORS[key]!['code']! as int, + message: context != '' + ? "${SDK_ERRORS[key]!['message']! as String} $context" + : SDK_ERRORS[key]!['message']! as String, + ); + } + return const ReownCoreError(code: -1, message: 'UNKNOWN SDK ERROR'); + } +} + +class WebSocketErrors { + static const int PROJECT_ID_NOT_FOUND = 401; + static const int INVALID_PROJECT_ID = 403; + static const int TOO_MANY_REQUESTS = 1013; + static const String INVALID_PROJECT_ID_OR_JWT = + 'Invalid project ID or JWT Token'; + static const String INVALID_PROJECT_ID_MESSAGE = + 'Invalid project id. Please check your project id.'; + static const String PROJECT_ID_NOT_FOUND_MESSAGE = 'Project id not found.'; + static const String TOO_MANY_REQUESTS_MESSAGE = + 'Too many requests. Please try again later.'; + + static const int SERVER_TERMINATING = 1001; + static const int CLIENT_STALE = 4008; + static const int LOAD_REBALANCING = 4010; +} diff --git a/packages/reown_core/lib/utils/log_level.dart b/packages/reown_core/lib/utils/log_level.dart new file mode 100644 index 0000000..9be0076 --- /dev/null +++ b/packages/reown_core/lib/utils/log_level.dart @@ -0,0 +1,30 @@ +import 'package:logger/logger.dart'; + +enum LogLevel { + verbose, + debug, + info, + warning, + error, + wtf, + nothing; + + Level toLevel() { + switch (this) { + case LogLevel.verbose: + return Level.trace; + case LogLevel.debug: + return Level.debug; + case LogLevel.info: + return Level.info; + case LogLevel.warning: + return Level.warning; + case LogLevel.error: + return Level.error; + case LogLevel.wtf: + return Level.fatal; + default: + return Level.off; + } + } +} diff --git a/packages/reown_core/lib/utils/method_constants.dart b/packages/reown_core/lib/utils/method_constants.dart new file mode 100644 index 0000000..6704572 --- /dev/null +++ b/packages/reown_core/lib/utils/method_constants.dart @@ -0,0 +1,200 @@ +import 'package:reown_core/models/json_rpc_models.dart'; +import 'package:reown_core/utils/constants.dart'; + +class MethodConstants { + static const WC_PAIRING_PING = 'wc_pairingPing'; + static const WC_PAIRING_DELETE = 'wc_pairingDelete'; + static const UNREGISTERED_METHOD = 'unregistered_method'; + + static const WC_SESSION_PROPOSE = 'wc_sessionPropose'; + static const WC_SESSION_SETTLE = 'wc_sessionSettle'; + static const WC_SESSION_UPDATE = 'wc_sessionUpdate'; + static const WC_SESSION_EXTEND = 'wc_sessionExtend'; + static const WC_SESSION_REQUEST = 'wc_sessionRequest'; + static const WC_SESSION_EVENT = 'wc_sessionEvent'; + static const WC_SESSION_DELETE = 'wc_sessionDelete'; + static const WC_SESSION_PING = 'wc_sessionPing'; + + // static const WC_AUTH_REQUEST = 'wc_authRequest'; + + static const WC_SESSION_AUTHENTICATE = 'wc_sessionAuthenticate'; + + static const Map> RPC_OPTS = { + WC_PAIRING_PING: { + 'req': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 1000, + ), + 'res': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 1001, + ), + }, + WC_PAIRING_DELETE: { + 'req': RpcOptions( + ttl: ReownConstants.THIRTY_SECONDS, + prompt: false, + tag: 1002, + ), + 'res': RpcOptions( + ttl: ReownConstants.THIRTY_SECONDS, + prompt: false, + tag: 1003, + ), + }, + UNREGISTERED_METHOD: { + 'req': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 0, + ), + 'res': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 0, + ), + }, + WC_SESSION_PROPOSE: { + 'req': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: true, + tag: 1100, + ), + 'res': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1101, + ), + 'reject': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1120, + ), + 'autoReject': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1121, + ), + }, + WC_SESSION_SETTLE: { + 'req': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1102, + ), + 'res': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1103, + ), + }, + WC_SESSION_UPDATE: { + 'req': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 1104, + ), + 'res': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 1105, + ), + }, + WC_SESSION_EXTEND: { + 'req': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 1106, + ), + 'res': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 1107, + ), + }, + WC_SESSION_REQUEST: { + 'req': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: true, + tag: 1108, + ), + 'res': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1109, + ), + }, + WC_SESSION_EVENT: { + 'req': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: true, + tag: 1110, + ), + 'res': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1111, + ), + }, + WC_SESSION_DELETE: { + 'req': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 1112, + ), + 'res': RpcOptions( + ttl: ReownConstants.ONE_DAY, + prompt: false, + tag: 1113, + ), + }, + WC_SESSION_PING: { + 'req': RpcOptions( + ttl: ReownConstants.THIRTY_SECONDS, + prompt: false, + tag: 1114, + ), + 'res': RpcOptions( + ttl: ReownConstants.THIRTY_SECONDS, + prompt: false, + tag: 1115, + ), + }, + WC_SESSION_AUTHENTICATE: { + 'req': RpcOptions( + ttl: ReownConstants.ONE_HOUR, + prompt: false, + tag: 1116, + ), + 'res': RpcOptions( + ttl: ReownConstants.ONE_HOUR, + prompt: false, + tag: 1117, + ), + 'reject': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1118, + ), + 'autoReject': RpcOptions( + ttl: ReownConstants.FIVE_MINUTES, + prompt: false, + tag: 1119, + ), + }, + // WC_AUTH_REQUEST: { + // 'req': RpcOptions( + // ttl: ReownConstants.ONE_DAY, + // prompt: true, + // tag: 3000, + // ), + // 'res': RpcOptions( + // ttl: ReownConstants.ONE_DAY, + // prompt: false, + // tag: 3001, + // ), + // }, + }; +} diff --git a/packages/reown_core/lib/utils/utils.dart b/packages/reown_core/lib/utils/utils.dart new file mode 100644 index 0000000..5988c49 --- /dev/null +++ b/packages/reown_core/lib/utils/utils.dart @@ -0,0 +1,265 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter/foundation.dart' show kIsWeb; + +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import 'package:reown_core/models/basic_models.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/models/uri_parse_result.dart'; + +class ReownCoreUtils { + static bool isExpired(int expiry) { + return DateTime.now().toUtc().compareTo( + DateTime.fromMillisecondsSinceEpoch( + toMilliseconds(expiry), + ), + ) >= + 0; + } + + static DateTime expiryToDateTime(int expiry) { + final milliseconds = expiry * 1000; + return DateTime.fromMillisecondsSinceEpoch(milliseconds); + } + + static int toMilliseconds(int seconds) { + return seconds * 1000; + } + + static int calculateExpiry(int offset) { + return DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000 + offset; + } + + static String getOS() { + if (kIsWeb) { + // TODO change this into an actual value + return 'web-browser'; + } else { + return [Platform.operatingSystem, Platform.operatingSystemVersion] + .join('-'); + } + } + + static Future getPackageName() async { + final packageInfo = await PackageInfo.fromPlatform(); + return packageInfo.packageName; + } + + static String getId() { + if (kIsWeb) { + return 'web'; + } else { + if (Platform.isAndroid) { + return 'android'; + } else if (Platform.isIOS) { + return 'ios'; + } else if (Platform.isLinux) { + return 'linux'; + } else if (Platform.isMacOS) { + return 'macos'; + } else if (Platform.isWindows) { + return 'windows'; + } else { + return 'unknown'; + } + } + } + + static String formatUA( + String protocol, + int version, + String sdkVersion, + ) { + String os = getOS(); + String id = getId(); + return [ + [protocol, version].join('-'), + ['Reown-Flutter', sdkVersion].join('-'), + os, + id, + ].join('/'); + } + + static String formatRelayRpcUrl({ + required String protocol, + required int version, + required String relayUrl, + required String sdkVersion, + required String auth, + String? projectId, + String? packageName, + }) { + final Uri uri = Uri.parse(relayUrl); + final Map queryParams = Uri.splitQueryString(uri.query); + final userAgent = formatUA(protocol, version, sdkVersion); + + // Add basic query params + final Map relayParams = { + 'auth': auth, + 'ua': userAgent, + }; + + // Add projectId query param + if ((projectId ?? '').isNotEmpty) { + relayParams['projectId'] = projectId!; + } + + // Add bundleId, packageName or origin query param based on platform + if ((packageName ?? '').isNotEmpty) { + final platform = getId(); + if (platform == 'ios') { + relayParams['bundleId'] = packageName!; + } else if (platform == 'android') { + relayParams['packageName'] = packageName!; + } else { + relayParams['origin'] = packageName!; + } + } + + queryParams.addAll(relayParams); + return uri.replace(queryParameters: queryParams).toString(); + } + + /// ---- URI HANDLING --- /// + + static URIParseResult parseUri(Uri uri) { + String protocol = uri.scheme; + String path = uri.path; + final List splitParams = path.split('@'); + if (splitParams.length == 1) { + throw const ReownCoreError( + code: 0, + message: 'Invalid URI: Missing @', + ); + } + List methods = (uri.queryParameters['methods'] ?? '') + // Replace all the square brackets with empty string, split by comma + .replaceAll(RegExp(r'[\[\]"]+'), '') + .split(','); + if (methods.length == 1 && methods[0].isEmpty) { + methods = []; + } + final URIVersion? version; + switch (splitParams[1]) { + case '1': + version = URIVersion.v1; + break; + case '2': + version = URIVersion.v2; + break; + default: + version = null; + } + final URIV1ParsedData? v1Data; + final URIV2ParsedData? v2Data; + if (version == URIVersion.v1) { + v1Data = URIV1ParsedData( + key: uri.queryParameters['key']!, + bridge: uri.queryParameters['bridge']!, + ); + v2Data = null; + } else { + v1Data = null; + v2Data = URIV2ParsedData( + symKey: uri.queryParameters['symKey']!, + relay: Relay( + uri.queryParameters['relay-protocol']!, + data: uri.queryParameters.containsKey('relay-data') + ? uri.queryParameters['relay-data'] + : null, + ), + methods: methods, + ); + } + + URIParseResult ret = URIParseResult( + protocol: protocol, + version: version, + topic: splitParams[0], + v1Data: v1Data, + v2Data: v2Data, + ); + return ret; + } + + static Map formatRelayParams( + Relay relay, { + String delimiter = '-', + }) { + Map params = {}; + params[['relay', 'protocol'].join(delimiter)] = relay.protocol; + if (relay.data != null) { + params[['relay', 'data'].join(delimiter)] = relay.data!; + } + return params; + } + + static Uri formatUri({ + required String protocol, + required String version, + required String topic, + required String symKey, + required Relay relay, + required List>? methods, + int? expiry, + }) { + Map params = formatRelayParams(relay); + params['symKey'] = symKey; + if (methods != null) { + final uriMethods = methods.expand((e) => e).toList(); + params['methods'] = + uriMethods.map((e) => jsonEncode(e)).join(',').replaceAll('"', ''); + } + + if (expiry != null) { + params['expiryTimestamp'] = expiry.toString(); + } + + return Uri( + scheme: protocol, + path: '$topic@$version', + queryParameters: params, + ); + } + + static Map convertMapTo(Map inMap) { + Map m = {}; + for (var entry in inMap.entries) { + m[entry.key] = entry.value as T; + } + return m; + } + + static String getSearchParamFromURL(String url, String param) { + final decodedUri = Uri.parse(Uri.decodeFull(url)); + final hasParam = decodedUri.queryParameters.keys.contains(param); + if (!hasParam) return ''; + + return Uri.encodeQueryComponent(decodedUri.queryParameters[param]!); + } + + static String getLinkModeURL( + String universalLink, + String topic, + String encodedEnvelope, + ) { + return '$universalLink?wc_ev=$encodedEnvelope&topic=$topic'; + } + + static Future openURL(String url) async { + try { + final success = await launchUrlString( + url, + mode: LaunchMode.externalApplication, + ); + if (!success) { + throw ReownCoreError(code: 3000, message: 'Can not open $url'); + } + return true; + } catch (_) { + throw ReownCoreError(code: 3001, message: 'Can not open $url'); + } + } +} diff --git a/packages/reown_core/lib/verify/i_verify.dart b/packages/reown_core/lib/verify/i_verify.dart new file mode 100644 index 0000000..be7c6d6 --- /dev/null +++ b/packages/reown_core/lib/verify/i_verify.dart @@ -0,0 +1,12 @@ +import 'package:reown_core/verify/models/verify_context.dart'; + +abstract class IVerify { + Future init({String? verifyUrl}); + + Future resolve({required String attestationId}); + + Validation getValidation( + AttestationResponse? attestation, + Uri? metadataUri, + ); +} diff --git a/packages/reown_core/lib/verify/models/verify_context.dart b/packages/reown_core/lib/verify/models/verify_context.dart new file mode 100644 index 0000000..8b93734 --- /dev/null +++ b/packages/reown_core/lib/verify/models/verify_context.dart @@ -0,0 +1,50 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'verify_context.g.dart'; +part 'verify_context.freezed.dart'; + +enum Validation { + UNKNOWN, + VALID, + INVALID, + SCAM; + + bool get invalid => this == INVALID; + bool get valid => this == VALID; + bool get unknown => this == UNKNOWN; + bool get scam => this == SCAM; +} + +@freezed +class VerifyContext with _$VerifyContext { + @JsonSerializable() + const factory VerifyContext({ + required String origin, + required Validation validation, + required String verifyUrl, + bool? isScam, + }) = _VerifyContext; + + factory VerifyContext.fromJson(Map json) => + _$VerifyContextFromJson(json); +} + +@freezed +class AttestationResponse with _$AttestationResponse { + @JsonSerializable() + const factory AttestationResponse({ + required String origin, + required String attestationId, + bool? isScam, + }) = _AttestationResponse; + + factory AttestationResponse.fromJson(Map json) => + _$AttestationResponseFromJson(json); +} + +class AttestationNotFound implements Exception { + int code; + String message; + + AttestationNotFound({required this.code, required this.message}) : super(); +} diff --git a/packages/reown_core/lib/verify/models/verify_context.freezed.dart b/packages/reown_core/lib/verify/models/verify_context.freezed.dart new file mode 100644 index 0000000..8880e08 --- /dev/null +++ b/packages/reown_core/lib/verify/models/verify_context.freezed.dart @@ -0,0 +1,388 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'verify_context.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +VerifyContext _$VerifyContextFromJson(Map json) { + return _VerifyContext.fromJson(json); +} + +/// @nodoc +mixin _$VerifyContext { + String get origin => throw _privateConstructorUsedError; + Validation get validation => throw _privateConstructorUsedError; + String get verifyUrl => throw _privateConstructorUsedError; + bool? get isScam => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $VerifyContextCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VerifyContextCopyWith<$Res> { + factory $VerifyContextCopyWith( + VerifyContext value, $Res Function(VerifyContext) then) = + _$VerifyContextCopyWithImpl<$Res, VerifyContext>; + @useResult + $Res call( + {String origin, Validation validation, String verifyUrl, bool? isScam}); +} + +/// @nodoc +class _$VerifyContextCopyWithImpl<$Res, $Val extends VerifyContext> + implements $VerifyContextCopyWith<$Res> { + _$VerifyContextCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? origin = null, + Object? validation = null, + Object? verifyUrl = null, + Object? isScam = freezed, + }) { + return _then(_value.copyWith( + origin: null == origin + ? _value.origin + : origin // ignore: cast_nullable_to_non_nullable + as String, + validation: null == validation + ? _value.validation + : validation // ignore: cast_nullable_to_non_nullable + as Validation, + verifyUrl: null == verifyUrl + ? _value.verifyUrl + : verifyUrl // ignore: cast_nullable_to_non_nullable + as String, + isScam: freezed == isScam + ? _value.isScam + : isScam // ignore: cast_nullable_to_non_nullable + as bool?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$VerifyContextImplCopyWith<$Res> + implements $VerifyContextCopyWith<$Res> { + factory _$$VerifyContextImplCopyWith( + _$VerifyContextImpl value, $Res Function(_$VerifyContextImpl) then) = + __$$VerifyContextImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String origin, Validation validation, String verifyUrl, bool? isScam}); +} + +/// @nodoc +class __$$VerifyContextImplCopyWithImpl<$Res> + extends _$VerifyContextCopyWithImpl<$Res, _$VerifyContextImpl> + implements _$$VerifyContextImplCopyWith<$Res> { + __$$VerifyContextImplCopyWithImpl( + _$VerifyContextImpl _value, $Res Function(_$VerifyContextImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? origin = null, + Object? validation = null, + Object? verifyUrl = null, + Object? isScam = freezed, + }) { + return _then(_$VerifyContextImpl( + origin: null == origin + ? _value.origin + : origin // ignore: cast_nullable_to_non_nullable + as String, + validation: null == validation + ? _value.validation + : validation // ignore: cast_nullable_to_non_nullable + as Validation, + verifyUrl: null == verifyUrl + ? _value.verifyUrl + : verifyUrl // ignore: cast_nullable_to_non_nullable + as String, + isScam: freezed == isScam + ? _value.isScam + : isScam // ignore: cast_nullable_to_non_nullable + as bool?, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$VerifyContextImpl implements _VerifyContext { + const _$VerifyContextImpl( + {required this.origin, + required this.validation, + required this.verifyUrl, + this.isScam}); + + factory _$VerifyContextImpl.fromJson(Map json) => + _$$VerifyContextImplFromJson(json); + + @override + final String origin; + @override + final Validation validation; + @override + final String verifyUrl; + @override + final bool? isScam; + + @override + String toString() { + return 'VerifyContext(origin: $origin, validation: $validation, verifyUrl: $verifyUrl, isScam: $isScam)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VerifyContextImpl && + (identical(other.origin, origin) || other.origin == origin) && + (identical(other.validation, validation) || + other.validation == validation) && + (identical(other.verifyUrl, verifyUrl) || + other.verifyUrl == verifyUrl) && + (identical(other.isScam, isScam) || other.isScam == isScam)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, origin, validation, verifyUrl, isScam); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$VerifyContextImplCopyWith<_$VerifyContextImpl> get copyWith => + __$$VerifyContextImplCopyWithImpl<_$VerifyContextImpl>(this, _$identity); + + @override + Map toJson() { + return _$$VerifyContextImplToJson( + this, + ); + } +} + +abstract class _VerifyContext implements VerifyContext { + const factory _VerifyContext( + {required final String origin, + required final Validation validation, + required final String verifyUrl, + final bool? isScam}) = _$VerifyContextImpl; + + factory _VerifyContext.fromJson(Map json) = + _$VerifyContextImpl.fromJson; + + @override + String get origin; + @override + Validation get validation; + @override + String get verifyUrl; + @override + bool? get isScam; + @override + @JsonKey(ignore: true) + _$$VerifyContextImplCopyWith<_$VerifyContextImpl> get copyWith => + throw _privateConstructorUsedError; +} + +AttestationResponse _$AttestationResponseFromJson(Map json) { + return _AttestationResponse.fromJson(json); +} + +/// @nodoc +mixin _$AttestationResponse { + String get origin => throw _privateConstructorUsedError; + String get attestationId => throw _privateConstructorUsedError; + bool? get isScam => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AttestationResponseCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AttestationResponseCopyWith<$Res> { + factory $AttestationResponseCopyWith( + AttestationResponse value, $Res Function(AttestationResponse) then) = + _$AttestationResponseCopyWithImpl<$Res, AttestationResponse>; + @useResult + $Res call({String origin, String attestationId, bool? isScam}); +} + +/// @nodoc +class _$AttestationResponseCopyWithImpl<$Res, $Val extends AttestationResponse> + implements $AttestationResponseCopyWith<$Res> { + _$AttestationResponseCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? origin = null, + Object? attestationId = null, + Object? isScam = freezed, + }) { + return _then(_value.copyWith( + origin: null == origin + ? _value.origin + : origin // ignore: cast_nullable_to_non_nullable + as String, + attestationId: null == attestationId + ? _value.attestationId + : attestationId // ignore: cast_nullable_to_non_nullable + as String, + isScam: freezed == isScam + ? _value.isScam + : isScam // ignore: cast_nullable_to_non_nullable + as bool?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AttestationResponseImplCopyWith<$Res> + implements $AttestationResponseCopyWith<$Res> { + factory _$$AttestationResponseImplCopyWith(_$AttestationResponseImpl value, + $Res Function(_$AttestationResponseImpl) then) = + __$$AttestationResponseImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String origin, String attestationId, bool? isScam}); +} + +/// @nodoc +class __$$AttestationResponseImplCopyWithImpl<$Res> + extends _$AttestationResponseCopyWithImpl<$Res, _$AttestationResponseImpl> + implements _$$AttestationResponseImplCopyWith<$Res> { + __$$AttestationResponseImplCopyWithImpl(_$AttestationResponseImpl _value, + $Res Function(_$AttestationResponseImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? origin = null, + Object? attestationId = null, + Object? isScam = freezed, + }) { + return _then(_$AttestationResponseImpl( + origin: null == origin + ? _value.origin + : origin // ignore: cast_nullable_to_non_nullable + as String, + attestationId: null == attestationId + ? _value.attestationId + : attestationId // ignore: cast_nullable_to_non_nullable + as String, + isScam: freezed == isScam + ? _value.isScam + : isScam // ignore: cast_nullable_to_non_nullable + as bool?, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$AttestationResponseImpl implements _AttestationResponse { + const _$AttestationResponseImpl( + {required this.origin, required this.attestationId, this.isScam}); + + factory _$AttestationResponseImpl.fromJson(Map json) => + _$$AttestationResponseImplFromJson(json); + + @override + final String origin; + @override + final String attestationId; + @override + final bool? isScam; + + @override + String toString() { + return 'AttestationResponse(origin: $origin, attestationId: $attestationId, isScam: $isScam)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AttestationResponseImpl && + (identical(other.origin, origin) || other.origin == origin) && + (identical(other.attestationId, attestationId) || + other.attestationId == attestationId) && + (identical(other.isScam, isScam) || other.isScam == isScam)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, origin, attestationId, isScam); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AttestationResponseImplCopyWith<_$AttestationResponseImpl> get copyWith => + __$$AttestationResponseImplCopyWithImpl<_$AttestationResponseImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$AttestationResponseImplToJson( + this, + ); + } +} + +abstract class _AttestationResponse implements AttestationResponse { + const factory _AttestationResponse( + {required final String origin, + required final String attestationId, + final bool? isScam}) = _$AttestationResponseImpl; + + factory _AttestationResponse.fromJson(Map json) = + _$AttestationResponseImpl.fromJson; + + @override + String get origin; + @override + String get attestationId; + @override + bool? get isScam; + @override + @JsonKey(ignore: true) + _$$AttestationResponseImplCopyWith<_$AttestationResponseImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_core/lib/verify/models/verify_context.g.dart b/packages/reown_core/lib/verify/models/verify_context.g.dart new file mode 100644 index 0000000..f1c53d9 --- /dev/null +++ b/packages/reown_core/lib/verify/models/verify_context.g.dart @@ -0,0 +1,46 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'verify_context.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$VerifyContextImpl _$$VerifyContextImplFromJson(Map json) => + _$VerifyContextImpl( + origin: json['origin'] as String, + validation: $enumDecode(_$ValidationEnumMap, json['validation']), + verifyUrl: json['verifyUrl'] as String, + isScam: json['isScam'] as bool?, + ); + +Map _$$VerifyContextImplToJson(_$VerifyContextImpl instance) => + { + 'origin': instance.origin, + 'validation': _$ValidationEnumMap[instance.validation]!, + 'verifyUrl': instance.verifyUrl, + 'isScam': instance.isScam, + }; + +const _$ValidationEnumMap = { + Validation.UNKNOWN: 'UNKNOWN', + Validation.VALID: 'VALID', + Validation.INVALID: 'INVALID', + Validation.SCAM: 'SCAM', +}; + +_$AttestationResponseImpl _$$AttestationResponseImplFromJson( + Map json) => + _$AttestationResponseImpl( + origin: json['origin'] as String, + attestationId: json['attestationId'] as String, + isScam: json['isScam'] as bool?, + ); + +Map _$$AttestationResponseImplToJson( + _$AttestationResponseImpl instance) => + { + 'origin': instance.origin, + 'attestationId': instance.attestationId, + 'isScam': instance.isScam, + }; diff --git a/packages/reown_core/lib/verify/verify.dart b/packages/reown_core/lib/verify/verify.dart new file mode 100644 index 0000000..267815a --- /dev/null +++ b/packages/reown_core/lib/verify/verify.dart @@ -0,0 +1,79 @@ +import 'dart:convert'; + +import 'package:reown_core/i_core_impl.dart'; +import 'package:reown_core/relay_client/websocket/i_http_client.dart'; +import 'package:reown_core/verify/i_verify.dart'; +import 'package:reown_core/verify/models/verify_context.dart'; +import 'package:reown_core/utils/constants.dart'; + +class Verify implements IVerify { + final IReownCore _core; + final IHttpClient _httpClient; + late String _verifyUrl; + + Verify({ + required IReownCore core, + required IHttpClient httpClient, + }) : _core = core, + _httpClient = httpClient; + + @override + Future init({String? verifyUrl}) async { + // TODO custom verifyUrl is not yet allowed. + // Always using walletconnect urls for now + _verifyUrl = _setVerifyUrl(verifyUrl: verifyUrl); + } + + @override + Future resolve({required String attestationId}) async { + try { + final uri = Uri.parse('$_verifyUrl/attestation/$attestationId'); + final response = await _httpClient.get(uri).timeout(Duration(seconds: 5)); + if (response.statusCode == 404 || response.body.isEmpty) { + throw AttestationNotFound( + code: 404, + message: 'Attestion for this dapp could not be found', + ); + } + if (response.statusCode != 200) { + throw Exception('Attestation response error: ${response.statusCode}'); + } + return AttestationResponse.fromJson(jsonDecode(response.body)); + } catch (e) { + _core.logger.d('[$runtimeType] resolve $e'); + rethrow; + } + } + + @override + Validation getValidation( + AttestationResponse? attestation, + Uri? metadataUri, + ) { + if (attestation?.isScam == true) { + return Validation.SCAM; + } + + if ((attestation?.origin ?? '').isEmpty || + (metadataUri?.origin ?? '').isEmpty) { + return Validation.INVALID; + } + + return attestation!.origin == metadataUri!.origin + ? Validation.VALID + : Validation.INVALID; + } + + String _setVerifyUrl({String? verifyUrl}) { + String url = verifyUrl ?? ReownConstants.VERIFY_SERVER; + + if (!ReownConstants.TRUSTED_VERIFY_URLS.contains(url)) { + _core.logger.i( + '[$runtimeType] verifyUrl $url not included in trusted list, ' + 'assigning default: ${ReownConstants.VERIFY_SERVER}', + ); + url = ReownConstants.VERIFY_SERVER; + } + return url; + } +} diff --git a/packages/reown_core/lib/version.dart b/packages/reown_core/lib/version.dart new file mode 100644 index 0000000..526cd53 --- /dev/null +++ b/packages/reown_core/lib/version.dart @@ -0,0 +1,2 @@ +// Generated code. Do not modify. +const packageVersion = '1.0.0'; diff --git a/packages/reown_core/pubspec.yaml b/packages/reown_core/pubspec.yaml new file mode 100644 index 0000000..b3edff4 --- /dev/null +++ b/packages/reown_core/pubspec.yaml @@ -0,0 +1,50 @@ +name: reown_core +description: "The communications protocol for web3" +version: 1.0.0 +homepage: https://github.com/reown-com/reown_flutter +repository: https://github.com/reown-com/reown_flutter + +environment: + sdk: ">=2.19.0 <4.0.0" + +dependencies: + bs58: ^1.0.2 + connectivity_plus: ^6.0.4 + convert: ^3.0.1 + cryptography: ^2.0.5 + ed25519_edwards: ^0.3.1 + event: ^2.1.2 + flutter: + sdk: flutter + freezed_annotation: ^2.2.0 + http: ^1.2.0 + json_annotation: ^4.8.1 + logger: ^2.2.0 + package_info_plus: ^7.0.0 + pointycastle: ^3.9.1 + shared_preferences: ^2.2.2 + stack_trace: ^1.10.0 + stream_channel: ^2.1.0 + url_launcher: ^6.3.0 + web3dart: ^2.7.3 + web_socket_channel: ^2.4.4 + x25519: ^0.1.1 + +dev_dependencies: + build_runner: ^2.4.7 + build_version: ^2.1.1 + dependency_validator: ^3.2.2 + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + freezed: ^2.4.5 + json_serializable: ^6.7.0 + mockito: ^5.4.3 + +platforms: + android: + ios: + web: + macos: + linux: + windows: diff --git a/packages/reown_core/test/crypto_test.dart b/packages/reown_core/test/crypto_test.dart new file mode 100644 index 0000000..4560f4f --- /dev/null +++ b/packages/reown_core/test/crypto_test.dart @@ -0,0 +1,350 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:reown_core/crypto/crypto_models.dart'; +import 'package:reown_core/crypto/crypto_utils.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/models/basic_models.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_utils.mocks.dart'; +import 'shared/shared_test_values.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final testMessage = jsonEncode({ + 'id': 1, + 'jsonrpc': '2.0', + 'method': 'test_method', + 'params': {}, + }); + + final testSelf = TEST_KEY_PAIRS['A']!; + final testPeer = TEST_KEY_PAIRS['B']!; + + const TEST_IV = '717765636661617364616473'; + + const TEST_SEALED = + '7a5a1e843debf98b01d6a75718b5ee27115eafa3caba9703ca1c5601a6af2419045320faec2073cc8b6b8dc439e63e21612ff3883c867e0bdcd72c833eb7f7bb2034a9ec35c2fb03d93732'; + + const TEST_ENCODED_TYPE_0 = + 'AHF3ZWNmYWFzZGFkc3paHoQ96/mLAdanVxi17icRXq+jyrqXA8ocVgGmryQZBFMg+uwgc8yLa43EOeY+IWEv84g8hn4L3Ncsgz6397sgNKnsNcL7A9k3Mg=='; + const TEST_ENCODED_TYPE_1 = + 'Af96fVdnw2KwoXrZIpnr23gx3L2aVpWcATaMdARUOzNCcXdlY2ZhYXNkYWRzeloehD3r+YsB1qdXGLXuJxFer6PKupcDyhxWAaavJBkEUyD67CBzzItrjcQ55j4hYS/ziDyGfgvc1yyDPrf3uyA0qew1wvsD2Tcy'; + + group('Crypto API', () { + late MockCryptoUtils mockUtils; + late ICrypto cryptoAPI; + late ICrypto cryptoApiUninitialized; + + setUp(() async { + cryptoApiUninitialized = getCrypto(); + cryptoAPI = getCrypto(); + await cryptoAPI.init(); + // await cryptoAPI.core.start(); + }); + + // test('Initializes the keychain a single time', () async { + // verify(keyChain.init()).called(1); + // await cryptoAPI.init(); + // verifyNever(keyChain.init()); + // }); + + group('generateKeyPair', () { + test('Throws if not initialized', () async { + cryptoAPI = getCrypto(); + expect( + () async => await cryptoAPI.generateKeyPair(), + throwsA(isA()), + ); + }); + + test( + 'generates a keypair, sets it in the keychain and returns a public key', + () async { + final privateKey = CryptoUtils().generateRandomBytes32(); + final publicKey = CryptoUtils().generateRandomBytes32(); + mockUtils = MockCryptoUtils(); + when(mockUtils.generateKeyPair()).thenReturn( + CryptoKeyPair(privateKey, publicKey), + ); + cryptoAPI = getCrypto( + utils: mockUtils, + ); + await cryptoAPI.init(); + + final String pubKeyActual = await cryptoAPI.generateKeyPair(); + verify(mockUtils.generateKeyPair()).called(1); + // verify(keyChain.set(publicKey, privateKey)).called(1); + expect(pubKeyActual, publicKey); + }, + ); + }); + + group('generateSharedKey', () { + test('Throws if not initialized', () async { + cryptoAPI = getCrypto(); + expect( + () async => await cryptoAPI.generateSharedKey('a', 'b'), + throwsA(isA()), + ); + }); + + test( + 'generates a shared symKey, sets it in the keychain and returns the topic', + () async { + CryptoUtils utils = CryptoUtils(); + final overrideTopic = utils.generateRandomBytes32(); + final peerPublicKey = utils.generateRandomBytes32(); + final CryptoKeyPair selfKP = utils.generateKeyPair(); + final String expectedSymKey = await utils.deriveSymKey( + selfKP.privateKey, + peerPublicKey, + ); + + mockUtils = MockCryptoUtils(); + when(mockUtils.deriveSymKey(selfKP.privateKey, peerPublicKey)) + .thenAnswer( + (_) async => expectedSymKey, + ); + cryptoAPI = getCrypto( + utils: mockUtils, + ); + await cryptoAPI.init(); + await cryptoAPI.keyChain.set(selfKP.publicKey, selfKP.privateKey); + + final String topicActual = await cryptoAPI.generateSharedKey( + selfKP.publicKey, + peerPublicKey, + overrideTopic: overrideTopic, + ); + verify(mockUtils.deriveSymKey(selfKP.privateKey, peerPublicKey)) + .called(1); + expect(topicActual, overrideTopic); + }, + ); + }); + + group('setSymKey', () { + test('Throws if not initialized', () async { + expect( + () async => await cryptoApiUninitialized.setSymKey('a'), + throwsA(isA()), + ); + }); + + test( + 'sets expected topic-symKey pair in keychain, returns topic', + () async { + CryptoUtils utils = CryptoUtils(); + final fakeSymKey = utils.generateRandomBytes32(); + final topic = utils.hashKey(fakeSymKey); + + final topicActual = await cryptoAPI.setSymKey(fakeSymKey); + + expect(topicActual, topic); + }, + ); + + test( + 'sets expected topic-symKey pair in keychain it override topic is provided', + () async { + CryptoUtils utils = CryptoUtils(); + final fakeSymKey = utils.generateRandomBytes32(); + const topic = 'test-topic'; + + final topicActual = await cryptoAPI.setSymKey( + fakeSymKey, + overrideTopic: topic, + ); + + expect(topicActual, topic); + }, + ); + }); + + group('deleteKeyPair', () { + test('Throws if not initialized', () async { + expect( + () async => await cryptoApiUninitialized.deleteKeyPair('a'), + throwsA(isA()), + ); + }); + + test( + 'sets expected topic-symKey pair in keychain, returns topic', + () async { + CryptoUtils utils = CryptoUtils(); + final pubKey = utils.generateRandomBytes32(); + + await cryptoAPI.keyChain.set(pubKey, 'test'); + + expect(cryptoAPI.keyChain.get(pubKey), isNotNull); + await cryptoAPI.deleteKeyPair(pubKey); + + expect(cryptoAPI.keyChain.get(pubKey), isNull); + }, + ); + }); + + group('deleteSymKey', () { + test('Throws if not initialized', () async { + expect( + () async => await cryptoApiUninitialized.deleteSymKey('a'), + throwsA(isA()), + ); + }); + + test( + 'sets expected topic-symKey pair in keychain, returns topic', + () async { + CryptoUtils utils = CryptoUtils(); + final topic = utils.generateRandomBytes32(); + + await cryptoAPI.keyChain.set(topic, 'test'); + + expect(cryptoAPI.keyChain.get(topic), isNotNull); + await cryptoAPI.deleteSymKey(topic); + + expect(cryptoAPI.keyChain.get(topic), isNull); + }, + ); + }); + + group('encode and decode', () { + const SYM_KEY = + '5720435e682cd03ee45b484f9a213f0e3246a0ccc2cca183b72ab1cbfbefb702'; + const PAYLOAD = {'id': 1, 'jsonrpc': '2.0', 'result': 'result'}; + const ENCODED = + 'AG7iJl9mMl9K04REnuWaKLQU6kwMcQWUd69OxGOJ5/A+VRRKkxnKhBeIAl4JRaIft3qZKEfnBvc7/Fife1DWcERqAfJwzPI='; + + test('Throws if not initialized', () async { + expect( + () async => await cryptoApiUninitialized.encode('a', {}), + throwsA(isA()), + ); + }); + + test( + 'encrypts payload if the passed topic is known', + () async { + final topic = await cryptoAPI.setSymKey(SYM_KEY); + final String? encoded = await cryptoAPI.encode(topic, PAYLOAD); + + final String? decoded = await cryptoAPI.decode(topic, encoded!); + expect(decoded, jsonEncode(PAYLOAD)); + + final String? decoded2 = await cryptoAPI.decode(topic, ENCODED); + expect(decoded2, jsonEncode(PAYLOAD)); + }, + ); + + test( + 'returns null if the passed topic is known', + () async { + final topic = CryptoUtils().hashKey(SYM_KEY); + final String? encoded = await cryptoAPI.encode(topic, PAYLOAD); + expect(encoded, isNull); + + final String? decoded = await cryptoAPI.decode(topic, ENCODED); + expect(decoded, isNull); + }, + ); + }); + }); + + group('Crypto Utils', () { + CryptoUtils utils = CryptoUtils(); + test('should generate keypairs properly', () { + CryptoKeyPair kp = utils.generateKeyPair(); + expect(kp.privateKey.length, 64); + expect(kp.publicKey.length, 64); + }); + + test('can derive the sym key', () async { + CryptoKeyPair kp1 = utils.generateKeyPair(); + CryptoKeyPair kp2 = utils.generateKeyPair(); + final String symKeyA = await utils.deriveSymKey( + kp1.privateKey, + kp2.publicKey, + ); + final String symKeyB = await utils.deriveSymKey( + kp2.privateKey, + kp1.publicKey, + ); + expect(symKeyA, symKeyB); + }); + + test('hashes key correctly', () { + final String hashedKey = utils.hashKey(TEST_SHARED_KEY); + expect(hashedKey, TEST_HASHED_KEY); + }); + + test('hashes messages correctly', () { + const TEST_HASHED_MESSAGE = + '15112289b5b794e68d1ea3cd91330db55582a37d0596f7b99ea8becdf9d10496'; + final String hashedKey = utils.hashMessage(testMessage); + expect(hashedKey, TEST_HASHED_MESSAGE); + }); + + test('encrypt type 0 envelope', () async { + final String encoded = await utils.encrypt( + testMessage, + TEST_SYM_KEY, + iv: TEST_IV, + ); + expect(encoded, TEST_ENCODED_TYPE_0); + final EncodingParams deserialized = utils.deserialize(encoded); + final String iv = hex.encode(deserialized.iv); + expect(iv, TEST_IV); + final String sealed = hex.encode(deserialized.sealed); + expect(sealed, TEST_SEALED); + }); + + test('decrypts type 0 envelope properly', () async { + final String decrypted = await utils.decrypt( + TEST_SYM_KEY, + TEST_ENCODED_TYPE_0, + ); + expect(decrypted, testMessage); + }); + + test('encrypt (type 1)', () async { + final String encoded = await utils.encrypt( + testMessage, + TEST_SYM_KEY, + type: 1, + iv: TEST_IV, + senderPublicKey: testSelf.publicKey, + ); + expect(encoded, TEST_ENCODED_TYPE_1); + final EncodingParams deserialized = utils.deserialize(encoded); + final String iv = hex.encode(deserialized.iv); + expect(iv, TEST_IV); + final String sealed = hex.encode(deserialized.sealed); + expect(sealed, TEST_SEALED); + }); + + test('decrypt (type 1)', () async { + const encoded = TEST_ENCODED_TYPE_1; + final EncodingValidation params = utils.validateDecoding( + encoded, + receiverPublicKey: testPeer.publicKey, + ); + expect(utils.isTypeOneEnvelope(params), true); + expect(params.type, 1); + expect(params.senderPublicKey, testSelf.publicKey); + expect(params.receiverPublicKey, testPeer.publicKey); + // print( + // await utils.deriveSymKey(TEST_PEER.privateKey, TEST_SELF.publicKey)); + final String symKey = await utils.deriveSymKey( + testPeer.privateKey, params.senderPublicKey!); + expect(symKey, TEST_SYM_KEY); + final String decrypted = await utils.decrypt(symKey, encoded); + expect(decrypted, testMessage); + }); + }); +} diff --git a/packages/reown_core/test/echo_test.dart b/packages/reown_core/test/echo_test.dart new file mode 100644 index 0000000..82dfe50 --- /dev/null +++ b/packages/reown_core/test/echo_test.dart @@ -0,0 +1,102 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; +import 'package:mockito/mockito.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/echo/echo.dart'; +import 'package:reown_core/echo/echo_client.dart'; +import 'package:reown_core/echo/i_echo_client.dart'; +import 'package:reown_core/reown_core.dart'; + +import 'shared/shared_test_utils.mocks.dart'; + +void main() { + const baseUrl = 'https://echo.walletconnect.com'; + const testFcmToken = 'fcmToken'; + const errorResponse = + '{"errors":[{"message":"The provided Tenant ID, projectId, is invalid. Please ensure it\'s valid and the url is in the format /:tenant_id/...path","name":"tenant"}],"fields":[{"description":"Invalid Tenant ID, projectId","field":"tenant_id","location":"path"}],"status":"FAILURE"}'; + + late IReownCore core; + late ICrypto crypto; + late MockHttpWrapper http; + late IEchoClient echoClient; + + setUp(() { + core = MockReownCore(); + crypto = MockCrypto(); + http = MockHttpWrapper(); + echoClient = EchoClient( + baseUrl: baseUrl, + httpClient: http, + ); + + when(core.projectId).thenReturn('projectId'); + when(core.crypto).thenReturn(crypto); + when(crypto.getClientId()).thenAnswer((_) async => 'clientId'); + }); + + group('register -', () { + test('should successfully register the fcm token', () async { + when(http.post(any, headers: anyNamed('headers'), body: anyNamed('body'))) + .thenAnswer((_) async => Response('{"status": "SUCCESS"}', 200)); + + final echo = Echo(core: core, echoClient: echoClient); + await echo.register(testFcmToken); + + verify(core.projectId).called(1); + verify(crypto.getClientId()).called(1); + verify( + http.post(any, headers: anyNamed('headers'), body: anyNamed('body')), + ).called(1); + }); + + test('should return an error on register', () async { + when(http.post(any, headers: anyNamed('headers'), body: anyNamed('body'))) + .thenAnswer((_) async => Response(errorResponse, 400)); + + final echo = Echo(core: core, echoClient: echoClient); + await expectLater( + echo.register(testFcmToken), + throwsA(isInstanceOf()), + ); + + verify(core.projectId).called(1); + verify(crypto.getClientId()).called(1); + verify( + http.post(any, headers: anyNamed('headers'), body: anyNamed('body')), + ).called(1); + }); + }); + + group('unregister -', () { + test('should successfully unregister the fcm token', () async { + when(http.delete(any, headers: anyNamed('headers'))) + .thenAnswer((_) async => Response('{"status": "SUCCESS"}', 200)); + + final echo = Echo(core: core, echoClient: echoClient); + await echo.unregister(); + + verify(core.projectId).called(1); + verify(crypto.getClientId()).called(1); + verify( + http.delete(any, headers: anyNamed('headers')), + ).called(1); + }); + + test('should return an error on unregister', () async { + when(http.delete(any, headers: anyNamed('headers'))) + .thenAnswer((_) async => Response(errorResponse, 400)); + + final echo = Echo(core: core, echoClient: echoClient); + await expectLater( + echo.unregister(), + throwsA(isInstanceOf()), + ); + + verify(core.projectId).called(1); + verify(crypto.getClientId()).called(1); + verify( + http.delete(any, headers: anyNamed('headers')), + ).called(1); + }); + }); +} diff --git a/packages/reown_core/test/pairing_store_test.dart b/packages/reown_core/test/pairing_store_test.dart new file mode 100644 index 0000000..015e404 --- /dev/null +++ b/packages/reown_core/test/pairing_store_test.dart @@ -0,0 +1,131 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:reown_core/pairing/i_json_rpc_history.dart'; +import 'package:reown_core/pairing/i_pairing.dart'; +import 'package:reown_core/pairing/json_rpc_history.dart'; +import 'package:reown_core/pairing/pairing.dart'; +import 'package:reown_core/pairing/pairing_store.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/generic_store.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_values.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(); + + group('Pairing store', () { + late IReownCore coreA; + late IReownCore coreB; + + late IPairing pairing; + late IJsonRpcHistory history; + late IPairingStore pairingStore; + late GenericStore topicToReceiverPublicKey; + + setUp(() async { + coreA = ReownCore( + relayUrl: TEST_RELAY_URL, + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + coreB = ReownCore( + relayUrl: TEST_RELAY_URL, + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + await coreA.start(); + await coreB.start(); + + pairingStore = PairingStore( + storage: coreA.storage, + context: StoreVersions.CONTEXT_PAIRINGS, + version: StoreVersions.VERSION_PAIRINGS, + fromJson: (dynamic value) { + return PairingInfo.fromJson(value as Map); + }, + ); + history = JsonRpcHistory( + storage: coreA.storage, + context: StoreVersions.CONTEXT_JSON_RPC_HISTORY, + version: StoreVersions.VERSION_JSON_RPC_HISTORY, + fromJson: (dynamic value) => JsonRpcRecord.fromJson(value), + ); + topicToReceiverPublicKey = GenericStore( + storage: coreA.storage, + context: StoreVersions.CONTEXT_TOPIC_TO_RECEIVER_PUBLIC_KEY, + version: StoreVersions.VERSION_TOPIC_TO_RECEIVER_PUBLIC_KEY, + fromJson: (dynamic value) => ReceiverPublicKey.fromJson(value), + ); + pairing = Pairing( + core: coreA, + pairings: pairingStore, + history: history, + topicToReceiverPublicKey: topicToReceiverPublicKey, + ); + }); + + tearDown(() async { + await coreA.relayClient.disconnect(); + await coreB.relayClient.disconnect(); + }); + + group('pairing init', () { + test('deletes expired records', () async { + await history.init(); + await history.set( + '1', + const JsonRpcRecord( + id: 1, + topic: '1', + method: 'eth_sign', + params: '', + ), + ); + await history.set( + '2', + const JsonRpcRecord( + id: 2, + topic: '1', + method: 'eth_sign', + params: '', + expiry: 0, + ), + ); + await pairingStore.init(); + await pairingStore.set( + 'expired', + PairingInfo( + topic: 'expired', + expiry: -1, + relay: Relay( + 'irn', + ), + active: true, + ), + ); + await topicToReceiverPublicKey.init(); + await topicToReceiverPublicKey.set( + 'abc', + const ReceiverPublicKey( + topic: 'abc', + publicKey: 'def', + expiry: -1, + ), + ); + + expect(history.getAll().length, 2); + expect(pairingStore.getAll().length, 1); + expect(topicToReceiverPublicKey.getAll().length, 1); + await pairing.init(); + expect(history.getAll().length, 0); + expect(pairingStore.getAll().length, 0); + expect(topicToReceiverPublicKey.getAll().length, 0); + }); + }); + }); +} diff --git a/packages/reown_core/test/pairing_test.dart b/packages/reown_core/test/pairing_test.dart new file mode 100644 index 0000000..1a8c899 --- /dev/null +++ b/packages/reown_core/test/pairing_test.dart @@ -0,0 +1,483 @@ +@Timeout(Duration(seconds: 45)) + +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:reown_core/pairing/utils/json_rpc_utils.dart'; +import 'package:reown_core/reown_core.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_values.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(); + + test('Format and parses URI correctly', () { + Uri response = ReownCoreUtils.formatUri( + protocol: 'wc', + version: '2', + topic: 'abc', + symKey: 'xyz', + relay: Relay('irn'), + methods: [ + [MethodConstants.WC_SESSION_PROPOSE], + ['wc_authBatchRequest'], + ], + ); + expect( + Uri.decodeFull(response.toString()), + 'wc:abc@2?relay-protocol=irn&symKey=xyz&methods=wc_sessionPropose,wc_authBatchRequest', + ); + + URIParseResult parsed = ReownCoreUtils.parseUri(response); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v2); + expect(parsed.topic, 'abc'); + expect(parsed.v2Data!.symKey, 'xyz'); + expect(parsed.v2Data!.relay.protocol, 'irn'); + expect(parsed.v2Data!.methods.length, 2); + expect(parsed.v2Data!.methods[0], MethodConstants.WC_SESSION_PROPOSE); + // expect(parsed.v2Data!.methods[1], MethodConstants.WC_AUTH_REQUEST); + expect(parsed.v2Data!.methods[1], 'wc_authBatchRequest'); + + response = ReownCoreUtils.formatUri( + protocol: 'wc', + version: '2', + topic: 'abc', + symKey: 'xyz', + relay: Relay('irn'), + methods: null, + ); + expect( + Uri.decodeFull(response.toString()), + 'wc:abc@2?relay-protocol=irn&symKey=xyz', + ); + + parsed = ReownCoreUtils.parseUri(response); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v2); + expect(parsed.topic, 'abc'); + expect(parsed.v2Data!.symKey, 'xyz'); + expect(parsed.v2Data!.relay.protocol, 'irn'); + expect(parsed.v2Data!.methods.length, 0); + + // Can parse URI with missing methods param + response = Uri.parse('wc:abc@2?relay-protocol=irn&symKey=xyz'); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v2); + expect(parsed.topic, 'abc'); + expect(parsed.v2Data!.symKey, 'xyz'); + expect(parsed.v2Data!.relay.protocol, 'irn'); + expect(parsed.v2Data!.methods.length, 0); + + // V1 testing + parsed = ReownCoreUtils.parseUri(Uri.parse( + 'wc:00e46b69-d0cc-4b3e-b6a2-cee442f97188@1?bridge=https%3A%2F%2Fbridge.reown.org&key=91303dedf64285cbbaf9120f6e9d160a5c8aa3deb67017a3874cd272323f48ae')); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v1); + expect(parsed.topic, '00e46b69-d0cc-4b3e-b6a2-cee442f97188'); + expect(parsed.v2Data, null); + expect(parsed.v1Data!.key, + '91303dedf64285cbbaf9120f6e9d160a5c8aa3deb67017a3874cd272323f48ae'); + expect(parsed.v1Data!.bridge, 'https://bridge.reown.org'); + }); + + group('history', () { + test('deletes old records', () async {}); + }); + + group('Pairing API', () { + late IReownCore coreA; + late IReownCore coreB; + + setUp(() async { + coreA = ReownCore( + relayUrl: TEST_RELAY_URL, + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + coreB = ReownCore( + relayUrl: TEST_RELAY_URL, + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + await coreA.start(); + await coreB.start(); + }); + + tearDown(() async { + await coreA.relayClient.disconnect(); + await coreB.relayClient.disconnect(); + }); + + test('Initializes', () async { + expect(coreA.pairing.getPairings().length, 0); + expect(coreB.pairing.getPairings().length, 0); + }); + + group('create', () { + test('returns pairing topic and URI in expected format', () async { + Completer completer = Completer(); + int counter = 0; + coreA.pairing.onPairingCreate.subscribe((args) { + counter++; + completer.complete(); + }); + + CreateResponse response = await coreA.pairing.create(); + await completer.future; + expect(response.topic.length, 64); + // print(response.uri); + // print('${coreA.protocol}:${response.topic}@${coreA.version}'); + expect( + response.uri.toString().startsWith( + '${coreA.protocol}:${response.topic}@${coreA.version}', + ), + true, + ); + expect(counter, 1); + + response = await coreB.pairing.create( + methods: [ + [MethodConstants.WC_SESSION_PROPOSE], + ['wc_authBatchRequest'], + ], + ); + + final URIParseResult parsed = ReownCoreUtils.parseUri(response.uri); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v2); + expect(parsed.v2Data!.relay.protocol, 'irn'); + expect(parsed.v2Data!.methods.length, 2); + expect(parsed.v2Data!.methods[0], MethodConstants.WC_SESSION_PROPOSE); + // expect(parsed.v2Data!.methods[1], MethodConstants.WC_AUTH_REQUEST); + expect(parsed.v2Data!.methods[1], 'wc_authBatchRequest'); + }); + }); + + group('pair', () { + test('can pair via provided URI', () async { + final CreateResponse response = await coreA.pairing.create(); + + Completer completer = Completer(); + int counter = 0; + coreB.pairing.onPairingCreate.subscribe((args) { + counter++; + completer.complete(); + }); + + await coreB.pairing.pair(uri: response.uri, activatePairing: false); + await completer.future; + + expect(counter, 1); + + expect(coreA.pairing.getPairings().length, 1); + expect(coreB.pairing.getPairings().length, 1); + expect( + coreA.pairing.getPairings()[0].topic, + coreB.pairing.getPairings()[0].topic, + ); + expect(coreA.pairing.getPairings()[0].active, false); + expect(coreB.pairing.getPairings()[0].active, false); + }); + + test('can pair via provided URI', () async { + final CreateResponse response = await coreA.pairing.create(); + + await coreB.pairing.pair(uri: response.uri, activatePairing: true); + expect(coreA.pairing.getPairings()[0].active, false); + expect(coreB.pairing.getPairings()[0].active, true); + }); + }); + + test('can activate pairing', () async { + final CreateResponse response = await coreA.pairing.create(); + + await coreB.pairing.pair(uri: response.uri, activatePairing: false); + PairingInfo? pairing = coreB.pairing.getStore().get(response.topic); + + expect(pairing != null, true); + expect(pairing!.active, false); + final int expiry = pairing.expiry; + await coreB.pairing.activate(topic: response.topic); + PairingInfo? pairing2 = coreB.pairing.getStore().get(response.topic); + expect(pairing2 != null, true); + expect(pairing2!.active, true); + expect(pairing2.expiry > expiry, true); + }); + + test('can update expiry', () async { + final CreateResponse response = await coreA.pairing.create(); + const int mockExpiry = 1111111; + + await coreA.pairing + .updateExpiry(topic: response.topic, expiry: mockExpiry); + expect(coreA.pairing.getStore().get(response.topic)!.expiry, mockExpiry); + expect(coreA.expirer.get(response.topic), mockExpiry); + }); + + test('update expiry throws error if expiry past 30 days', () async { + final CreateResponse response = await coreA.pairing.create(); + final int mockExpiry = ReownCoreUtils.calculateExpiry( + ReownConstants.THIRTY_DAYS + 1, + ); + + expect( + () async => await coreA.pairing.updateExpiry( + topic: response.topic, + expiry: mockExpiry, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expiry cannot be more than 30 days away', + ), + ), + ); + }); + + test('can update peer metadata', () async { + final CreateResponse response = await coreA.pairing.create(); + PairingMetadata mock = const PairingMetadata( + name: 'Mock', + description: 'Mock Metadata', + url: 'https://mockurl.com', + icons: [], + ); + + expect( + coreA.pairing.getStore().get(response.topic)!.peerMetadata == null, + true, + ); + coreA.pairing.updateMetadata(topic: response.topic, metadata: mock); + expect( + coreA.pairing.getStore().get(response.topic)!.peerMetadata!.name, + mock.name, + ); + }); + + test('clients can ping each other', () async { + // TODO more logs to check any fails in the future. + final pairingInfo = await coreA.pairing.create(); + + final completer = Completer(); + coreB.pairing.onPairingPing.subscribe((args) { + expect(args != null, true); + completer.complete(); + }); + await coreB.pairing.pair(uri: pairingInfo.uri, activatePairing: true); + + await coreA.pairing.activate(topic: pairingInfo.topic); + await coreA.pairing.ping(topic: pairingInfo.topic); + + await completer.future; + }); + + test('can disconnect from a known pairing', () async { + final CreateResponse response = await coreA.pairing.create(); + expect(coreA.pairing.getStore().getAll().length, 1); + expect(coreB.pairing.getStore().getAll().length, 0); + await coreB.pairing.pair(uri: response.uri, activatePairing: true); + expect(coreA.pairing.getStore().getAll().length, 1); + expect(coreB.pairing.getStore().getAll().length, 1); + bool hasDeletedA = false; + bool hasDeletedB = false; + + Completer completerA = Completer(); + Completer completerB = Completer(); + coreA.pairing.onPairingDelete.subscribe((args) { + expect(args != null, true); + expect(args!.topic != null, true); + expect(args.error == null, true); + hasDeletedA = true; + completerA.complete(); + }); + coreB.pairing.onPairingDelete.subscribe((args) { + expect(args != null, true); + expect(args!.topic != null, true); + expect(args.error == null, true); + hasDeletedB = true; + completerB.complete(); + }); + + await coreB.pairing.disconnect(topic: response.topic); + + await completerA.future; + await completerB.future; + + expect(hasDeletedA, true); + expect(hasDeletedB, true); + expect(coreA.pairing.getStore().getAll().length, 0); + expect(coreB.pairing.getStore().getAll().length, 0); + }); + + group('Validations', () { + setUp(() async { + coreA = ReownCore( + relayUrl: TEST_RELAY_URL, + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + await coreA.start(); + }); + + tearDown(() async { + await coreA.relayClient.disconnect(); + }); + + group('Pairing', () { + test('throws when no empty/invalid uri is provided', () async { + expect( + () async => await coreA.pairing.pair(uri: Uri.parse('')), + throwsA( + predicate( + (e) => + e is ReownCoreError && + e.message == 'Invalid URI: Missing @', + ), + ), + ); + expect( + () async => await coreA.pairing.pair(uri: Uri.parse('wc:abc')), + throwsA( + predicate( + (e) => + e is ReownCoreError && + e.message == 'Invalid URI: Missing @', + ), + ), + ); + }); + + test("throws when required methods aren't contained in registered", + () async { + const String uriWithMethods = + '$TEST_URI&methods=[wc_sessionPropose],[wc_authBatchRequest]'; + expect( + () async => + await coreA.pairing.pair(uri: Uri.parse(uriWithMethods)), + throwsA( + predicate( + (e) => + e is ReownCoreError && + e.message == + 'Unsupported wc_ method. The following methods are not registered: wc_sessionPropose, wc_authBatchRequest.', + ), + ), + ); + coreA.pairing.register( + method: 'wc_sessionPropose', + function: (s, r, [t = TransportType.relay]) => {}, + type: ProtocolType.sign, + ); + expect( + () async => + await coreA.pairing.pair(uri: Uri.parse(uriWithMethods)), + throwsA( + predicate( + (e) => + e is ReownCoreError && + e.message == + 'Unsupported wc_ method. The following methods are not registered: wc_authBatchRequest.', + ), + ), + ); + // coreA.pairing.register( + // method: 'wc_authRequest', + // function: (s, r, [t = TransportType.relay]) => {}, + // type: ProtocolType.auth, + // ); + // expect( + // () async => + // await coreA.pairing.pair(uri: Uri.parse(uriWithMethods)), + // throwsA( + // predicate( + // (e) => + // e is ReownCoreError && + // e.message == + // 'Unsupported wc_ method. The following methods are not registered: wc_authBatchRequest.', + // ), + // ), + // ); + }); + + test('succeeds when required methods are contained in registered', + () async { + List registeredFunctions = [ + RegisteredFunction( + method: MethodConstants.WC_SESSION_PROPOSE, + function: (s, r, [t = TransportType.relay]) => {}, + type: ProtocolType.sign, + ), + // RegisteredFunction( + // method: 'wc_authRequest', + // function: (s, r, [t = TransportType.relay]) => {}, + // type: ProtocolType.sign, + // ), + RegisteredFunction( + method: 'wc_authBatchRequest', + function: (s, r, [t = TransportType.relay]) => {}, + type: ProtocolType.sign, + ) + ]; + expect( + JsonRpcUtils.validateMethods( + ['wc_sessionPropose'], + registeredFunctions, + ), + true, + ); + expect( + JsonRpcUtils.validateMethods( + ['wc_sessionPropose'], + registeredFunctions, + ), + true, + ); + expect( + JsonRpcUtils.validateMethods( + ['wc_sessionPropose', 'wc_authBatchRequest'], + registeredFunctions, + ), + true, + ); + }); + }); + + group('Ping', () { + test('throws when unused topic is provided', () async { + expect( + () async => await coreA.pairing.ping(topic: 'abc'), + throwsA( + predicate((e) => + e is ReownCoreError && + e.message == + "No matching key. pairing topic doesn't exist: abc"), + ), + ); + }); + }); + + group('Disconnect', () { + test('throws when unused topic is provided', () async { + expect( + () async => await coreA.pairing.disconnect(topic: 'abc'), + throwsA( + predicate((e) => + e is ReownCoreError && + e.message == + "No matching key. pairing topic doesn't exist: abc"), + ), + ); + }); + }); + }); + }); +} diff --git a/packages/reown_core/test/relay_auth_test.dart b/packages/reown_core/test/relay_auth_test.dart new file mode 100644 index 0000000..251b54e --- /dev/null +++ b/packages/reown_core/test/relay_auth_test.dart @@ -0,0 +1,128 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:reown_core/relay_auth/relay_auth.dart'; +import 'package:reown_core/relay_auth/relay_auth_models.dart'; +import 'package:reown_core/utils/constants.dart'; + +import 'shared/shared_test_utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(); + + group('Relay Auth/API', () { + // Client will sign a unique identifier as the subject + const TEST_SUBJECT = + 'c479fe5dc464e771e78b193d239a65b58d278cad1c34bfb0b5716e5bb514928e'; + + // Client will include the server endpoint as audience + const TEST_AUDIENCE = 'wss://relay.walletconnect.com'; + + // Client will use the JWT for 24 hours + const TEST_TTL = 86400; + + // Test issued at timestamp in seconds + const TEST_IAT = 1656910097; + + // Test seed to generate the same key pair + // const TEST_SEED = + // "58e0254c211b858ef7896b00e3f36beeb13d568d47c6031c4218b87718061295"; + + // Expected secret key for above seed + const EXPECTED_SECRET_KEY = + '58e0254c211b858ef7896b00e3f36beeb13d568d47c6031c4218b87718061295884ab67f787b69e534bfdba8d5beb4e719700e90ac06317ed177d49e5a33be5a'; + + const EXPECTED_PUBLIC_KEY = + '884ab67f787b69e534bfdba8d5beb4e719700e90ac06317ed177d49e5a33be5a'; + + // Expected issuer using did:key method + const EXPECTED_ISS = + 'did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH'; + + // Expected expiry given injected issued at + const EXPECTED_EXP = TEST_IAT + TEST_TTL; + + // Expected data encode for given payload + const EXPECTED_DATA = + 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtvZEhad25lVlJTaHRhTGY4SktZa3hwREdwMXZHWm5wR21kQnBYOE0yZXh4SCIsInN1YiI6ImM0NzlmZTVkYzQ2NGU3NzFlNzhiMTkzZDIzOWE2NWI1OGQyNzhjYWQxYzM0YmZiMGI1NzE2ZTViYjUxNDkyOGUiLCJhdWQiOiJ3c3M6Ly9yZWxheS53YWxsZXRjb25uZWN0LmNvbSIsImlhdCI6MTY1NjkxMDA5NywiZXhwIjoxNjU2OTk2NDk3fQ'; + + // Expected signature for given data + const EXPECTED_SIG = + 'bAKl1swvwqqV_FgwvD4Bx3Yp987B9gTpZctyBviA-EkAuWc8iI8SyokOjkv9GJESgid4U8Tf2foCgrQp2qrxBA'; + + // Expected JWT for given payload + const EXPECTED_JWT = + 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtvZEhad25lVlJTaHRhTGY4SktZa3hwREdwMXZHWm5wR21kQnBYOE0yZXh4SCIsInN1YiI6ImM0NzlmZTVkYzQ2NGU3NzFlNzhiMTkzZDIzOWE2NWI1OGQyNzhjYWQxYzM0YmZiMGI1NzE2ZTViYjUxNDkyOGUiLCJhdWQiOiJ3c3M6Ly9yZWxheS53YWxsZXRjb25uZWN0LmNvbSIsImlhdCI6MTY1NjkxMDA5NywiZXhwIjoxNjU2OTk2NDk3fQ.bAKl1swvwqqV_FgwvD4Bx3Yp987B9gTpZctyBviA-EkAuWc8iI8SyokOjkv9GJESgid4U8Tf2foCgrQp2qrxBA'; + + // Expected decoded JWT using did-jwt + final expectedDecoded = JWTDecoded( + Uint8List.fromList(utf8.encode(EXPECTED_DATA)), + Uint8List.fromList(utf8.encode(EXPECTED_SIG)), + JWTPayload( + EXPECTED_ISS, + TEST_SUBJECT, + TEST_AUDIENCE, + TEST_IAT, + EXPECTED_EXP, + ), + ); + + RelayAuthKeyPair keyPair = RelayAuthKeyPair.fromStrings( + EXPECTED_SECRET_KEY, + EXPECTED_PUBLIC_KEY, + ); + + late RelayAuth relayAuth; + late RelayAuth relayApi; + + setUp(() async { + relayAuth = RelayAuth(); + relayApi = RelayAuth(); + }); + + test('encode and decode issuer', () async { + String iss = relayAuth.encodeIss(keyPair.publicKeyBytes); + expect(iss, EXPECTED_ISS); + Uint8List publicKey = relayAuth.decodeIss(iss); + expect(publicKey, keyPair.publicKeyBytes); + }); + + test('encode and decode data', () async { + Uint8List data = relayAuth.encodeData(expectedDecoded); + expect(data, utf8.encode(EXPECTED_DATA)); + }); + + test('Sign and verify JWT', () async { + RelayAuthKeyPair keyPair1 = RelayAuthKeyPair.fromStrings( + 'db74f4788fbaf87bc8e3cd6a84ae82586fd4fd701216a1d18f7ed936cb3a8cfb579e2b8f7190abb558ee5461a852389bdff6079b3a4eabc3e759a7775334f7f7', + '579e2b8f7190abb558ee5461a852389bdff6079b3a4eabc3e759a7775334f7f7', + ); + String jwt1 = await relayApi.signJWT( + sub: '6a26d1c13b8f7bfda6e7415f6db94084a3b97f2990da5216fa5aa7b80f08391d', + aud: TEST_AUDIENCE, + ttl: ReownConstants.ONE_DAY, + keyPair: keyPair1, + iat: 1674244632, + ); + expect( + jwt1, + 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtrTUhRRlYzYkNUOXVIV3Z6Z1N4UXNMbVZzMVc1c0NVdzhyQnBmamg5ZHNydiIsInN1YiI6IjZhMjZkMWMxM2I4ZjdiZmRhNmU3NDE1ZjZkYjk0MDg0YTNiOTdmMjk5MGRhNTIxNmZhNWFhN2I4MGYwODM5MWQiLCJhdWQiOiJ3c3M6Ly9yZWxheS53YWxsZXRjb25uZWN0LmNvbSIsImlhdCI6MTY3NDI0NDYzMiwiZXhwIjoxNjc0MzMxMDMyfQ.FUfsQtGuyMTOfEjQUdfr_KfBEaftEQPU9lpQ_mNwgpPlzqk2Hmn9RKnbTnvL9rPWzbm5wnWrc7LuzUQGqp99Cw', + ); + + String jwt = await relayApi.signJWT( + sub: TEST_SUBJECT, + aud: TEST_AUDIENCE, + ttl: TEST_TTL, + keyPair: keyPair, + iat: TEST_IAT, + ); + expect(jwt, EXPECTED_JWT); + bool verified = await relayApi.verifyJWT(jwt); + expect(verified, true); + }); + }); +} diff --git a/packages/reown_core/test/relay_client_test.dart b/packages/reown_core/test/relay_client_test.dart new file mode 100644 index 0000000..4bf118b --- /dev/null +++ b/packages/reown_core/test/relay_client_test.dart @@ -0,0 +1,309 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; +import 'package:mockito/mockito.dart'; +import 'package:reown_core/relay_client/relay_client.dart'; +import 'package:reown_core/reown_core.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_utils.mocks.dart'; +import 'shared/shared_test_values.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(); + + const TEST_TOPIC = 'abc123'; + const TEST_MESSAGE = 'swagmasterss'; + + test('relays are correct', () { + expect( + ReownConstants.DEFAULT_RELAY_URL, + 'wss://relay.walletconnect.org', + ); + expect( + ReownConstants.DEFAULT_PUSH_URL, + 'https://echo.walletconnect.org', + ); + }); + + group('Relay throws errors', () { + test('on init if there is no internet connection', () async { + final MockWebSocketHandler mockWebSocketHandler = MockWebSocketHandler(); + when(mockWebSocketHandler.connect()).thenThrow(const ReownCoreError( + code: -1, + message: 'No internet connection: test', + )); + + const testRelayUrl = 'wss://relay.test.com'; + IReownCore core = ReownCore( + projectId: 'abc', + memoryStore: true, + relayUrl: testRelayUrl, + ); + core.relayClient = RelayClient( + core: core, + messageTracker: getMessageTracker(core: core), + topicMap: getTopicMap(core: core), + socketHandler: mockWebSocketHandler, + ); + int errorCounter = 0; + core.relayClient.onRelayClientError.subscribe((args) { + errorCounter++; + expect(args!.error.message, 'No internet connection: test'); + }); + await core.storage.init(); + await core.crypto.init(); + await core.relayClient.init(); + + verify(mockWebSocketHandler.setup( + url: argThat( + contains(testRelayUrl), + named: 'url', + ), + )).called(1); + verify(mockWebSocketHandler.connect()).called(1); + expect(errorCounter, 1); + }); + + test('when connection parameters are invalid', () async { + final http = MockHttpWrapper(); + when(http.get(any)).thenAnswer( + (_) async => Response('', 3000), + ); + final IReownCore core = ReownCore( + projectId: 'abc', + memoryStore: true, + httpClient: http, + ); + + Completer completer = Completer(); + core.relayClient.onRelayClientError.subscribe((args) { + expect(args!.error, isA()); + expect(args.error.code, 3000); + completer.complete(); + }); + + await core.start(); + + await completer.future; + + core.relayClient.onRelayClientError.unsubscribeAll(); + }); + }); + + test('Relay client connect and disconnect events broadcast', () async { + IReownCore coreA = ReownCore( + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + IReownCore coreB = ReownCore( + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + + int counterA = 0, counterB = 0, counterC = 0, counterD = 0; + Completer completerA = Completer(), + completerB = Completer(), + completerC = Completer(), + completerD = Completer(); + coreA.relayClient.onRelayClientConnect.subscribe((args) { + expect(args, null); + counterA++; + completerA.complete(); + }); + coreA.relayClient.onRelayClientDisconnect.subscribe((args) { + expect(args, null); + counterB++; + completerB.complete(); + }); + coreB.relayClient.onRelayClientConnect.subscribe((args) { + expect(args, null); + counterC++; + completerC.complete(); + }); + coreB.relayClient.onRelayClientDisconnect.subscribe((args) { + expect(args, null); + counterD++; + completerD.complete(); + }); + + await coreA.start(); + await coreB.start(); + + if (!completerA.isCompleted) { + coreA.logger.i('relay client test waiting sessionACompleter'); + await completerA.future; + } + if (!completerC.isCompleted) { + coreA.logger.i('relay client test waiting sessionCCompleter'); + await completerC.future; + } + + expect(counterA, 1); + expect(counterC, 1); + + await coreA.relayClient.disconnect(); + await coreB.relayClient.disconnect(); + + if (!completerB.isCompleted) { + coreA.logger.i('relay client test waiting sessionBCompleter'); + await completerB.future; + } + if (!completerD.isCompleted) { + coreA.logger.i('relay client test waiting sessionDCompleter'); + await completerD.future; + } + + expect(counterB, 1); + expect(counterD, 1); + }); + + group('Relay Client', () { + IReownCore core = ReownCore( + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + late RelayClient relayClient; + MockMessageTracker messageTracker = MockMessageTracker(); + + setUp(() async { + await core.start(); + relayClient = RelayClient( + core: core, + messageTracker: messageTracker, + topicMap: getTopicMap(core: core), + ); + await relayClient.init(); + }); + + // tearDown(() async { + // await relayClient.disconnect(); + // }); + + test('Handle publish broadcasts and stores the message event', () async { + await relayClient.topicMap.set(TEST_TOPIC, 'test'); + + int counter = 0; + relayClient.onRelayClientMessage.subscribe((MessageEvent? args) { + counter++; + }); + + when(messageTracker.messageIsRecorded( + TEST_TOPIC, + TEST_MESSAGE, + )).thenAnswer( + (_) => false, + ); + + bool published = await relayClient.handlePublish( + TEST_TOPIC, + TEST_MESSAGE, + ); + expect(published, true); + expect(counter, 1); + + verify( + messageTracker.recordMessageEvent( + TEST_TOPIC, + TEST_MESSAGE, + ), + ).called(1); + }); + + group('JSON RPC', () { + late IReownCore coreA; + late IReownCore coreB; + + setUp(() async { + coreA = ReownCore( + relayUrl: TEST_RELAY_URL, + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + coreB = ReownCore( + relayUrl: TEST_RELAY_URL, + projectId: TEST_PROJECT_ID, + memoryStore: true, + httpClient: getHttpWrapper(), + ); + await coreA.start(); + await coreB.start(); + coreA.relayClient = RelayClient( + core: coreA, + messageTracker: getMessageTracker(core: coreA), + topicMap: getTopicMap(core: coreA), + ); + coreB.relayClient = RelayClient( + core: coreB, + messageTracker: getMessageTracker(core: coreB), + topicMap: getTopicMap(core: coreB), + ); + await coreA.relayClient.init(); + await coreB.relayClient.init(); + }); + + tearDown(() async { + await coreA.relayClient.disconnect(); + await coreB.relayClient.disconnect(); + }); + + test('Publish is received by clients', () async { + CreateResponse response = await coreA.pairing.create(); + await coreB.pairing.pair(uri: response.uri, activatePairing: true); + coreA.pairing.activate(topic: response.topic); + + Completer completerA = Completer(); + Completer completerB = Completer(); + int counterA = 0; + int counterB = 0; + coreA.relayClient.onRelayClientMessage.subscribe((args) { + expect(args == null, false); + expect(args!.topic, response.topic); + expect(args.message, 'Swag'); + counterA++; + completerA.complete(); + }); + coreB.relayClient.onRelayClientMessage.subscribe((args) { + expect(args == null, false); + expect(args!.topic, response.topic); + expect(args.message, TEST_MESSAGE); + counterB++; + completerB.complete(); + }); + + // await coreA.relayClient.unsubscribe(response.topic); + // await coreB.relayClient.unsubscribe(response.topic); + + await coreA.relayClient.publish( + topic: response.topic, + message: TEST_MESSAGE, + ttl: 6000, + tag: 0, + ); + await coreB.relayClient.publish( + topic: response.topic, + message: 'Swag', + ttl: 6000, + tag: 0, + ); + + if (!completerA.isCompleted) { + await completerA.future; + } + if (!completerB.isCompleted) { + await completerB.future; + } + + expect(counterA, 1); + expect(counterB, 1); + }); + }); + }); +} diff --git a/packages/reown_core/test/reown_core_test.dart b/packages/reown_core/test/reown_core_test.dart new file mode 100644 index 0000000..9d96ab5 --- /dev/null +++ b/packages/reown_core/test/reown_core_test.dart @@ -0,0 +1,90 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:reown_core/relay_client/relay_client.dart'; +import 'package:reown_core/reown_core.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_utils.mocks.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(['none']); + + group('ReownCore throws errors', () { + test('on start if there is no internet connection', () async { + final MockWebSocketHandler mockWebSocketHandler = MockWebSocketHandler(); + when(mockWebSocketHandler.connect()).thenThrow(const ReownCoreError( + code: -1, + message: 'No internet connection: test', + )); + + IReownCore core = ReownCore( + projectId: 'abc', + memoryStore: true, + ); + core.relayClient = RelayClient( + core: core, + messageTracker: getMessageTracker(core: core), + topicMap: getTopicMap(core: core), + socketHandler: mockWebSocketHandler, + ); + int errorCount = 0; + core.relayClient.onRelayClientError.subscribe((args) { + errorCount++; + expect(args!.error.message, 'No internet connection: test'); + }); + + await core.start(); + expect(errorCount, 1); + expect(core.relayUrl, ReownConstants.DEFAULT_RELAY_URL); + + verifyInOrder([ + mockWebSocketHandler.setup( + url: argThat( + contains( + ReownConstants.DEFAULT_RELAY_URL, + ), + named: 'url', + ), + ), + mockWebSocketHandler.connect(), + ]); + + core.relayClient.onRelayClientError.unsubscribeAll(); + + const testRelayUrl = 'wss://relay.test.com'; + core = ReownCore( + projectId: 'abc', + memoryStore: true, + relayUrl: testRelayUrl, + ); + core.relayClient = RelayClient( + core: core, + messageTracker: getMessageTracker(core: core), + topicMap: getTopicMap(core: core), + socketHandler: mockWebSocketHandler, + ); + errorCount = 0; + core.relayClient.onRelayClientError.subscribe((args) { + errorCount++; + expect(args!.error.message, 'No internet connection: test'); + }); + + await core.start(); + + // Check that setup was called once for custom URL + verify( + mockWebSocketHandler.setup( + url: argThat( + contains(testRelayUrl), + named: 'url', + ), + ), + ).called(1); + verify(mockWebSocketHandler.connect()).called(1); + expect(errorCount, 1); + expect(core.relayUrl, testRelayUrl); + }); + }); +} diff --git a/packages/reown_core/test/shared/shared_test_utils.dart b/packages/reown_core/test/shared/shared_test_utils.dart new file mode 100644 index 0000000..9ac2d19 --- /dev/null +++ b/packages/reown_core/test/shared/shared_test_utils.dart @@ -0,0 +1,120 @@ +// ignore_for_file: no_leading_underscores_for_local_identifiers + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:reown_core/crypto/crypto.dart'; +import 'package:reown_core/crypto/crypto_utils.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/relay_client/i_message_tracker.dart'; +import 'package:reown_core/relay_client/message_tracker.dart'; +import 'package:reown_core/relay_client/websocket/http_client.dart'; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +import 'shared_test_utils.mocks.dart'; + +@GenerateMocks([ + CryptoUtils, + Crypto, + MessageTracker, + HttpWrapper, + ReownCore, + WebSocketHandler, +]) +class SharedTestUtils {} + +ICrypto getCrypto({ + IReownCore? core, + MockCryptoUtils? utils, +}) { + final IReownCore _core = core ?? ReownCore(projectId: '', memoryStore: true); + final ICrypto crypto = Crypto( + core: _core, + keyChain: GenericStore( + storage: _core.storage, + context: StoreVersions.CONTEXT_KEYCHAIN, + version: StoreVersions.VERSION_KEYCHAIN, + fromJson: (dynamic value) => value as String, + ), + utils: utils, + ); + _core.crypto = crypto; + return crypto; +} + +IMessageTracker getMessageTracker({ + IReownCore? core, +}) { + final IReownCore _core = core ?? ReownCore(projectId: '', memoryStore: true); + return MessageTracker( + storage: _core.storage, + context: StoreVersions.CONTEXT_MESSAGE_TRACKER, + version: StoreVersions.VERSION_MESSAGE_TRACKER, + fromJson: (dynamic value) => ReownCoreUtils.convertMapTo(value), + ); +} + +IGenericStore getTopicMap({ + IReownCore? core, +}) { + final IReownCore _core = core ?? ReownCore(projectId: '', memoryStore: true); + return GenericStore( + storage: _core.storage, + context: StoreVersions.CONTEXT_TOPIC_MAP, + version: StoreVersions.VERSION_TOPIC_MAP, + fromJson: (dynamic value) => value as String, + ); +} + +MockHttpWrapper getHttpWrapper() { + final MockHttpWrapper httpWrapper = MockHttpWrapper(); + when(httpWrapper.get(any)).thenAnswer((_) async => Response('', 200)); + // when(httpWrapper.post( + // url: anyNamed('url'), + // body: anyNamed('body'), + // )).thenAnswer((_) async => ''); + + return httpWrapper; +} + +mockPackageInfo() { + PackageInfo.setMockInitialValues( + appName: _mockInitialValues['appName'], + packageName: _mockInitialValues['packageName'], + version: _mockInitialValues['version'], + buildNumber: _mockInitialValues['buildNumber'], + buildSignature: _mockInitialValues['buildSignature'], + ); +} + +mockConnectivity([List values = const ['wifi']]) { + const channel = MethodChannel('dev.fluttercommunity.plus/connectivity'); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler( + channel.name, + (data) async { + final call = channel.codec.decodeMethodCall(data); + if (call.method == 'getAll') { + return channel.codec.encodeSuccessEnvelope(_mockInitialValues); + } + if (call.method == 'check') { + return channel.codec.encodeSuccessEnvelope(values); + } + return null; + }, + ); +} + +Map get _mockInitialValues => { + 'appName': 'ReownCoreTest', + 'packageName': 'com.walletconnect.flutterdapp', + 'version': '1.0', + 'buildNumber': '2', + 'buildSignature': 'buildSignature', + }; diff --git a/packages/reown_core/test/shared/shared_test_utils.mocks.dart b/packages/reown_core/test/shared/shared_test_utils.mocks.dart new file mode 100644 index 0000000..e422282 --- /dev/null +++ b/packages/reown_core/test/shared/shared_test_utils.mocks.dart @@ -0,0 +1,1558 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in reown_core/test/shared/shared_test_utils.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i23; +import 'dart:typed_data' as _i21; + +import 'package:event/event.dart' as _i8; +import 'package:http/http.dart' as _i9; +import 'package:logger/logger.dart' as _i19; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i22; +import 'package:reown_core/connectivity/i_connectivity.dart' as _i17; +import 'package:reown_core/core_impl.dart' as _i28; +import 'package:reown_core/crypto/crypto.dart' as _i24; +import 'package:reown_core/crypto/crypto_models.dart' as _i2; +import 'package:reown_core/crypto/crypto_utils.dart' as _i20; +import 'package:reown_core/crypto/i_crypto.dart' as _i10; +import 'package:reown_core/crypto/i_crypto_utils.dart' as _i5; +import 'package:reown_core/echo/i_echo.dart' as _i14; +import 'package:reown_core/heartbit/i_heartbeat.dart' as _i15; +import 'package:reown_core/i_core_impl.dart' as _i3; +import 'package:reown_core/pairing/i_expirer.dart' as _i12; +import 'package:reown_core/pairing/i_pairing.dart' as _i13; +import 'package:reown_core/relay_auth/i_relay_auth.dart' as _i6; +import 'package:reown_core/relay_client/i_relay_client.dart' as _i11; +import 'package:reown_core/relay_client/message_tracker.dart' as _i25; +import 'package:reown_core/relay_client/websocket/http_client.dart' as _i27; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart' + as _i29; +import 'package:reown_core/store/i_generic_store.dart' as _i4; +import 'package:reown_core/store/i_store.dart' as _i7; +import 'package:reown_core/store/link_mode_store.dart' as _i18; +import 'package:reown_core/store/store_models.dart' as _i26; +import 'package:reown_core/verify/i_verify.dart' as _i16; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeCryptoKeyPair_0 extends _i1.SmartFake implements _i2.CryptoKeyPair { + _FakeCryptoKeyPair_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEncodingParams_1 extends _i1.SmartFake + implements _i2.EncodingParams { + _FakeEncodingParams_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEncodingValidation_2 extends _i1.SmartFake + implements _i2.EncodingValidation { + _FakeEncodingValidation_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIReownCore_3 extends _i1.SmartFake implements _i3.IReownCore { + _FakeIReownCore_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIGenericStore_4 extends _i1.SmartFake + implements _i4.IGenericStore { + _FakeIGenericStore_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeICryptoUtils_5 extends _i1.SmartFake implements _i5.ICryptoUtils { + _FakeICryptoUtils_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIRelayAuth_6 extends _i1.SmartFake implements _i6.IRelayAuth { + _FakeIRelayAuth_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIStore_7 extends _i1.SmartFake implements _i7.IStore { + _FakeIStore_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEvent_8 extends _i1.SmartFake + implements _i8.Event { + _FakeEvent_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponse_9 extends _i1.SmartFake implements _i9.Response { + _FakeResponse_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeICrypto_10 extends _i1.SmartFake implements _i10.ICrypto { + _FakeICrypto_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIRelayClient_11 extends _i1.SmartFake implements _i11.IRelayClient { + _FakeIRelayClient_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIExpirer_12 extends _i1.SmartFake implements _i12.IExpirer { + _FakeIExpirer_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIPairing_13 extends _i1.SmartFake implements _i13.IPairing { + _FakeIPairing_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIEcho_14 extends _i1.SmartFake implements _i14.IEcho { + _FakeIEcho_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIHeartBeat_15 extends _i1.SmartFake implements _i15.IHeartBeat { + _FakeIHeartBeat_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIVerify_16 extends _i1.SmartFake implements _i16.IVerify { + _FakeIVerify_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIConnectivity_17 extends _i1.SmartFake + implements _i17.IConnectivity { + _FakeIConnectivity_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeILinkModeStore_18 extends _i1.SmartFake + implements _i18.ILinkModeStore { + _FakeILinkModeStore_18( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLogger_19 extends _i1.SmartFake implements _i19.Logger { + _FakeLogger_19( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [CryptoUtils]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCryptoUtils extends _i1.Mock implements _i20.CryptoUtils { + MockCryptoUtils() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.CryptoKeyPair generateKeyPair() => (super.noSuchMethod( + Invocation.method( + #generateKeyPair, + [], + ), + returnValue: _FakeCryptoKeyPair_0( + this, + Invocation.method( + #generateKeyPair, + [], + ), + ), + ) as _i2.CryptoKeyPair); + + @override + _i21.Uint8List randomBytes(int? length) => (super.noSuchMethod( + Invocation.method( + #randomBytes, + [length], + ), + returnValue: _i21.Uint8List(0), + ) as _i21.Uint8List); + + @override + String generateRandomBytes32() => (super.noSuchMethod( + Invocation.method( + #generateRandomBytes32, + [], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #generateRandomBytes32, + [], + ), + ), + ) as String); + + @override + _i23.Future deriveSymKey( + String? privKeyA, + String? pubKeyB, + ) => + (super.noSuchMethod( + Invocation.method( + #deriveSymKey, + [ + privKeyA, + pubKeyB, + ], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #deriveSymKey, + [ + privKeyA, + pubKeyB, + ], + ), + )), + ) as _i23.Future); + + @override + String hashKey(String? key) => (super.noSuchMethod( + Invocation.method( + #hashKey, + [key], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashKey, + [key], + ), + ), + ) as String); + + @override + String hashMessage(String? message) => (super.noSuchMethod( + Invocation.method( + #hashMessage, + [message], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashMessage, + [message], + ), + ), + ) as String); + + @override + _i23.Future encrypt( + String? message, + String? symKey, { + int? type, + String? iv, + String? senderPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #encrypt, + [ + message, + symKey, + ], + { + #type: type, + #iv: iv, + #senderPublicKey: senderPublicKey, + }, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #encrypt, + [ + message, + symKey, + ], + { + #type: type, + #iv: iv, + #senderPublicKey: senderPublicKey, + }, + ), + )), + ) as _i23.Future); + + @override + _i23.Future decrypt( + String? symKey, + String? encoded, + ) => + (super.noSuchMethod( + Invocation.method( + #decrypt, + [ + symKey, + encoded, + ], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #decrypt, + [ + symKey, + encoded, + ], + ), + )), + ) as _i23.Future); + + @override + String serialize( + int? type, + _i21.Uint8List? sealed, + _i21.Uint8List? iv, { + _i21.Uint8List? senderPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #serialize, + [ + type, + sealed, + iv, + ], + {#senderPublicKey: senderPublicKey}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #serialize, + [ + type, + sealed, + iv, + ], + {#senderPublicKey: senderPublicKey}, + ), + ), + ) as String); + + @override + _i2.EncodingParams deserialize(String? encoded) => (super.noSuchMethod( + Invocation.method( + #deserialize, + [encoded], + ), + returnValue: _FakeEncodingParams_1( + this, + Invocation.method( + #deserialize, + [encoded], + ), + ), + ) as _i2.EncodingParams); + + @override + _i2.EncodingValidation validateDecoding( + String? encoded, { + String? receiverPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #validateDecoding, + [encoded], + {#receiverPublicKey: receiverPublicKey}, + ), + returnValue: _FakeEncodingValidation_2( + this, + Invocation.method( + #validateDecoding, + [encoded], + {#receiverPublicKey: receiverPublicKey}, + ), + ), + ) as _i2.EncodingValidation); + + @override + _i2.EncodingValidation validateEncoding({ + int? type, + String? senderPublicKey, + String? receiverPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #validateEncoding, + [], + { + #type: type, + #senderPublicKey: senderPublicKey, + #receiverPublicKey: receiverPublicKey, + }, + ), + returnValue: _FakeEncodingValidation_2( + this, + Invocation.method( + #validateEncoding, + [], + { + #type: type, + #senderPublicKey: senderPublicKey, + #receiverPublicKey: receiverPublicKey, + }, + ), + ), + ) as _i2.EncodingValidation); + + @override + bool isTypeOneEnvelope(_i2.EncodingValidation? result) => (super.noSuchMethod( + Invocation.method( + #isTypeOneEnvelope, + [result], + ), + returnValue: false, + ) as bool); + + @override + bool isTypeTwoEnvelope(_i2.EncodingValidation? result) => (super.noSuchMethod( + Invocation.method( + #isTypeTwoEnvelope, + [result], + ), + returnValue: false, + ) as bool); + + @override + _i21.Uint8List encodeTypeByte(int? type) => (super.noSuchMethod( + Invocation.method( + #encodeTypeByte, + [type], + ), + returnValue: _i21.Uint8List(0), + ) as _i21.Uint8List); + + @override + int decodeTypeByte(_i21.Uint8List? byte) => (super.noSuchMethod( + Invocation.method( + #decodeTypeByte, + [byte], + ), + returnValue: 0, + ) as int); + + @override + String encodeTypeTwoEnvelope({required String? message}) => + (super.noSuchMethod( + Invocation.method( + #encodeTypeTwoEnvelope, + [], + {#message: message}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #encodeTypeTwoEnvelope, + [], + {#message: message}, + ), + ), + ) as String); + + @override + String decodeTypeTwoEnvelope({required String? message}) => + (super.noSuchMethod( + Invocation.method( + #decodeTypeTwoEnvelope, + [], + {#message: message}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #decodeTypeTwoEnvelope, + [], + {#message: message}, + ), + ), + ) as String); +} + +/// A class which mocks [Crypto]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCrypto extends _i1.Mock implements _i24.Crypto { + MockCrypto() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.IReownCore get core => (super.noSuchMethod( + Invocation.getter(#core), + returnValue: _FakeIReownCore_3( + this, + Invocation.getter(#core), + ), + ) as _i3.IReownCore); + + @override + _i4.IGenericStore get keyChain => (super.noSuchMethod( + Invocation.getter(#keyChain), + returnValue: _FakeIGenericStore_4( + this, + Invocation.getter(#keyChain), + ), + ) as _i4.IGenericStore); + + @override + set keyChain(_i4.IGenericStore? _keyChain) => super.noSuchMethod( + Invocation.setter( + #keyChain, + _keyChain, + ), + returnValueForMissingStub: null, + ); + + @override + _i5.ICryptoUtils get utils => (super.noSuchMethod( + Invocation.getter(#utils), + returnValue: _FakeICryptoUtils_5( + this, + Invocation.getter(#utils), + ), + ) as _i5.ICryptoUtils); + + @override + set utils(_i5.ICryptoUtils? _utils) => super.noSuchMethod( + Invocation.setter( + #utils, + _utils, + ), + returnValueForMissingStub: null, + ); + + @override + _i6.IRelayAuth get relayAuth => (super.noSuchMethod( + Invocation.getter(#relayAuth), + returnValue: _FakeIRelayAuth_6( + this, + Invocation.getter(#relayAuth), + ), + ) as _i6.IRelayAuth); + + @override + set relayAuth(_i6.IRelayAuth? _relayAuth) => super.noSuchMethod( + Invocation.setter( + #relayAuth, + _relayAuth, + ), + returnValueForMissingStub: null, + ); + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + _i23.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool hasKeys(String? tag) => (super.noSuchMethod( + Invocation.method( + #hasKeys, + [tag], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future getClientId() => (super.noSuchMethod( + Invocation.method( + #getClientId, + [], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #getClientId, + [], + ), + )), + ) as _i23.Future); + + @override + _i23.Future generateKeyPair() => (super.noSuchMethod( + Invocation.method( + #generateKeyPair, + [], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #generateKeyPair, + [], + ), + )), + ) as _i23.Future); + + @override + _i23.Future generateSharedKey( + String? selfPublicKey, + String? peerPublicKey, { + String? overrideTopic, + }) => + (super.noSuchMethod( + Invocation.method( + #generateSharedKey, + [ + selfPublicKey, + peerPublicKey, + ], + {#overrideTopic: overrideTopic}, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #generateSharedKey, + [ + selfPublicKey, + peerPublicKey, + ], + {#overrideTopic: overrideTopic}, + ), + )), + ) as _i23.Future); + + @override + _i23.Future setSymKey( + String? symKey, { + String? overrideTopic, + }) => + (super.noSuchMethod( + Invocation.method( + #setSymKey, + [symKey], + {#overrideTopic: overrideTopic}, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #setSymKey, + [symKey], + {#overrideTopic: overrideTopic}, + ), + )), + ) as _i23.Future); + + @override + _i23.Future deleteKeyPair(String? publicKey) => (super.noSuchMethod( + Invocation.method( + #deleteKeyPair, + [publicKey], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future deleteSymKey(String? topic) => (super.noSuchMethod( + Invocation.method( + #deleteSymKey, + [topic], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future encode( + String? topic, + Map? payload, { + _i2.EncodeOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #encode, + [ + topic, + payload, + ], + {#options: options}, + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future decode( + String? topic, + String? encoded, { + _i2.DecodeOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #decode, + [ + topic, + encoded, + ], + {#options: options}, + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future signJWT(String? aud) => (super.noSuchMethod( + Invocation.method( + #signJWT, + [aud], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #signJWT, + [aud], + ), + )), + ) as _i23.Future); + + @override + int getPayloadType(String? encoded) => (super.noSuchMethod( + Invocation.method( + #getPayloadType, + [encoded], + ), + returnValue: 0, + ) as int); + + @override + String? getPayloadSenderPublicKey(String? encoded) => + (super.noSuchMethod(Invocation.method( + #getPayloadSenderPublicKey, + [encoded], + )) as String?); + + @override + _i5.ICryptoUtils getUtils() => (super.noSuchMethod( + Invocation.method( + #getUtils, + [], + ), + returnValue: _FakeICryptoUtils_5( + this, + Invocation.method( + #getUtils, + [], + ), + ), + ) as _i5.ICryptoUtils); +} + +/// A class which mocks [MessageTracker]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMessageTracker extends _i1.Mock implements _i25.MessageTracker { + MockMessageTracker() { + _i1.throwOnMissingStub(this); + } + + @override + String get context => (super.noSuchMethod( + Invocation.getter(#context), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#context), + ), + ) as String); + + @override + String get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#version), + ), + ) as String); + + @override + _i7.IStore get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeIStore_7( + this, + Invocation.getter(#storage), + ), + ) as _i7.IStore); + + @override + _i8.Event<_i26.StoreCreateEvent>> get onCreate => + (super.noSuchMethod( + Invocation.getter(#onCreate), + returnValue: _FakeEvent_8<_i26.StoreCreateEvent>>( + this, + Invocation.getter(#onCreate), + ), + ) as _i8.Event<_i26.StoreCreateEvent>>); + + @override + _i8.Event<_i26.StoreUpdateEvent>> get onUpdate => + (super.noSuchMethod( + Invocation.getter(#onUpdate), + returnValue: _FakeEvent_8<_i26.StoreUpdateEvent>>( + this, + Invocation.getter(#onUpdate), + ), + ) as _i8.Event<_i26.StoreUpdateEvent>>); + + @override + _i8.Event<_i26.StoreDeleteEvent>> get onDelete => + (super.noSuchMethod( + Invocation.getter(#onDelete), + returnValue: _FakeEvent_8<_i26.StoreDeleteEvent>>( + this, + Invocation.getter(#onDelete), + ), + ) as _i8.Event<_i26.StoreDeleteEvent>>); + + @override + _i8.Event<_i26.StoreSyncEvent> get onSync => (super.noSuchMethod( + Invocation.getter(#onSync), + returnValue: _FakeEvent_8<_i26.StoreSyncEvent>( + this, + Invocation.getter(#onSync), + ), + ) as _i8.Event<_i26.StoreSyncEvent>); + + @override + Map> get data => (super.noSuchMethod( + Invocation.getter(#data), + returnValue: >{}, + ) as Map>); + + @override + set data(Map>? _data) => super.noSuchMethod( + Invocation.setter( + #data, + _data, + ), + returnValueForMissingStub: null, + ); + + @override + Map Function(dynamic) get fromJson => (super.noSuchMethod( + Invocation.getter(#fromJson), + returnValue: (dynamic __p0) => {}, + ) as Map Function(dynamic)); + + @override + String get storageKey => (super.noSuchMethod( + Invocation.getter(#storageKey), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#storageKey), + ), + ) as String); + + @override + String hashMessage(String? message) => (super.noSuchMethod( + Invocation.method( + #hashMessage, + [message], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashMessage, + [message], + ), + ), + ) as String); + + @override + _i23.Future recordMessageEvent( + String? topic, + String? message, + ) => + (super.noSuchMethod( + Invocation.method( + #recordMessageEvent, + [ + topic, + message, + ], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool messageIsRecorded( + String? topic, + String? message, + ) => + (super.noSuchMethod( + Invocation.method( + #messageIsRecorded, + [ + topic, + message, + ], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool has(String? key) => (super.noSuchMethod( + Invocation.method( + #has, + [key], + ), + returnValue: false, + ) as bool); + + @override + Map? get(String? key) => + (super.noSuchMethod(Invocation.method( + #get, + [key], + )) as Map?); + + @override + List> getAll() => (super.noSuchMethod( + Invocation.method( + #getAll, + [], + ), + returnValue: >[], + ) as List>); + + @override + _i23.Future set( + String? key, + Map? value, + ) => + (super.noSuchMethod( + Invocation.method( + #set, + [ + key, + value, + ], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future delete(String? key) => (super.noSuchMethod( + Invocation.method( + #delete, + [key], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future persist() => (super.noSuchMethod( + Invocation.method( + #persist, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future restore() => (super.noSuchMethod( + Invocation.method( + #restore, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + void checkInitialized() => super.noSuchMethod( + Invocation.method( + #checkInitialized, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [HttpWrapper]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHttpWrapper extends _i1.Mock implements _i27.HttpWrapper { + MockHttpWrapper() { + _i1.throwOnMissingStub(this); + } + + @override + _i23.Future<_i9.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i23.Future<_i9.Response>); + + @override + _i23.Future<_i9.Response> delete( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + {#headers: headers}, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #delete, + [url], + {#headers: headers}, + ), + )), + ) as _i23.Future<_i9.Response>); + + @override + _i23.Future<_i9.Response> post( + Uri? url, { + Map? headers, + Object? body, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + }, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + }, + ), + )), + ) as _i23.Future<_i9.Response>); +} + +/// A class which mocks [ReownCore]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReownCore extends _i1.Mock implements _i28.ReownCore { + MockReownCore() { + _i1.throwOnMissingStub(this); + } + + @override + String get projectId => (super.noSuchMethod( + Invocation.getter(#projectId), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#projectId), + ), + ) as String); + + @override + String get relayUrl => (super.noSuchMethod( + Invocation.getter(#relayUrl), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#relayUrl), + ), + ) as String); + + @override + set relayUrl(String? _relayUrl) => super.noSuchMethod( + Invocation.setter( + #relayUrl, + _relayUrl, + ), + returnValueForMissingStub: null, + ); + + @override + String get pushUrl => (super.noSuchMethod( + Invocation.getter(#pushUrl), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#pushUrl), + ), + ) as String); + + @override + set pushUrl(String? _pushUrl) => super.noSuchMethod( + Invocation.setter( + #pushUrl, + _pushUrl, + ), + returnValueForMissingStub: null, + ); + + @override + _i10.ICrypto get crypto => (super.noSuchMethod( + Invocation.getter(#crypto), + returnValue: _FakeICrypto_10( + this, + Invocation.getter(#crypto), + ), + ) as _i10.ICrypto); + + @override + set crypto(_i10.ICrypto? _crypto) => super.noSuchMethod( + Invocation.setter( + #crypto, + _crypto, + ), + returnValueForMissingStub: null, + ); + + @override + _i11.IRelayClient get relayClient => (super.noSuchMethod( + Invocation.getter(#relayClient), + returnValue: _FakeIRelayClient_11( + this, + Invocation.getter(#relayClient), + ), + ) as _i11.IRelayClient); + + @override + set relayClient(_i11.IRelayClient? _relayClient) => super.noSuchMethod( + Invocation.setter( + #relayClient, + _relayClient, + ), + returnValueForMissingStub: null, + ); + + @override + _i12.IExpirer get expirer => (super.noSuchMethod( + Invocation.getter(#expirer), + returnValue: _FakeIExpirer_12( + this, + Invocation.getter(#expirer), + ), + ) as _i12.IExpirer); + + @override + set expirer(_i12.IExpirer? _expirer) => super.noSuchMethod( + Invocation.setter( + #expirer, + _expirer, + ), + returnValueForMissingStub: null, + ); + + @override + _i13.IPairing get pairing => (super.noSuchMethod( + Invocation.getter(#pairing), + returnValue: _FakeIPairing_13( + this, + Invocation.getter(#pairing), + ), + ) as _i13.IPairing); + + @override + set pairing(_i13.IPairing? _pairing) => super.noSuchMethod( + Invocation.setter( + #pairing, + _pairing, + ), + returnValueForMissingStub: null, + ); + + @override + _i14.IEcho get echo => (super.noSuchMethod( + Invocation.getter(#echo), + returnValue: _FakeIEcho_14( + this, + Invocation.getter(#echo), + ), + ) as _i14.IEcho); + + @override + set echo(_i14.IEcho? _echo) => super.noSuchMethod( + Invocation.setter( + #echo, + _echo, + ), + returnValueForMissingStub: null, + ); + + @override + _i15.IHeartBeat get heartbeat => (super.noSuchMethod( + Invocation.getter(#heartbeat), + returnValue: _FakeIHeartBeat_15( + this, + Invocation.getter(#heartbeat), + ), + ) as _i15.IHeartBeat); + + @override + set heartbeat(_i15.IHeartBeat? _heartbeat) => super.noSuchMethod( + Invocation.setter( + #heartbeat, + _heartbeat, + ), + returnValueForMissingStub: null, + ); + + @override + _i16.IVerify get verify => (super.noSuchMethod( + Invocation.getter(#verify), + returnValue: _FakeIVerify_16( + this, + Invocation.getter(#verify), + ), + ) as _i16.IVerify); + + @override + set verify(_i16.IVerify? _verify) => super.noSuchMethod( + Invocation.setter( + #verify, + _verify, + ), + returnValueForMissingStub: null, + ); + + @override + _i17.IConnectivity get connectivity => (super.noSuchMethod( + Invocation.getter(#connectivity), + returnValue: _FakeIConnectivity_17( + this, + Invocation.getter(#connectivity), + ), + ) as _i17.IConnectivity); + + @override + set connectivity(_i17.IConnectivity? _connectivity) => super.noSuchMethod( + Invocation.setter( + #connectivity, + _connectivity, + ), + returnValueForMissingStub: null, + ); + + @override + _i18.ILinkModeStore get linkModeStore => (super.noSuchMethod( + Invocation.getter(#linkModeStore), + returnValue: _FakeILinkModeStore_18( + this, + Invocation.getter(#linkModeStore), + ), + ) as _i18.ILinkModeStore); + + @override + set linkModeStore(_i18.ILinkModeStore? _linkModeStore) => super.noSuchMethod( + Invocation.setter( + #linkModeStore, + _linkModeStore, + ), + returnValueForMissingStub: null, + ); + + @override + _i7.IStore> get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeIStore_7>( + this, + Invocation.getter(#storage), + ), + ) as _i7.IStore>); + + @override + set storage(_i7.IStore>? _storage) => super.noSuchMethod( + Invocation.setter( + #storage, + _storage, + ), + returnValueForMissingStub: null, + ); + + @override + String get protocol => (super.noSuchMethod( + Invocation.getter(#protocol), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#protocol), + ), + ) as String); + + @override + String get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#version), + ), + ) as String); + + @override + _i19.Logger get logger => (super.noSuchMethod( + Invocation.getter(#logger), + returnValue: _FakeLogger_19( + this, + Invocation.getter(#logger), + ), + ) as _i19.Logger); + + @override + void addLogListener(_i19.LogCallback? callback) => super.noSuchMethod( + Invocation.method( + #addLogListener, + [callback], + ), + returnValueForMissingStub: null, + ); + + @override + bool removeLogListener(_i19.LogCallback? callback) => (super.noSuchMethod( + Invocation.method( + #removeLogListener, + [callback], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future start() => (super.noSuchMethod( + Invocation.method( + #start, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future addLinkModeSupportedApp(String? universalLink) => + (super.noSuchMethod( + Invocation.method( + #addLinkModeSupportedApp, + [universalLink], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + List getLinkModeSupportedApps() => (super.noSuchMethod( + Invocation.method( + #getLinkModeSupportedApps, + [], + ), + returnValue: [], + ) as List); + + @override + void confirmOnlineStateOrThrow() => super.noSuchMethod( + Invocation.method( + #confirmOnlineStateOrThrow, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [WebSocketHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSocketHandler extends _i1.Mock implements _i29.WebSocketHandler { + MockWebSocketHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i23.Future get ready => (super.noSuchMethod( + Invocation.getter(#ready), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future setup({required String? url}) => (super.noSuchMethod( + Invocation.method( + #setup, + [], + {#url: url}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future connect() => (super.noSuchMethod( + Invocation.method( + #connect, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future close() => (super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); +} diff --git a/packages/reown_core/test/shared/shared_test_values.dart b/packages/reown_core/test/shared/shared_test_values.dart new file mode 100644 index 0000000..dcbf96f --- /dev/null +++ b/packages/reown_core/test/shared/shared_test_values.dart @@ -0,0 +1,235 @@ +import 'package:reown_core/reown_core.dart'; + +const TEST_RELAY_URL = String.fromEnvironment( + 'RELAY_ENDPOINT', + defaultValue: 'wss://relay.walletconnect.com', +); +const TEST_PROJECT_ID = String.fromEnvironment( + 'PROJECT_ID', + defaultValue: 'cad4956f31a5e40a00b62865b030c6f8', +); + +const PROPOSER = PairingMetadata( + name: 'App A (Proposer, dapp)', + description: 'Description of Proposer App run by client A', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], +); +const RESPONDER = PairingMetadata( + name: 'App B (Responder, Wallet)', + description: 'Description of Proposer App run by client B', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], +); + +const TEST_PAIRING_TOPIC = ''; +const TEST_SESSION_TOPIC = ''; +const TEST_KEY_PAIRS = { + 'A': CryptoKeyPair( + '1fb63fca5c6ac731246f2f069d3bc2454345d5208254aa8ea7bffc6d110c8862', + 'ff7a7d5767c362b0a17ad92299ebdb7831dcbd9a56959c01368c7404543b3342', + ), + 'B': CryptoKeyPair( + '36bf507903537de91f5e573666eaa69b1fa313974f23b2b59645f20fea505854', + '590c2c627be7af08597091ff80dd41f7fa28acd10ef7191d7e830e116d3a186a', + ), +}; + +const TEST_SHARED_KEY = + '9c87e48e69b33a613907515bcd5b1b4cc10bbaf15167b19804b00f0a9217e607'; +const TEST_HASHED_KEY = + 'a492906ccc809a411bb53a84572b57329375378c6ad7566f3e1c688200123e77'; +const TEST_SYM_KEY = + '0653ca620c7b4990392e1c53c4a51c14a2840cd20f0f1524cf435b17b6fe988c'; + +const TEST_URI = + 'wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&relay-protocol=irn'; +const TEST_URI_V1 = + 'wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@1?key=abc&bridge=xyz'; + +const TEST_ETHEREUM_CHAIN = 'eip155:1'; + +final Set availableAccounts = { + 'namespace1:chain1:address1', + 'namespace1:chain1:address2', + 'namespace2:chain1:address3', + 'namespace2:chain1:address4', + 'namespace2:chain2:address5', + 'namespace4:chain1:address6', +}; + +final Set availableMethods = { + 'namespace1:chain1:method1', + 'namespace1:chain1:method2', + 'namespace2:chain1:method3', + 'namespace2:chain1:method4', + 'namespace2:chain2:method3', + 'namespace4:chain1:method5', +}; + +final Set availableEvents = { + 'namespace1:chain1:event1', + 'namespace1:chain1:event2', + 'namespace2:chain1:event3', + 'namespace2:chain1:event4', + 'namespace2:chain2:event3', + 'namespace4:chain1:event5', +}; + +// final Map requiredNamespacesInAvailable = { +// 'namespace1:chain1': const RequiredNamespace( +// methods: ['method1'], +// events: ['event1'], +// ), +// 'namespace2': const RequiredNamespace( +// chains: ['namespace2:chain1', 'namespace2:chain2'], +// methods: ['method3'], +// events: ['event3'], +// ), +// }; + +// final Map requiredNamespacesInAvailable2 = { +// 'namespace1': const RequiredNamespace( +// methods: ['method1'], +// events: ['event1'], +// ), +// 'namespace2': const RequiredNamespace( +// chains: ['namespace2:chain1', 'namespace2:chain2'], +// methods: ['method3'], +// events: ['event3'], +// ), +// }; + +// final Map requiredNamespacesMatchingAvailable1 = { +// 'namespace1:chain1': const RequiredNamespace( +// methods: ['method1', 'method2'], +// events: ['event1', 'event2'], +// ), +// 'namespace2': const RequiredNamespace( +// chains: ['namespace2:chain1'], +// methods: ['method3', 'method4'], +// events: ['event3', 'event4'], +// ), +// }; + +// final Map requiredNamespacesNonconformingAccounts1 = +// { +// 'namespace3': const RequiredNamespace( +// chains: ['namespace3:chain1'], +// methods: [], +// events: [], +// ), +// }; + +// final Map requiredNamespacesNonconformingMethods1 = { +// 'namespace1:chain1': const RequiredNamespace( +// methods: ['method1', 'method2', 'method3'], +// events: ['event1', 'event2'], +// ), +// 'namespace2': const RequiredNamespace( +// chains: ['namespace2:chain1', 'namespace2:chain2'], +// methods: ['method3'], +// events: ['event3'], +// ), +// }; + +// final Map requiredNamespacesNonconformingMethods2 = { +// 'namespace1:chain1': const RequiredNamespace( +// methods: ['method1', 'method2', 'method3'], +// events: ['event1', 'event2'], +// ), +// 'namespace2': const RequiredNamespace( +// chains: ['namespace2:chain1', 'namespace2:chain2'], +// methods: ['method3', 'method4'], +// events: ['event3'], +// ), +// }; + +// final Map requiredNamespacesNonconformingEvents1 = { +// 'namespace1:chain1': const RequiredNamespace( +// methods: ['method1', 'method2'], +// events: ['event1', 'event2', 'event3'], +// ), +// 'namespace2': const RequiredNamespace( +// chains: ['namespace2:chain1', 'namespace2:chain2'], +// methods: ['method3'], +// events: ['event3'], +// ), +// }; + +// final Map requiredNamespacesNonconformingEvents2 = { +// 'namespace1:chain1': const RequiredNamespace( +// methods: ['method1', 'method2'], +// events: ['event1', 'event2', 'event3'], +// ), +// 'namespace2': const RequiredNamespace( +// chains: ['namespace2:chain1', 'namespace2:chain2'], +// methods: ['method3'], +// events: ['event3', 'event4'], +// ), +// }; + +// Map optionalNamespaces = { +// 'namespace4:chain1': const RequiredNamespace( +// methods: ['method5'], +// events: ['event5', 'event2'], +// ), +// }; + +// const sepolia = 'eip155:11155111'; + +// final Set availableAccounts3 = { +// '$sepolia:0x99999999999999999999999999', +// }; + +// final Set availableMethods3 = { +// '$sepolia:eth_sendTransaction', +// '$sepolia:personal_sign', +// '$sepolia:eth_signTypedData', +// '$sepolia:eth_signTypedData_v4', +// '$sepolia:eth_sign', +// }; + +// final Set availableEvents3 = { +// '$sepolia:chainChanged', +// '$sepolia:accountsChanged', +// }; + +// final Map requiredNamespacesInAvailable3 = { +// 'eip155': const RequiredNamespace( +// chains: [sepolia], +// methods: ['eth_sendTransaction', 'personal_sign'], +// events: ['chainChanged', 'accountsChanged'], +// ), +// }; + +// final Map optionalNamespacesInAvailable3 = { +// 'eip155': const RequiredNamespace(chains: [ +// 'eip155:1', +// 'eip155:5', +// sepolia, +// 'eip155:137', +// 'eip155:80001', +// 'eip155:42220', +// 'eip155:44787', +// 'eip155:56', +// 'eip155:43114', +// 'eip155:42161', +// 'eip155:421613', +// 'eip155:10', +// 'eip155:420', +// 'eip155:8453' +// ], methods: [ +// 'eth_sendTransaction', +// 'personal_sign', +// 'eth_signTypedData', +// 'eth_signTypedData_v4', +// 'eth_sign' +// ], events: [ +// 'chainChanged', +// 'accountsChanged', +// 'message', +// 'disconnect', +// 'connect' +// ]), +// }; diff --git a/packages/reown_core/test/store_test.dart b/packages/reown_core/test/store_test.dart new file mode 100644 index 0000000..f6cbf17 --- /dev/null +++ b/packages/reown_core/test/store_test.dart @@ -0,0 +1,302 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/pairing/i_json_rpc_history.dart'; +import 'package:reown_core/pairing/i_pairing_store.dart'; +import 'package:reown_core/pairing/json_rpc_history.dart'; +import 'package:reown_core/pairing/pairing_store.dart'; +import 'package:reown_core/pairing/utils/pairing_models.dart'; +import 'package:reown_core/relay_client/i_message_tracker.dart'; +import 'package:reown_core/relay_client/message_tracker.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_core/store/i_store.dart'; +import 'package:reown_core/store/shared_prefs_store.dart'; +import 'package:reown_core/utils/constants.dart'; +import 'package:reown_core/utils/utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('Store', () { + late IStore> store; + + setUp(() async { + store = SharedPrefsStores( + memoryStore: true, + ); + await store.init(); + }); + + group('Generic', () { + late IGenericStore genericStore; + + setUp(() async { + genericStore = GenericStore( + storage: store, + context: 'keychain', + version: 'swag', + fromJson: (value) => value as String, + ); + await genericStore.init(); + }); + + test('has correct outcome', () async { + Completer createComplete = Completer(); + Completer updateComplete = Completer(); + Completer deleteComplete = Completer(); + Completer syncComplete = Completer(); + genericStore.onCreate.subscribe((args) { + createComplete.complete(); + }); + genericStore.onUpdate.subscribe((args) { + updateComplete.complete(); + }); + genericStore.onDelete.subscribe((args) { + deleteComplete.complete(); + }); + genericStore.onSync.subscribe((args) { + syncComplete.complete(); + }); + + expect(genericStore.get('key'), null); + expect(genericStore.has('key'), false); + await genericStore.set('key', 'value'); + await createComplete.future; + await syncComplete.future; + expect(genericStore.get('key'), 'value'); + expect(genericStore.has('key'), true); + expect(genericStore.getAll(), ['value']); + + genericStore.onCreate.unsubscribeAll(); + syncComplete = Completer(); + + await genericStore.set('key', 'value2'); + await updateComplete.future; + await syncComplete.future; + expect(genericStore.get('key'), 'value2'); + expect(genericStore.getAll(), ['value2']); + + genericStore.onUpdate.unsubscribeAll(); + syncComplete = Completer(); + + await genericStore.delete('key'); + await deleteComplete.future; + await syncComplete.future; + expect(genericStore.get('key'), null); + expect(genericStore.has('key'), false); + + genericStore.onDelete.unsubscribeAll(); + genericStore.onSync.unsubscribeAll(); + }); + + test('restores properly', () async { + store = SharedPrefsStores( + defaultValue: { + '${ReownConstants.CORE_STORAGE_PREFIX}swag//keychain': { + 'key': 'value', + }, + '${ReownConstants.CORE_STORAGE_PREFIX}swag//invalid': { + 'key': { + 'invalid': 'value', + }, + }, + '${ReownConstants.CORE_STORAGE_PREFIX}keychain': { + 'version': 'swag', + }, + '${ReownConstants.CORE_STORAGE_PREFIX}invalid': { + 'version': 'swag', + }, + }, + memoryStore: true, + ); + await store.init(); + + // Case 1: Storage doesn't have the context + genericStore = GenericStore( + storage: store, + context: 'records', + version: 'swag', + fromJson: (value) => value as String, + ); + await genericStore.init(); + + expect(store.get('records'), {'version': 'swag'}); + expect(store.get(genericStore.storageKey), {}); + + // Case 2: Storage has the context, version, and key + genericStore = GenericStore( + storage: store, + context: 'keychain', + version: 'swag', + fromJson: (value) => value as String, + ); + await genericStore.init(); + + expect(store.get('keychain'), {'version': 'swag'}); + expect(genericStore.get('key'), 'value'); + + // Case 3: Storage has the context, but versions don't match + genericStore = GenericStore( + storage: store, + context: 'keychain', + version: 'swagV2', + fromJson: (value) => value as String, + ); + await genericStore.init(); + + expect(store.get('keychain'), {'version': 'swagV2'}); + expect(store.get('swag//keychain') == null, true); + expect(store.get(genericStore.storageKey) == null, false); + expect(genericStore.get('key') == null, true); + + // Case 4: Storage data is invalid + // print('case 4'); + genericStore = GenericStore( + storage: store, + context: 'invalid', + version: 'swag', + fromJson: (value) => value as String, + ); + await genericStore.init(); + + expect(store.get('invalid'), {'version': 'swag'}); + expect(genericStore.get('key') == null, true); + }); + }); + + group('special stores', () { + test('message tracker', () async { + IMessageTracker messageTracker = MessageTracker( + storage: store, + context: 'messageTracker', + version: 'swag', + fromJson: (dynamic value) { + return ReownCoreUtils.convertMapTo(value); + }, + ); + + Completer createComplete = Completer(); + messageTracker.onCreate.subscribe((args) { + createComplete.complete(); + }); + + await messageTracker.init(); + + expect(messageTracker.messageIsRecorded('test', 'message'), false); + + await messageTracker.recordMessageEvent('test', 'message'); + await createComplete.future; + + expect( + messageTracker.get('test'), + { + 'ab530a13e45914982b79f9b7e3fba994cfd1f3fb22f71cea1afbf02b460c6d1d': + 'message', + }, + ); + + expect(messageTracker.messageIsRecorded('test', 'message'), true); + }); + + test('json rpc store', () async { + IJsonRpcHistory specialStore = JsonRpcHistory( + storage: store, + context: 'specialStore', + version: 'swag', + fromJson: (dynamic value) { + return JsonRpcRecord.fromJson(value); + }, + ); + await specialStore.init(); + + await specialStore.set( + '1', + const JsonRpcRecord( + id: 1, + topic: 'test', + method: 'method', + params: [], + expiry: 0, + ), + ); + + Completer updateComplete = Completer(); + Completer syncComplete = Completer(); + specialStore.onUpdate.subscribe((args) { + updateComplete.complete(); + }); + specialStore.onSync.subscribe((args) { + syncComplete.complete(); + }); + + await specialStore.resolve( + { + 'id': 1, + 'result': 'result', + }, + ); + + await updateComplete.future; + await syncComplete.future; + + expect( + specialStore.get('1')!.response, + 'result', + ); + }); + + test('pairing store', () async { + IPairingStore specialStore = PairingStore( + storage: store, + context: 'specialStore', + version: 'swag', + fromJson: (dynamic value) { + return PairingInfo.fromJson(value); + }, + ); + await specialStore.init(); + + await specialStore.set( + '1', + PairingInfo( + topic: 'expired', + expiry: -1, + relay: Relay( + 'irn', + ), + active: true, + ), + ); + + Completer updateComplete = Completer(); + Completer syncComplete = Completer(); + specialStore.onUpdate.subscribe((args) { + updateComplete.complete(); + }); + specialStore.onSync.subscribe((args) { + syncComplete.complete(); + }); + + expect( + specialStore.get('1')!.expiry, + -1, + ); + + await specialStore.update( + '1', + expiry: 2, + ); + + await updateComplete.future; + await syncComplete.future; + + expect( + specialStore.get('1')!.expiry, + 2, + ); + }); + }); + }); +} diff --git a/packages/reown_core/test/uri_parse_test.dart b/packages/reown_core/test/uri_parse_test.dart new file mode 100644 index 0000000..a28b310 --- /dev/null +++ b/packages/reown_core/test/uri_parse_test.dart @@ -0,0 +1,37 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; + +import 'shared/shared_test_values.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('URIParse', () { + test('parsed v2 url properly', () { + URIParseResult parsed = ReownCoreUtils.parseUri(Uri.parse(TEST_URI)); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v2); + expect( + parsed.topic, + '7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9', + ); + expect( + parsed.v2Data!.symKey, + '587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303', + ); + expect(parsed.v2Data!.relay.protocol, 'irn'); + }); + + test('parsed v1 url properly', () { + URIParseResult parsed = ReownCoreUtils.parseUri( + Uri.parse(TEST_URI_V1), + ); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v1); + expect(parsed.topic, + '7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9'); + expect(parsed.v1Data!.key, 'abc'); + expect(parsed.v1Data!.bridge, 'xyz'); + }); + }); +} diff --git a/packages/reown_sign/.gitignore b/packages/reown_sign/.gitignore new file mode 100644 index 0000000..474a12e --- /dev/null +++ b/packages/reown_sign/.gitignore @@ -0,0 +1,58 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/packages/reown_sign/.metadata b/packages/reown_sign/.metadata new file mode 100644 index 0000000..bc3f0ae --- /dev/null +++ b/packages/reown_sign/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "300451adae589accbece3490f4396f10bdf15e6e" + channel: "stable" + +project_type: package diff --git a/packages/reown_sign/CHANGELOG.md b/packages/reown_sign/CHANGELOG.md new file mode 100644 index 0000000..69b4a02 --- /dev/null +++ b/packages/reown_sign/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +Initial release. diff --git a/packages/reown_sign/LICENSE b/packages/reown_sign/LICENSE new file mode 100644 index 0000000..212a53d --- /dev/null +++ b/packages/reown_sign/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Reown, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/reown_sign/README.md b/packages/reown_sign/README.md new file mode 100644 index 0000000..69929cf --- /dev/null +++ b/packages/reown_sign/README.md @@ -0,0 +1 @@ +# **Reown - Sign SDK Flutter** diff --git a/packages/reown_sign/analysis_options.yaml b/packages/reown_sign/analysis_options.yaml new file mode 100644 index 0000000..b677fc6 --- /dev/null +++ b/packages/reown_sign/analysis_options.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:lints/recommended.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + non_constant_identifier_names: false + constant_identifier_names: false + avoid_print: true + prefer_single_quotes: true + sort_pub_dependencies: true + avoid_unnecessary_containers: true + cancel_subscriptions: true + +analyzer: + exclude: + - '**.freezed.dart' + - '**.g.dart' + - '**/*.freezed.dart' + - '**/*.g.dart' + - '**/generated_plugin_registrant.dart' + errors: + invalid_annotation_target: ignore +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reown_sign/build.yaml b/packages/reown_sign/build.yaml new file mode 100644 index 0000000..71e3c33 --- /dev/null +++ b/packages/reown_sign/build.yaml @@ -0,0 +1,16 @@ +targets: + $default: + builders: + build_version: + options: + output: lib/version.dart + freezed: + generate_for: + - lib/**.dart + - test/shared/shared_test_utils.dart + json_serializable: + options: + explicit_to_json: true + generate_for: + - lib/**.dart + - test/shared/shared_test_utils.dart diff --git a/packages/reown_sign/lib/i_sign_client.dart b/packages/reown_sign/lib/i_sign_client.dart new file mode 100644 index 0000000..6cb01de --- /dev/null +++ b/packages/reown_sign/lib/i_sign_client.dart @@ -0,0 +1,186 @@ +import 'package:event/event.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_sign/reown_sign.dart'; + +abstract class IReownSignClient { + final String protocol = 'wc'; + final int version = 2; + + abstract final IReownSign engine; + + // Common + abstract final Event onSessionConnect; + abstract final Event onSessionDelete; + abstract final Event onSessionExpire; + abstract final Event onSessionPing; + abstract final Event onProposalExpire; + + abstract final IReownCore core; + abstract final PairingMetadata metadata; + abstract final IGenericStore proposals; + abstract final ISessions sessions; + abstract final IGenericStore pendingRequests; + + abstract final IGenericStore authKeys; + abstract final IGenericStore pairingTopics; + + // Wallet + abstract final Event onSessionProposal; + abstract final Event onSessionProposalError; + abstract final Event onSessionRequest; + abstract final Event onSessionAuthRequest; + abstract final IGenericStore sessionAuthRequests; + + // App + abstract final Event onSessionUpdate; + abstract final Event onSessionExtend; + abstract final Event onSessionEvent; + abstract final Event onSessionAuthResponse; + + Future init(); + Future connect({ + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + String? pairingTopic, + List? relays, + List>? methods, + }); + Future pair({ + required Uri uri, + }); + Future approve({ + required int id, + required Map namespaces, + Map? sessionProperties, + String? relayProtocol, + }); + Future reject({ + required int id, + required ReownSignError reason, + }); + Future update({ + required String topic, + required Map namespaces, + }); + Future extend({ + required String topic, + }); + void registerRequestHandler({ + required String chainId, + required String method, + dynamic Function(String, dynamic)? handler, + }); + Future respond({ + required String topic, + required JsonRpcResponse response, + }); + Future emit({ + required String topic, + required String chainId, + required SessionEventParams event, + }); + Future request({ + required String topic, + required String chainId, + required SessionRequestParams request, + }); + Future> requestReadContract({ + required DeployedContract deployedContract, + required String functionName, + required String rpcUrl, + EthereumAddress? sender, + List parameters = const [], + }); + Future requestWriteContract({ + required String topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + required Transaction transaction, + String? method, + List parameters = const [], + }); + + void registerEventHandler({ + required String chainId, + required String event, + required dynamic Function(String, dynamic)? handler, + }); + Future ping({ + required String topic, + }); + Future disconnect({ + required String topic, + required ReownSignError reason, + }); + SessionData? find({ + required Map requiredNamespaces, + }); + Map getActiveSessions(); + Map getSessionsForPairing({ + required String pairingTopic, + }); + Map getPendingSessionProposals(); + Map getPendingSessionRequests(); + abstract final IPairingStore pairings; + + /// Register event emitters for a given namespace or chainId + /// Used to construct the Namespaces map for the session proposal + void registerEventEmitter({ + required String chainId, + required String event, + }); + + /// Register accounts for a given namespace or chainId. + /// Used to construct the Namespaces map for the session proposal. + /// Each account must follow the namespace:chainId:address format or this will throw an error. + void registerAccount({ + required String chainId, + required String accountAddress, + }); + + // FORMER AUTH ENGINE COMMON METHODS + + String formatAuthMessage({ + required String iss, + required CacaoRequestPayload cacaoPayload, + }); + + Future approveSessionAuthenticate({ + required int id, + List? auths, + }); + + Future rejectSessionAuthenticate({ + required int id, + required ReownSignError reason, + }); + + Map getPendingSessionAuthRequests(); + + Future authenticate({ + required SessionAuthRequestParams params, + String? walletUniversalLink, + String? pairingTopic, + List>? methods, + }); + + Future validateSignedCacao({ + required Cacao cacao, + required String projectId, + }); + + Future dispatchEnvelope(String url); + + Future redirectToDapp({ + required String topic, + required Redirect? redirect, + }); + + Future redirectToWallet({ + required String topic, + required Redirect? redirect, + }); +} diff --git a/packages/reown_sign/lib/i_sign_common.dart b/packages/reown_sign/lib/i_sign_common.dart new file mode 100644 index 0000000..2f13a58 --- /dev/null +++ b/packages/reown_sign/lib/i_sign_common.dart @@ -0,0 +1,50 @@ +import 'package:event/event.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_sign/store/i_sessions.dart'; +import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/models/proposal_models.dart'; +import 'package:reown_sign/models/session_models.dart'; +import 'package:reown_sign/models/sign_client_events.dart'; +import 'package:reown_sign/models/cacao_models.dart'; + +abstract class IReownSignCommon { + abstract final Event onSessionConnect; + abstract final Event onSessionDelete; + abstract final Event onSessionExpire; + abstract final Event onSessionPing; + abstract final Event onProposalExpire; + + abstract final IReownCore core; + abstract final PairingMetadata metadata; + abstract final IGenericStore proposals; + abstract final ISessions sessions; + abstract final IGenericStore pendingRequests; + + abstract final IGenericStore authKeys; + abstract final IPairingStore pairings; + abstract final IGenericStore pairingTopics; + + Future init(); + Future disconnectSession({ + required String topic, + required ReownSignError reason, + }); + Map getActiveSessions(); + Map getSessionsForPairing({ + required String pairingTopic, + }); + Map getPendingSessionProposals(); + + String formatAuthMessage({ + required String iss, + required CacaoRequestPayload cacaoPayload, + }); + + Future validateSignedCacao({ + required Cacao cacao, + required String projectId, + }); + + Future dispatchEnvelope(String url); +} diff --git a/packages/reown_sign/lib/i_sign_dapp.dart b/packages/reown_sign/lib/i_sign_dapp.dart new file mode 100644 index 0000000..d5d7a2f --- /dev/null +++ b/packages/reown_sign/lib/i_sign_dapp.dart @@ -0,0 +1,62 @@ +import 'package:event/event.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/reown_sign.dart'; + +abstract class IReownSignDapp extends IReownSignCommon { + abstract final Event onSessionUpdate; + abstract final Event onSessionExtend; + abstract final Event onSessionEvent; + abstract final Event onSessionAuthResponse; + + Future connect({ + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + String? pairingTopic, + List? relays, + List>? methods, + }); + Future request({ + required String topic, + required String chainId, + required SessionRequestParams request, + }); + Future> requestReadContract({ + required DeployedContract deployedContract, + required String functionName, + required String rpcUrl, + EthereumAddress? sender, + List parameters = const [], + }); + Future requestWriteContract({ + required String topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + required Transaction transaction, + String? method, + List parameters = const [], + }); + + void registerEventHandler({ + required String chainId, + required String event, + dynamic Function(String, dynamic)? handler, + }); + Future ping({ + required String topic, + }); + + Future authenticate({ + required SessionAuthRequestParams params, + String? walletUniversalLink, + String? pairingTopic, + List>? methods, + }); + + Future redirectToWallet({ + required String topic, + required Redirect? redirect, + }); +} diff --git a/packages/reown_sign/lib/i_sign_engine.dart b/packages/reown_sign/lib/i_sign_engine.dart new file mode 100644 index 0000000..6c0cfa9 --- /dev/null +++ b/packages/reown_sign/lib/i_sign_engine.dart @@ -0,0 +1,6 @@ +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; + +abstract class IReownSign implements IReownSignWallet, IReownSignDapp { + Future checkAndExpire(); +} diff --git a/packages/reown_sign/lib/i_sign_wallet.dart b/packages/reown_sign/lib/i_sign_wallet.dart new file mode 100644 index 0000000..f53c61e --- /dev/null +++ b/packages/reown_sign/lib/i_sign_wallet.dart @@ -0,0 +1,94 @@ +import 'package:event/event.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/models/cacao_models.dart'; +import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/models/json_rpc_models.dart'; +import 'package:reown_sign/models/proposal_models.dart'; +import 'package:reown_sign/models/session_models.dart'; +import 'package:reown_sign/models/sign_client_events.dart'; +import 'package:reown_sign/models/sign_client_models.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +import 'package:reown_sign/models/session_auth_models.dart'; +import 'package:reown_sign/models/session_auth_events.dart'; + +abstract class IReownSignWallet extends IReownSignCommon { + abstract final Event onSessionProposal; + abstract final Event onSessionProposalError; + abstract final Event onSessionRequest; + + abstract final Event onSessionAuthRequest; + abstract final IGenericStore sessionAuthRequests; + + Future pair({ + required Uri uri, + }); + Future approveSession({ + required int id, + required Map namespaces, + Map? sessionProperties, + String? relayProtocol, + }); + Future rejectSession({ + required int id, + required ReownSignError reason, + }); + Future updateSession({ + required String topic, + required Map namespaces, + }); + Future extendSession({ + required String topic, + }); + void registerRequestHandler({ + required String chainId, + required String method, + dynamic Function(String, dynamic)? handler, + }); + Future respondSessionRequest({ + required String topic, + required JsonRpcResponse response, + }); + Future emitSessionEvent({ + required String topic, + required String chainId, + required SessionEventParams event, + }); + SessionData? find({ + required Map requiredNamespaces, + }); + Map getPendingSessionRequests(); + + /// Register event emitters for a given namespace or chainId + /// Used to construct the Namespaces map for the session proposal + void registerEventEmitter({ + required String chainId, + required String event, + }); + + /// Register accounts for a given namespace or chainId. + /// Used to construct the Namespaces map for the session proposal. + /// Each account must follow the namespace:chainId:address format or this will throw an error. + void registerAccount({ + required String chainId, + required String accountAddress, + }); + + Future approveSessionAuthenticate({ + required int id, + List? auths, + }); + + Future rejectSessionAuthenticate({ + required int id, + required ReownSignError reason, + }); + + Map getPendingSessionAuthRequests(); + + Future redirectToDapp({ + required String topic, + required Redirect? redirect, + }); +} diff --git a/packages/reown_sign/lib/models/auth_client_models.freezed.dart b/packages/reown_sign/lib/models/auth_client_models.freezed.dart new file mode 100644 index 0000000..6c0a6d4 --- /dev/null +++ b/packages/reown_sign/lib/models/auth_client_models.freezed.dart @@ -0,0 +1,655 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'auth_client_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +AuthPayloadParams _$AuthPayloadParamsFromJson(Map json) { + return _AuthPayloadParams.fromJson(json); +} + +/// @nodoc +mixin _$AuthPayloadParams { + String get chainId => throw _privateConstructorUsedError; + String get aud => throw _privateConstructorUsedError; + String get domain => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; // + String get version => throw _privateConstructorUsedError; + String get iat => throw _privateConstructorUsedError; // + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AuthPayloadParamsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthPayloadParamsCopyWith<$Res> { + factory $AuthPayloadParamsCopyWith( + AuthPayloadParams value, $Res Function(AuthPayloadParams) then) = + _$AuthPayloadParamsCopyWithImpl<$Res, AuthPayloadParams>; + @useResult + $Res call( + {String chainId, + String aud, + String domain, + String nonce, + String type, + String version, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class _$AuthPayloadParamsCopyWithImpl<$Res, $Val extends AuthPayloadParams> + implements $AuthPayloadParamsCopyWith<$Res> { + _$AuthPayloadParamsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chainId = null, + Object? aud = null, + Object? domain = null, + Object? nonce = null, + Object? type = null, + Object? version = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_value.copyWith( + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AuthPayloadParamsImplCopyWith<$Res> + implements $AuthPayloadParamsCopyWith<$Res> { + factory _$$AuthPayloadParamsImplCopyWith(_$AuthPayloadParamsImpl value, + $Res Function(_$AuthPayloadParamsImpl) then) = + __$$AuthPayloadParamsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String chainId, + String aud, + String domain, + String nonce, + String type, + String version, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class __$$AuthPayloadParamsImplCopyWithImpl<$Res> + extends _$AuthPayloadParamsCopyWithImpl<$Res, _$AuthPayloadParamsImpl> + implements _$$AuthPayloadParamsImplCopyWith<$Res> { + __$$AuthPayloadParamsImplCopyWithImpl(_$AuthPayloadParamsImpl _value, + $Res Function(_$AuthPayloadParamsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chainId = null, + Object? aud = null, + Object? domain = null, + Object? nonce = null, + Object? type = null, + Object? version = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_$AuthPayloadParamsImpl( + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$AuthPayloadParamsImpl implements _AuthPayloadParams { + const _$AuthPayloadParamsImpl( + {required this.chainId, + required this.aud, + required this.domain, + required this.nonce, + required this.type, + required this.version, + required this.iat, + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources}) + : _resources = resources; + + factory _$AuthPayloadParamsImpl.fromJson(Map json) => + _$$AuthPayloadParamsImplFromJson(json); + + @override + final String chainId; + @override + final String aud; + @override + final String domain; + @override + final String nonce; + @override + final String type; +// + @override + final String version; + @override + final String iat; +// + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'AuthPayloadParams(chainId: $chainId, aud: $aud, domain: $domain, nonce: $nonce, type: $type, version: $version, iat: $iat, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthPayloadParamsImpl && + (identical(other.chainId, chainId) || other.chainId == chainId) && + (identical(other.aud, aud) || other.aud == aud) && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.type, type) || other.type == type) && + (identical(other.version, version) || other.version == version) && + (identical(other.iat, iat) || other.iat == iat) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + chainId, + aud, + domain, + nonce, + type, + version, + iat, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AuthPayloadParamsImplCopyWith<_$AuthPayloadParamsImpl> get copyWith => + __$$AuthPayloadParamsImplCopyWithImpl<_$AuthPayloadParamsImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$AuthPayloadParamsImplToJson( + this, + ); + } +} + +abstract class _AuthPayloadParams implements AuthPayloadParams { + const factory _AuthPayloadParams( + {required final String chainId, + required final String aud, + required final String domain, + required final String nonce, + required final String type, + required final String version, + required final String iat, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources}) = _$AuthPayloadParamsImpl; + + factory _AuthPayloadParams.fromJson(Map json) = + _$AuthPayloadParamsImpl.fromJson; + + @override + String get chainId; + @override + String get aud; + @override + String get domain; + @override + String get nonce; + @override + String get type; + @override // + String get version; + @override + String get iat; + @override // + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + @JsonKey(ignore: true) + _$$AuthPayloadParamsImplCopyWith<_$AuthPayloadParamsImpl> get copyWith => + throw _privateConstructorUsedError; +} + +PendingAuthRequest _$PendingAuthRequestFromJson(Map json) { + return _PendingAuthRequest.fromJson(json); +} + +/// @nodoc +mixin _$PendingAuthRequest { + int get id => throw _privateConstructorUsedError; + String get pairingTopic => throw _privateConstructorUsedError; + ConnectionMetadata get metadata => throw _privateConstructorUsedError; + CacaoRequestPayload get cacaoPayload => throw _privateConstructorUsedError; + TransportType get transportType => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PendingAuthRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PendingAuthRequestCopyWith<$Res> { + factory $PendingAuthRequestCopyWith( + PendingAuthRequest value, $Res Function(PendingAuthRequest) then) = + _$PendingAuthRequestCopyWithImpl<$Res, PendingAuthRequest>; + @useResult + $Res call( + {int id, + String pairingTopic, + ConnectionMetadata metadata, + CacaoRequestPayload cacaoPayload, + TransportType transportType}); + + $ConnectionMetadataCopyWith<$Res> get metadata; + $CacaoRequestPayloadCopyWith<$Res> get cacaoPayload; +} + +/// @nodoc +class _$PendingAuthRequestCopyWithImpl<$Res, $Val extends PendingAuthRequest> + implements $PendingAuthRequestCopyWith<$Res> { + _$PendingAuthRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? pairingTopic = null, + Object? metadata = null, + Object? cacaoPayload = null, + Object? transportType = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + cacaoPayload: null == cacaoPayload + ? _value.cacaoPayload + : cacaoPayload // ignore: cast_nullable_to_non_nullable + as CacaoRequestPayload, + transportType: null == transportType + ? _value.transportType + : transportType // ignore: cast_nullable_to_non_nullable + as TransportType, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get metadata { + return $ConnectionMetadataCopyWith<$Res>(_value.metadata, (value) { + return _then(_value.copyWith(metadata: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoRequestPayloadCopyWith<$Res> get cacaoPayload { + return $CacaoRequestPayloadCopyWith<$Res>(_value.cacaoPayload, (value) { + return _then(_value.copyWith(cacaoPayload: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$PendingAuthRequestImplCopyWith<$Res> + implements $PendingAuthRequestCopyWith<$Res> { + factory _$$PendingAuthRequestImplCopyWith(_$PendingAuthRequestImpl value, + $Res Function(_$PendingAuthRequestImpl) then) = + __$$PendingAuthRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String pairingTopic, + ConnectionMetadata metadata, + CacaoRequestPayload cacaoPayload, + TransportType transportType}); + + @override + $ConnectionMetadataCopyWith<$Res> get metadata; + @override + $CacaoRequestPayloadCopyWith<$Res> get cacaoPayload; +} + +/// @nodoc +class __$$PendingAuthRequestImplCopyWithImpl<$Res> + extends _$PendingAuthRequestCopyWithImpl<$Res, _$PendingAuthRequestImpl> + implements _$$PendingAuthRequestImplCopyWith<$Res> { + __$$PendingAuthRequestImplCopyWithImpl(_$PendingAuthRequestImpl _value, + $Res Function(_$PendingAuthRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? pairingTopic = null, + Object? metadata = null, + Object? cacaoPayload = null, + Object? transportType = null, + }) { + return _then(_$PendingAuthRequestImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + cacaoPayload: null == cacaoPayload + ? _value.cacaoPayload + : cacaoPayload // ignore: cast_nullable_to_non_nullable + as CacaoRequestPayload, + transportType: null == transportType + ? _value.transportType + : transportType // ignore: cast_nullable_to_non_nullable + as TransportType, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$PendingAuthRequestImpl implements _PendingAuthRequest { + const _$PendingAuthRequestImpl( + {required this.id, + required this.pairingTopic, + required this.metadata, + required this.cacaoPayload, + this.transportType = TransportType.relay}); + + factory _$PendingAuthRequestImpl.fromJson(Map json) => + _$$PendingAuthRequestImplFromJson(json); + + @override + final int id; + @override + final String pairingTopic; + @override + final ConnectionMetadata metadata; + @override + final CacaoRequestPayload cacaoPayload; + @override + @JsonKey() + final TransportType transportType; + + @override + String toString() { + return 'PendingAuthRequest(id: $id, pairingTopic: $pairingTopic, metadata: $metadata, cacaoPayload: $cacaoPayload, transportType: $transportType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PendingAuthRequestImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.pairingTopic, pairingTopic) || + other.pairingTopic == pairingTopic) && + (identical(other.metadata, metadata) || + other.metadata == metadata) && + (identical(other.cacaoPayload, cacaoPayload) || + other.cacaoPayload == cacaoPayload) && + (identical(other.transportType, transportType) || + other.transportType == transportType)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, id, pairingTopic, metadata, cacaoPayload, transportType); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PendingAuthRequestImplCopyWith<_$PendingAuthRequestImpl> get copyWith => + __$$PendingAuthRequestImplCopyWithImpl<_$PendingAuthRequestImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$PendingAuthRequestImplToJson( + this, + ); + } +} + +abstract class _PendingAuthRequest implements PendingAuthRequest { + const factory _PendingAuthRequest( + {required final int id, + required final String pairingTopic, + required final ConnectionMetadata metadata, + required final CacaoRequestPayload cacaoPayload, + final TransportType transportType}) = _$PendingAuthRequestImpl; + + factory _PendingAuthRequest.fromJson(Map json) = + _$PendingAuthRequestImpl.fromJson; + + @override + int get id; + @override + String get pairingTopic; + @override + ConnectionMetadata get metadata; + @override + CacaoRequestPayload get cacaoPayload; + @override + TransportType get transportType; + @override + @JsonKey(ignore: true) + _$$PendingAuthRequestImplCopyWith<_$PendingAuthRequestImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_sign/lib/models/auth_client_models.g.dart b/packages/reown_sign/lib/models/auth_client_models.g.dart new file mode 100644 index 0000000..e48bc53 --- /dev/null +++ b/packages/reown_sign/lib/models/auth_client_models.g.dart @@ -0,0 +1,81 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_client_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AuthPayloadParamsImpl _$$AuthPayloadParamsImplFromJson( + Map json) => + _$AuthPayloadParamsImpl( + chainId: json['chainId'] as String, + aud: json['aud'] as String, + domain: json['domain'] as String, + nonce: json['nonce'] as String, + type: json['type'] as String, + version: json['version'] as String, + iat: json['iat'] as String, + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + ); + +Map _$$AuthPayloadParamsImplToJson( + _$AuthPayloadParamsImpl instance) { + final val = { + 'chainId': instance.chainId, + 'aud': instance.aud, + 'domain': instance.domain, + 'nonce': instance.nonce, + 'type': instance.type, + 'version': instance.version, + 'iat': instance.iat, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('nbf', instance.nbf); + writeNotNull('exp', instance.exp); + writeNotNull('statement', instance.statement); + writeNotNull('requestId', instance.requestId); + writeNotNull('resources', instance.resources); + return val; +} + +_$PendingAuthRequestImpl _$$PendingAuthRequestImplFromJson( + Map json) => + _$PendingAuthRequestImpl( + id: (json['id'] as num).toInt(), + pairingTopic: json['pairingTopic'] as String, + metadata: + ConnectionMetadata.fromJson(json['metadata'] as Map), + cacaoPayload: CacaoRequestPayload.fromJson( + json['cacaoPayload'] as Map), + transportType: + $enumDecodeNullable(_$TransportTypeEnumMap, json['transportType']) ?? + TransportType.relay, + ); + +Map _$$PendingAuthRequestImplToJson( + _$PendingAuthRequestImpl instance) => + { + 'id': instance.id, + 'pairingTopic': instance.pairingTopic, + 'metadata': instance.metadata.toJson(), + 'cacaoPayload': instance.cacaoPayload.toJson(), + 'transportType': _$TransportTypeEnumMap[instance.transportType]!, + }; + +const _$TransportTypeEnumMap = { + TransportType.relay: 'relay', + TransportType.linkMode: 'linkMode', +}; diff --git a/packages/reown_sign/lib/models/basic_models.dart b/packages/reown_sign/lib/models/basic_models.dart new file mode 100644 index 0000000..24bb2dc --- /dev/null +++ b/packages/reown_sign/lib/models/basic_models.dart @@ -0,0 +1,44 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reown_core/reown_core.dart'; + +part 'basic_models.g.dart'; +part 'basic_models.freezed.dart'; + +/// ERRORS + +class ReownSignErrorSilent {} + +@freezed +class ReownSignError with _$ReownSignError { + @JsonSerializable(includeIfNull: false) + const factory ReownSignError({ + required int code, + required String message, + String? data, + }) = _ReownSignError; + + factory ReownSignError.fromJson(Map json) => + _$ReownSignErrorFromJson(json); +} + +@freezed +class ConnectionMetadata with _$ConnectionMetadata { + const factory ConnectionMetadata({ + required String publicKey, + required PairingMetadata metadata, + }) = _ConnectionMetadata; + + factory ConnectionMetadata.fromJson(Map json) => + _$ConnectionMetadataFromJson(json); +} + +@freezed +class AuthPublicKey with _$AuthPublicKey { + @JsonSerializable(includeIfNull: false) + const factory AuthPublicKey({ + required String publicKey, + }) = _AuthPublicKey; + + factory AuthPublicKey.fromJson(Map json) => + _$AuthPublicKeyFromJson(json); +} diff --git a/packages/reown_sign/lib/models/basic_models.freezed.dart b/packages/reown_sign/lib/models/basic_models.freezed.dart new file mode 100644 index 0000000..e259bdd --- /dev/null +++ b/packages/reown_sign/lib/models/basic_models.freezed.dart @@ -0,0 +1,499 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'basic_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ReownSignError _$ReownSignErrorFromJson(Map json) { + return _ReownSignError.fromJson(json); +} + +/// @nodoc +mixin _$ReownSignError { + int get code => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + String? get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ReownSignErrorCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ReownSignErrorCopyWith<$Res> { + factory $ReownSignErrorCopyWith( + ReownSignError value, $Res Function(ReownSignError) then) = + _$ReownSignErrorCopyWithImpl<$Res, ReownSignError>; + @useResult + $Res call({int code, String message, String? data}); +} + +/// @nodoc +class _$ReownSignErrorCopyWithImpl<$Res, $Val extends ReownSignError> + implements $ReownSignErrorCopyWith<$Res> { + _$ReownSignErrorCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? message = null, + Object? data = freezed, + }) { + return _then(_value.copyWith( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ReownSignErrorImplCopyWith<$Res> + implements $ReownSignErrorCopyWith<$Res> { + factory _$$ReownSignErrorImplCopyWith(_$ReownSignErrorImpl value, + $Res Function(_$ReownSignErrorImpl) then) = + __$$ReownSignErrorImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int code, String message, String? data}); +} + +/// @nodoc +class __$$ReownSignErrorImplCopyWithImpl<$Res> + extends _$ReownSignErrorCopyWithImpl<$Res, _$ReownSignErrorImpl> + implements _$$ReownSignErrorImplCopyWith<$Res> { + __$$ReownSignErrorImplCopyWithImpl( + _$ReownSignErrorImpl _value, $Res Function(_$ReownSignErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? message = null, + Object? data = freezed, + }) { + return _then(_$ReownSignErrorImpl( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$ReownSignErrorImpl implements _ReownSignError { + const _$ReownSignErrorImpl( + {required this.code, required this.message, this.data}); + + factory _$ReownSignErrorImpl.fromJson(Map json) => + _$$ReownSignErrorImplFromJson(json); + + @override + final int code; + @override + final String message; + @override + final String? data; + + @override + String toString() { + return 'ReownSignError(code: $code, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ReownSignErrorImpl && + (identical(other.code, code) || other.code == code) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, code, message, data); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ReownSignErrorImplCopyWith<_$ReownSignErrorImpl> get copyWith => + __$$ReownSignErrorImplCopyWithImpl<_$ReownSignErrorImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ReownSignErrorImplToJson( + this, + ); + } +} + +abstract class _ReownSignError implements ReownSignError { + const factory _ReownSignError( + {required final int code, + required final String message, + final String? data}) = _$ReownSignErrorImpl; + + factory _ReownSignError.fromJson(Map json) = + _$ReownSignErrorImpl.fromJson; + + @override + int get code; + @override + String get message; + @override + String? get data; + @override + @JsonKey(ignore: true) + _$$ReownSignErrorImplCopyWith<_$ReownSignErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ConnectionMetadata _$ConnectionMetadataFromJson(Map json) { + return _ConnectionMetadata.fromJson(json); +} + +/// @nodoc +mixin _$ConnectionMetadata { + String get publicKey => throw _privateConstructorUsedError; + PairingMetadata get metadata => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ConnectionMetadataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ConnectionMetadataCopyWith<$Res> { + factory $ConnectionMetadataCopyWith( + ConnectionMetadata value, $Res Function(ConnectionMetadata) then) = + _$ConnectionMetadataCopyWithImpl<$Res, ConnectionMetadata>; + @useResult + $Res call({String publicKey, PairingMetadata metadata}); + + $PairingMetadataCopyWith<$Res> get metadata; +} + +/// @nodoc +class _$ConnectionMetadataCopyWithImpl<$Res, $Val extends ConnectionMetadata> + implements $ConnectionMetadataCopyWith<$Res> { + _$ConnectionMetadataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? publicKey = null, + Object? metadata = null, + }) { + return _then(_value.copyWith( + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as PairingMetadata, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $PairingMetadataCopyWith<$Res> get metadata { + return $PairingMetadataCopyWith<$Res>(_value.metadata, (value) { + return _then(_value.copyWith(metadata: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ConnectionMetadataImplCopyWith<$Res> + implements $ConnectionMetadataCopyWith<$Res> { + factory _$$ConnectionMetadataImplCopyWith(_$ConnectionMetadataImpl value, + $Res Function(_$ConnectionMetadataImpl) then) = + __$$ConnectionMetadataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String publicKey, PairingMetadata metadata}); + + @override + $PairingMetadataCopyWith<$Res> get metadata; +} + +/// @nodoc +class __$$ConnectionMetadataImplCopyWithImpl<$Res> + extends _$ConnectionMetadataCopyWithImpl<$Res, _$ConnectionMetadataImpl> + implements _$$ConnectionMetadataImplCopyWith<$Res> { + __$$ConnectionMetadataImplCopyWithImpl(_$ConnectionMetadataImpl _value, + $Res Function(_$ConnectionMetadataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? publicKey = null, + Object? metadata = null, + }) { + return _then(_$ConnectionMetadataImpl( + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as PairingMetadata, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ConnectionMetadataImpl implements _ConnectionMetadata { + const _$ConnectionMetadataImpl( + {required this.publicKey, required this.metadata}); + + factory _$ConnectionMetadataImpl.fromJson(Map json) => + _$$ConnectionMetadataImplFromJson(json); + + @override + final String publicKey; + @override + final PairingMetadata metadata; + + @override + String toString() { + return 'ConnectionMetadata(publicKey: $publicKey, metadata: $metadata)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ConnectionMetadataImpl && + (identical(other.publicKey, publicKey) || + other.publicKey == publicKey) && + (identical(other.metadata, metadata) || + other.metadata == metadata)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, publicKey, metadata); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ConnectionMetadataImplCopyWith<_$ConnectionMetadataImpl> get copyWith => + __$$ConnectionMetadataImplCopyWithImpl<_$ConnectionMetadataImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ConnectionMetadataImplToJson( + this, + ); + } +} + +abstract class _ConnectionMetadata implements ConnectionMetadata { + const factory _ConnectionMetadata( + {required final String publicKey, + required final PairingMetadata metadata}) = _$ConnectionMetadataImpl; + + factory _ConnectionMetadata.fromJson(Map json) = + _$ConnectionMetadataImpl.fromJson; + + @override + String get publicKey; + @override + PairingMetadata get metadata; + @override + @JsonKey(ignore: true) + _$$ConnectionMetadataImplCopyWith<_$ConnectionMetadataImpl> get copyWith => + throw _privateConstructorUsedError; +} + +AuthPublicKey _$AuthPublicKeyFromJson(Map json) { + return _AuthPublicKey.fromJson(json); +} + +/// @nodoc +mixin _$AuthPublicKey { + String get publicKey => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AuthPublicKeyCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthPublicKeyCopyWith<$Res> { + factory $AuthPublicKeyCopyWith( + AuthPublicKey value, $Res Function(AuthPublicKey) then) = + _$AuthPublicKeyCopyWithImpl<$Res, AuthPublicKey>; + @useResult + $Res call({String publicKey}); +} + +/// @nodoc +class _$AuthPublicKeyCopyWithImpl<$Res, $Val extends AuthPublicKey> + implements $AuthPublicKeyCopyWith<$Res> { + _$AuthPublicKeyCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? publicKey = null, + }) { + return _then(_value.copyWith( + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AuthPublicKeyImplCopyWith<$Res> + implements $AuthPublicKeyCopyWith<$Res> { + factory _$$AuthPublicKeyImplCopyWith( + _$AuthPublicKeyImpl value, $Res Function(_$AuthPublicKeyImpl) then) = + __$$AuthPublicKeyImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String publicKey}); +} + +/// @nodoc +class __$$AuthPublicKeyImplCopyWithImpl<$Res> + extends _$AuthPublicKeyCopyWithImpl<$Res, _$AuthPublicKeyImpl> + implements _$$AuthPublicKeyImplCopyWith<$Res> { + __$$AuthPublicKeyImplCopyWithImpl( + _$AuthPublicKeyImpl _value, $Res Function(_$AuthPublicKeyImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? publicKey = null, + }) { + return _then(_$AuthPublicKeyImpl( + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$AuthPublicKeyImpl implements _AuthPublicKey { + const _$AuthPublicKeyImpl({required this.publicKey}); + + factory _$AuthPublicKeyImpl.fromJson(Map json) => + _$$AuthPublicKeyImplFromJson(json); + + @override + final String publicKey; + + @override + String toString() { + return 'AuthPublicKey(publicKey: $publicKey)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthPublicKeyImpl && + (identical(other.publicKey, publicKey) || + other.publicKey == publicKey)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, publicKey); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AuthPublicKeyImplCopyWith<_$AuthPublicKeyImpl> get copyWith => + __$$AuthPublicKeyImplCopyWithImpl<_$AuthPublicKeyImpl>(this, _$identity); + + @override + Map toJson() { + return _$$AuthPublicKeyImplToJson( + this, + ); + } +} + +abstract class _AuthPublicKey implements AuthPublicKey { + const factory _AuthPublicKey({required final String publicKey}) = + _$AuthPublicKeyImpl; + + factory _AuthPublicKey.fromJson(Map json) = + _$AuthPublicKeyImpl.fromJson; + + @override + String get publicKey; + @override + @JsonKey(ignore: true) + _$$AuthPublicKeyImplCopyWith<_$AuthPublicKeyImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_sign/lib/models/basic_models.g.dart b/packages/reown_sign/lib/models/basic_models.g.dart new file mode 100644 index 0000000..d8ece13 --- /dev/null +++ b/packages/reown_sign/lib/models/basic_models.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'basic_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ReownSignErrorImpl _$$ReownSignErrorImplFromJson(Map json) => + _$ReownSignErrorImpl( + code: (json['code'] as num).toInt(), + message: json['message'] as String, + data: json['data'] as String?, + ); + +Map _$$ReownSignErrorImplToJson( + _$ReownSignErrorImpl instance) { + final val = { + 'code': instance.code, + 'message': instance.message, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('data', instance.data); + return val; +} + +_$ConnectionMetadataImpl _$$ConnectionMetadataImplFromJson( + Map json) => + _$ConnectionMetadataImpl( + publicKey: json['publicKey'] as String, + metadata: + PairingMetadata.fromJson(json['metadata'] as Map), + ); + +Map _$$ConnectionMetadataImplToJson( + _$ConnectionMetadataImpl instance) => + { + 'publicKey': instance.publicKey, + 'metadata': instance.metadata.toJson(), + }; + +_$AuthPublicKeyImpl _$$AuthPublicKeyImplFromJson(Map json) => + _$AuthPublicKeyImpl( + publicKey: json['publicKey'] as String, + ); + +Map _$$AuthPublicKeyImplToJson(_$AuthPublicKeyImpl instance) => + { + 'publicKey': instance.publicKey, + }; diff --git a/packages/reown_sign/lib/models/cacao_models.dart b/packages/reown_sign/lib/models/cacao_models.dart new file mode 100644 index 0000000..b2688eb --- /dev/null +++ b/packages/reown_sign/lib/models/cacao_models.dart @@ -0,0 +1,185 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +// import 'package:reown_sign/models/auth_client_models.dart'; +import 'package:reown_sign/models/session_auth_models.dart'; + +part 'cacao_models.g.dart'; +part 'cacao_models.freezed.dart'; + +@freezed +class CacaoRequestPayload with _$CacaoRequestPayload { + @JsonSerializable(includeIfNull: false) + const factory CacaoRequestPayload({ + required String domain, + required String aud, + required String version, + required String nonce, + required String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + }) = _CacaoRequestPayload; + + factory CacaoRequestPayload.fromPayloadParams(SessionAuthPayload params) { + return CacaoRequestPayload( + domain: params.domain, + aud: params.aud, + version: params.version, + nonce: params.nonce, + iat: params.iat, + nbf: params.nbf, + exp: params.exp, + statement: params.statement, + requestId: params.requestId, + resources: params.resources, + ); + } + + factory CacaoRequestPayload.fromSessionAuthPayload( + SessionAuthPayload params, + ) { + return CacaoRequestPayload( + domain: params.domain, + aud: params.aud, + version: params.version, + nonce: params.nonce, + iat: params.iat, + nbf: params.nbf, + exp: params.exp, + statement: params.statement, + requestId: params.requestId, + resources: params.resources, + ); + } + + factory CacaoRequestPayload.fromCacaoPayload(CacaoPayload payload) { + return CacaoRequestPayload( + domain: payload.domain, + aud: payload.aud, + version: payload.version, + nonce: payload.nonce, + iat: payload.iat, + nbf: payload.nbf, + exp: payload.exp, + statement: payload.statement, + requestId: payload.requestId, + resources: payload.resources, + ); + } + + factory CacaoRequestPayload.fromJson(Map json) => + _$CacaoRequestPayloadFromJson(json); +} + +@freezed +class CacaoPayload with _$CacaoPayload { + @JsonSerializable(includeIfNull: false) + const factory CacaoPayload({ + required String iss, + required String domain, + required String aud, + required String version, + required String nonce, + required String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + }) = _CacaoPayload; + + factory CacaoPayload.fromRequestPayload({ + required String issuer, + required CacaoRequestPayload payload, + }) { + return CacaoPayload( + iss: issuer, + domain: payload.domain, + aud: payload.aud, + version: payload.version, + nonce: payload.nonce, + iat: payload.iat, + nbf: payload.nbf, + exp: payload.exp, + statement: payload.statement, + requestId: payload.requestId, + resources: payload.resources, + ); + } + + factory CacaoPayload.fromJson(Map json) => + _$CacaoPayloadFromJson(json); +} + +@freezed +class CacaoHeader with _$CacaoHeader { + static const EIP4361 = 'eip4361'; + static const CAIP122 = 'caip122'; + + @JsonSerializable(includeIfNull: false) + const factory CacaoHeader({ + @Default('eip4361') String t, + }) = _CacaoHeader; + + factory CacaoHeader.fromJson(Map json) => + _$CacaoHeaderFromJson(json); +} + +@freezed +class CacaoSignature with _$CacaoSignature { + static const EIP191 = 'eip191'; + static const EIP1271 = 'eip1271'; + + @JsonSerializable(includeIfNull: false) + const factory CacaoSignature({ + required String t, + required String s, + String? m, + }) = _CacaoSignature; + + factory CacaoSignature.fromJson(Map json) => + _$CacaoSignatureFromJson(json); +} + +@freezed +class Cacao with _$Cacao { + @JsonSerializable(includeIfNull: false) + const factory Cacao({ + required CacaoHeader h, + required CacaoPayload p, + required CacaoSignature s, + }) = _Cacao; + + factory Cacao.fromJson(Map json) => _$CacaoFromJson(json); +} + +@freezed +class StoredCacao with _$StoredCacao { + @JsonSerializable(includeIfNull: false) + const factory StoredCacao({ + required int id, + required String pairingTopic, + required CacaoHeader h, + required CacaoPayload p, + required CacaoSignature s, + }) = _StoredCacao; + + factory StoredCacao.fromCacao({ + required int id, + required String pairingTopic, + required Cacao cacao, + }) { + return StoredCacao( + id: id, + pairingTopic: pairingTopic, + h: cacao.h, + p: cacao.p, + s: cacao.s, + ); + } + + factory StoredCacao.fromJson(Map json) => + _$StoredCacaoFromJson(json); +} diff --git a/packages/reown_sign/lib/models/cacao_models.freezed.dart b/packages/reown_sign/lib/models/cacao_models.freezed.dart new file mode 100644 index 0000000..c1e6120 --- /dev/null +++ b/packages/reown_sign/lib/models/cacao_models.freezed.dart @@ -0,0 +1,1494 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'cacao_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +CacaoRequestPayload _$CacaoRequestPayloadFromJson(Map json) { + return _CacaoRequestPayload.fromJson(json); +} + +/// @nodoc +mixin _$CacaoRequestPayload { + String get domain => throw _privateConstructorUsedError; + String get aud => throw _privateConstructorUsedError; + String get version => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + String get iat => throw _privateConstructorUsedError; + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoRequestPayloadCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoRequestPayloadCopyWith<$Res> { + factory $CacaoRequestPayloadCopyWith( + CacaoRequestPayload value, $Res Function(CacaoRequestPayload) then) = + _$CacaoRequestPayloadCopyWithImpl<$Res, CacaoRequestPayload>; + @useResult + $Res call( + {String domain, + String aud, + String version, + String nonce, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class _$CacaoRequestPayloadCopyWithImpl<$Res, $Val extends CacaoRequestPayload> + implements $CacaoRequestPayloadCopyWith<$Res> { + _$CacaoRequestPayloadCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? domain = null, + Object? aud = null, + Object? version = null, + Object? nonce = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_value.copyWith( + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CacaoRequestPayloadImplCopyWith<$Res> + implements $CacaoRequestPayloadCopyWith<$Res> { + factory _$$CacaoRequestPayloadImplCopyWith(_$CacaoRequestPayloadImpl value, + $Res Function(_$CacaoRequestPayloadImpl) then) = + __$$CacaoRequestPayloadImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String domain, + String aud, + String version, + String nonce, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class __$$CacaoRequestPayloadImplCopyWithImpl<$Res> + extends _$CacaoRequestPayloadCopyWithImpl<$Res, _$CacaoRequestPayloadImpl> + implements _$$CacaoRequestPayloadImplCopyWith<$Res> { + __$$CacaoRequestPayloadImplCopyWithImpl(_$CacaoRequestPayloadImpl _value, + $Res Function(_$CacaoRequestPayloadImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? domain = null, + Object? aud = null, + Object? version = null, + Object? nonce = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_$CacaoRequestPayloadImpl( + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoRequestPayloadImpl implements _CacaoRequestPayload { + const _$CacaoRequestPayloadImpl( + {required this.domain, + required this.aud, + required this.version, + required this.nonce, + required this.iat, + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources}) + : _resources = resources; + + factory _$CacaoRequestPayloadImpl.fromJson(Map json) => + _$$CacaoRequestPayloadImplFromJson(json); + + @override + final String domain; + @override + final String aud; + @override + final String version; + @override + final String nonce; + @override + final String iat; + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'CacaoRequestPayload(domain: $domain, aud: $aud, version: $version, nonce: $nonce, iat: $iat, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoRequestPayloadImpl && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.aud, aud) || other.aud == aud) && + (identical(other.version, version) || other.version == version) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.iat, iat) || other.iat == iat) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + domain, + aud, + version, + nonce, + iat, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoRequestPayloadImplCopyWith<_$CacaoRequestPayloadImpl> get copyWith => + __$$CacaoRequestPayloadImplCopyWithImpl<_$CacaoRequestPayloadImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$CacaoRequestPayloadImplToJson( + this, + ); + } +} + +abstract class _CacaoRequestPayload implements CacaoRequestPayload { + const factory _CacaoRequestPayload( + {required final String domain, + required final String aud, + required final String version, + required final String nonce, + required final String iat, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources}) = _$CacaoRequestPayloadImpl; + + factory _CacaoRequestPayload.fromJson(Map json) = + _$CacaoRequestPayloadImpl.fromJson; + + @override + String get domain; + @override + String get aud; + @override + String get version; + @override + String get nonce; + @override + String get iat; + @override + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + @JsonKey(ignore: true) + _$$CacaoRequestPayloadImplCopyWith<_$CacaoRequestPayloadImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CacaoPayload _$CacaoPayloadFromJson(Map json) { + return _CacaoPayload.fromJson(json); +} + +/// @nodoc +mixin _$CacaoPayload { + String get iss => throw _privateConstructorUsedError; + String get domain => throw _privateConstructorUsedError; + String get aud => throw _privateConstructorUsedError; + String get version => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + String get iat => throw _privateConstructorUsedError; + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoPayloadCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoPayloadCopyWith<$Res> { + factory $CacaoPayloadCopyWith( + CacaoPayload value, $Res Function(CacaoPayload) then) = + _$CacaoPayloadCopyWithImpl<$Res, CacaoPayload>; + @useResult + $Res call( + {String iss, + String domain, + String aud, + String version, + String nonce, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class _$CacaoPayloadCopyWithImpl<$Res, $Val extends CacaoPayload> + implements $CacaoPayloadCopyWith<$Res> { + _$CacaoPayloadCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? iss = null, + Object? domain = null, + Object? aud = null, + Object? version = null, + Object? nonce = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_value.copyWith( + iss: null == iss + ? _value.iss + : iss // ignore: cast_nullable_to_non_nullable + as String, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CacaoPayloadImplCopyWith<$Res> + implements $CacaoPayloadCopyWith<$Res> { + factory _$$CacaoPayloadImplCopyWith( + _$CacaoPayloadImpl value, $Res Function(_$CacaoPayloadImpl) then) = + __$$CacaoPayloadImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String iss, + String domain, + String aud, + String version, + String nonce, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class __$$CacaoPayloadImplCopyWithImpl<$Res> + extends _$CacaoPayloadCopyWithImpl<$Res, _$CacaoPayloadImpl> + implements _$$CacaoPayloadImplCopyWith<$Res> { + __$$CacaoPayloadImplCopyWithImpl( + _$CacaoPayloadImpl _value, $Res Function(_$CacaoPayloadImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? iss = null, + Object? domain = null, + Object? aud = null, + Object? version = null, + Object? nonce = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_$CacaoPayloadImpl( + iss: null == iss + ? _value.iss + : iss // ignore: cast_nullable_to_non_nullable + as String, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoPayloadImpl implements _CacaoPayload { + const _$CacaoPayloadImpl( + {required this.iss, + required this.domain, + required this.aud, + required this.version, + required this.nonce, + required this.iat, + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources}) + : _resources = resources; + + factory _$CacaoPayloadImpl.fromJson(Map json) => + _$$CacaoPayloadImplFromJson(json); + + @override + final String iss; + @override + final String domain; + @override + final String aud; + @override + final String version; + @override + final String nonce; + @override + final String iat; + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'CacaoPayload(iss: $iss, domain: $domain, aud: $aud, version: $version, nonce: $nonce, iat: $iat, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoPayloadImpl && + (identical(other.iss, iss) || other.iss == iss) && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.aud, aud) || other.aud == aud) && + (identical(other.version, version) || other.version == version) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.iat, iat) || other.iat == iat) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + iss, + domain, + aud, + version, + nonce, + iat, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoPayloadImplCopyWith<_$CacaoPayloadImpl> get copyWith => + __$$CacaoPayloadImplCopyWithImpl<_$CacaoPayloadImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CacaoPayloadImplToJson( + this, + ); + } +} + +abstract class _CacaoPayload implements CacaoPayload { + const factory _CacaoPayload( + {required final String iss, + required final String domain, + required final String aud, + required final String version, + required final String nonce, + required final String iat, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources}) = _$CacaoPayloadImpl; + + factory _CacaoPayload.fromJson(Map json) = + _$CacaoPayloadImpl.fromJson; + + @override + String get iss; + @override + String get domain; + @override + String get aud; + @override + String get version; + @override + String get nonce; + @override + String get iat; + @override + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + @JsonKey(ignore: true) + _$$CacaoPayloadImplCopyWith<_$CacaoPayloadImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CacaoHeader _$CacaoHeaderFromJson(Map json) { + return _CacaoHeader.fromJson(json); +} + +/// @nodoc +mixin _$CacaoHeader { + String get t => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoHeaderCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoHeaderCopyWith<$Res> { + factory $CacaoHeaderCopyWith( + CacaoHeader value, $Res Function(CacaoHeader) then) = + _$CacaoHeaderCopyWithImpl<$Res, CacaoHeader>; + @useResult + $Res call({String t}); +} + +/// @nodoc +class _$CacaoHeaderCopyWithImpl<$Res, $Val extends CacaoHeader> + implements $CacaoHeaderCopyWith<$Res> { + _$CacaoHeaderCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? t = null, + }) { + return _then(_value.copyWith( + t: null == t + ? _value.t + : t // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CacaoHeaderImplCopyWith<$Res> + implements $CacaoHeaderCopyWith<$Res> { + factory _$$CacaoHeaderImplCopyWith( + _$CacaoHeaderImpl value, $Res Function(_$CacaoHeaderImpl) then) = + __$$CacaoHeaderImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String t}); +} + +/// @nodoc +class __$$CacaoHeaderImplCopyWithImpl<$Res> + extends _$CacaoHeaderCopyWithImpl<$Res, _$CacaoHeaderImpl> + implements _$$CacaoHeaderImplCopyWith<$Res> { + __$$CacaoHeaderImplCopyWithImpl( + _$CacaoHeaderImpl _value, $Res Function(_$CacaoHeaderImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? t = null, + }) { + return _then(_$CacaoHeaderImpl( + t: null == t + ? _value.t + : t // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoHeaderImpl implements _CacaoHeader { + const _$CacaoHeaderImpl({this.t = 'eip4361'}); + + factory _$CacaoHeaderImpl.fromJson(Map json) => + _$$CacaoHeaderImplFromJson(json); + + @override + @JsonKey() + final String t; + + @override + String toString() { + return 'CacaoHeader(t: $t)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoHeaderImpl && + (identical(other.t, t) || other.t == t)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, t); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoHeaderImplCopyWith<_$CacaoHeaderImpl> get copyWith => + __$$CacaoHeaderImplCopyWithImpl<_$CacaoHeaderImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CacaoHeaderImplToJson( + this, + ); + } +} + +abstract class _CacaoHeader implements CacaoHeader { + const factory _CacaoHeader({final String t}) = _$CacaoHeaderImpl; + + factory _CacaoHeader.fromJson(Map json) = + _$CacaoHeaderImpl.fromJson; + + @override + String get t; + @override + @JsonKey(ignore: true) + _$$CacaoHeaderImplCopyWith<_$CacaoHeaderImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CacaoSignature _$CacaoSignatureFromJson(Map json) { + return _CacaoSignature.fromJson(json); +} + +/// @nodoc +mixin _$CacaoSignature { + String get t => throw _privateConstructorUsedError; + String get s => throw _privateConstructorUsedError; + String? get m => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoSignatureCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoSignatureCopyWith<$Res> { + factory $CacaoSignatureCopyWith( + CacaoSignature value, $Res Function(CacaoSignature) then) = + _$CacaoSignatureCopyWithImpl<$Res, CacaoSignature>; + @useResult + $Res call({String t, String s, String? m}); +} + +/// @nodoc +class _$CacaoSignatureCopyWithImpl<$Res, $Val extends CacaoSignature> + implements $CacaoSignatureCopyWith<$Res> { + _$CacaoSignatureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? t = null, + Object? s = null, + Object? m = freezed, + }) { + return _then(_value.copyWith( + t: null == t + ? _value.t + : t // ignore: cast_nullable_to_non_nullable + as String, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as String, + m: freezed == m + ? _value.m + : m // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CacaoSignatureImplCopyWith<$Res> + implements $CacaoSignatureCopyWith<$Res> { + factory _$$CacaoSignatureImplCopyWith(_$CacaoSignatureImpl value, + $Res Function(_$CacaoSignatureImpl) then) = + __$$CacaoSignatureImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String t, String s, String? m}); +} + +/// @nodoc +class __$$CacaoSignatureImplCopyWithImpl<$Res> + extends _$CacaoSignatureCopyWithImpl<$Res, _$CacaoSignatureImpl> + implements _$$CacaoSignatureImplCopyWith<$Res> { + __$$CacaoSignatureImplCopyWithImpl( + _$CacaoSignatureImpl _value, $Res Function(_$CacaoSignatureImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? t = null, + Object? s = null, + Object? m = freezed, + }) { + return _then(_$CacaoSignatureImpl( + t: null == t + ? _value.t + : t // ignore: cast_nullable_to_non_nullable + as String, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as String, + m: freezed == m + ? _value.m + : m // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoSignatureImpl implements _CacaoSignature { + const _$CacaoSignatureImpl({required this.t, required this.s, this.m}); + + factory _$CacaoSignatureImpl.fromJson(Map json) => + _$$CacaoSignatureImplFromJson(json); + + @override + final String t; + @override + final String s; + @override + final String? m; + + @override + String toString() { + return 'CacaoSignature(t: $t, s: $s, m: $m)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoSignatureImpl && + (identical(other.t, t) || other.t == t) && + (identical(other.s, s) || other.s == s) && + (identical(other.m, m) || other.m == m)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, t, s, m); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoSignatureImplCopyWith<_$CacaoSignatureImpl> get copyWith => + __$$CacaoSignatureImplCopyWithImpl<_$CacaoSignatureImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$CacaoSignatureImplToJson( + this, + ); + } +} + +abstract class _CacaoSignature implements CacaoSignature { + const factory _CacaoSignature( + {required final String t, + required final String s, + final String? m}) = _$CacaoSignatureImpl; + + factory _CacaoSignature.fromJson(Map json) = + _$CacaoSignatureImpl.fromJson; + + @override + String get t; + @override + String get s; + @override + String? get m; + @override + @JsonKey(ignore: true) + _$$CacaoSignatureImplCopyWith<_$CacaoSignatureImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Cacao _$CacaoFromJson(Map json) { + return _Cacao.fromJson(json); +} + +/// @nodoc +mixin _$Cacao { + CacaoHeader get h => throw _privateConstructorUsedError; + CacaoPayload get p => throw _privateConstructorUsedError; + CacaoSignature get s => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoCopyWith<$Res> { + factory $CacaoCopyWith(Cacao value, $Res Function(Cacao) then) = + _$CacaoCopyWithImpl<$Res, Cacao>; + @useResult + $Res call({CacaoHeader h, CacaoPayload p, CacaoSignature s}); + + $CacaoHeaderCopyWith<$Res> get h; + $CacaoPayloadCopyWith<$Res> get p; + $CacaoSignatureCopyWith<$Res> get s; +} + +/// @nodoc +class _$CacaoCopyWithImpl<$Res, $Val extends Cacao> + implements $CacaoCopyWith<$Res> { + _$CacaoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? h = null, + Object? p = null, + Object? s = null, + }) { + return _then(_value.copyWith( + h: null == h + ? _value.h + : h // ignore: cast_nullable_to_non_nullable + as CacaoHeader, + p: null == p + ? _value.p + : p // ignore: cast_nullable_to_non_nullable + as CacaoPayload, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as CacaoSignature, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $CacaoHeaderCopyWith<$Res> get h { + return $CacaoHeaderCopyWith<$Res>(_value.h, (value) { + return _then(_value.copyWith(h: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoPayloadCopyWith<$Res> get p { + return $CacaoPayloadCopyWith<$Res>(_value.p, (value) { + return _then(_value.copyWith(p: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoSignatureCopyWith<$Res> get s { + return $CacaoSignatureCopyWith<$Res>(_value.s, (value) { + return _then(_value.copyWith(s: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$CacaoImplCopyWith<$Res> implements $CacaoCopyWith<$Res> { + factory _$$CacaoImplCopyWith( + _$CacaoImpl value, $Res Function(_$CacaoImpl) then) = + __$$CacaoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({CacaoHeader h, CacaoPayload p, CacaoSignature s}); + + @override + $CacaoHeaderCopyWith<$Res> get h; + @override + $CacaoPayloadCopyWith<$Res> get p; + @override + $CacaoSignatureCopyWith<$Res> get s; +} + +/// @nodoc +class __$$CacaoImplCopyWithImpl<$Res> + extends _$CacaoCopyWithImpl<$Res, _$CacaoImpl> + implements _$$CacaoImplCopyWith<$Res> { + __$$CacaoImplCopyWithImpl( + _$CacaoImpl _value, $Res Function(_$CacaoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? h = null, + Object? p = null, + Object? s = null, + }) { + return _then(_$CacaoImpl( + h: null == h + ? _value.h + : h // ignore: cast_nullable_to_non_nullable + as CacaoHeader, + p: null == p + ? _value.p + : p // ignore: cast_nullable_to_non_nullable + as CacaoPayload, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as CacaoSignature, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoImpl implements _Cacao { + const _$CacaoImpl({required this.h, required this.p, required this.s}); + + factory _$CacaoImpl.fromJson(Map json) => + _$$CacaoImplFromJson(json); + + @override + final CacaoHeader h; + @override + final CacaoPayload p; + @override + final CacaoSignature s; + + @override + String toString() { + return 'Cacao(h: $h, p: $p, s: $s)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoImpl && + (identical(other.h, h) || other.h == h) && + (identical(other.p, p) || other.p == p) && + (identical(other.s, s) || other.s == s)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, h, p, s); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoImplCopyWith<_$CacaoImpl> get copyWith => + __$$CacaoImplCopyWithImpl<_$CacaoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CacaoImplToJson( + this, + ); + } +} + +abstract class _Cacao implements Cacao { + const factory _Cacao( + {required final CacaoHeader h, + required final CacaoPayload p, + required final CacaoSignature s}) = _$CacaoImpl; + + factory _Cacao.fromJson(Map json) = _$CacaoImpl.fromJson; + + @override + CacaoHeader get h; + @override + CacaoPayload get p; + @override + CacaoSignature get s; + @override + @JsonKey(ignore: true) + _$$CacaoImplCopyWith<_$CacaoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +StoredCacao _$StoredCacaoFromJson(Map json) { + return _StoredCacao.fromJson(json); +} + +/// @nodoc +mixin _$StoredCacao { + int get id => throw _privateConstructorUsedError; + String get pairingTopic => throw _privateConstructorUsedError; + CacaoHeader get h => throw _privateConstructorUsedError; + CacaoPayload get p => throw _privateConstructorUsedError; + CacaoSignature get s => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $StoredCacaoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $StoredCacaoCopyWith<$Res> { + factory $StoredCacaoCopyWith( + StoredCacao value, $Res Function(StoredCacao) then) = + _$StoredCacaoCopyWithImpl<$Res, StoredCacao>; + @useResult + $Res call( + {int id, + String pairingTopic, + CacaoHeader h, + CacaoPayload p, + CacaoSignature s}); + + $CacaoHeaderCopyWith<$Res> get h; + $CacaoPayloadCopyWith<$Res> get p; + $CacaoSignatureCopyWith<$Res> get s; +} + +/// @nodoc +class _$StoredCacaoCopyWithImpl<$Res, $Val extends StoredCacao> + implements $StoredCacaoCopyWith<$Res> { + _$StoredCacaoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? pairingTopic = null, + Object? h = null, + Object? p = null, + Object? s = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + h: null == h + ? _value.h + : h // ignore: cast_nullable_to_non_nullable + as CacaoHeader, + p: null == p + ? _value.p + : p // ignore: cast_nullable_to_non_nullable + as CacaoPayload, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as CacaoSignature, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $CacaoHeaderCopyWith<$Res> get h { + return $CacaoHeaderCopyWith<$Res>(_value.h, (value) { + return _then(_value.copyWith(h: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoPayloadCopyWith<$Res> get p { + return $CacaoPayloadCopyWith<$Res>(_value.p, (value) { + return _then(_value.copyWith(p: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoSignatureCopyWith<$Res> get s { + return $CacaoSignatureCopyWith<$Res>(_value.s, (value) { + return _then(_value.copyWith(s: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$StoredCacaoImplCopyWith<$Res> + implements $StoredCacaoCopyWith<$Res> { + factory _$$StoredCacaoImplCopyWith( + _$StoredCacaoImpl value, $Res Function(_$StoredCacaoImpl) then) = + __$$StoredCacaoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String pairingTopic, + CacaoHeader h, + CacaoPayload p, + CacaoSignature s}); + + @override + $CacaoHeaderCopyWith<$Res> get h; + @override + $CacaoPayloadCopyWith<$Res> get p; + @override + $CacaoSignatureCopyWith<$Res> get s; +} + +/// @nodoc +class __$$StoredCacaoImplCopyWithImpl<$Res> + extends _$StoredCacaoCopyWithImpl<$Res, _$StoredCacaoImpl> + implements _$$StoredCacaoImplCopyWith<$Res> { + __$$StoredCacaoImplCopyWithImpl( + _$StoredCacaoImpl _value, $Res Function(_$StoredCacaoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? pairingTopic = null, + Object? h = null, + Object? p = null, + Object? s = null, + }) { + return _then(_$StoredCacaoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + h: null == h + ? _value.h + : h // ignore: cast_nullable_to_non_nullable + as CacaoHeader, + p: null == p + ? _value.p + : p // ignore: cast_nullable_to_non_nullable + as CacaoPayload, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as CacaoSignature, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$StoredCacaoImpl implements _StoredCacao { + const _$StoredCacaoImpl( + {required this.id, + required this.pairingTopic, + required this.h, + required this.p, + required this.s}); + + factory _$StoredCacaoImpl.fromJson(Map json) => + _$$StoredCacaoImplFromJson(json); + + @override + final int id; + @override + final String pairingTopic; + @override + final CacaoHeader h; + @override + final CacaoPayload p; + @override + final CacaoSignature s; + + @override + String toString() { + return 'StoredCacao(id: $id, pairingTopic: $pairingTopic, h: $h, p: $p, s: $s)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$StoredCacaoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.pairingTopic, pairingTopic) || + other.pairingTopic == pairingTopic) && + (identical(other.h, h) || other.h == h) && + (identical(other.p, p) || other.p == p) && + (identical(other.s, s) || other.s == s)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, pairingTopic, h, p, s); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$StoredCacaoImplCopyWith<_$StoredCacaoImpl> get copyWith => + __$$StoredCacaoImplCopyWithImpl<_$StoredCacaoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$StoredCacaoImplToJson( + this, + ); + } +} + +abstract class _StoredCacao implements StoredCacao { + const factory _StoredCacao( + {required final int id, + required final String pairingTopic, + required final CacaoHeader h, + required final CacaoPayload p, + required final CacaoSignature s}) = _$StoredCacaoImpl; + + factory _StoredCacao.fromJson(Map json) = + _$StoredCacaoImpl.fromJson; + + @override + int get id; + @override + String get pairingTopic; + @override + CacaoHeader get h; + @override + CacaoPayload get p; + @override + CacaoSignature get s; + @override + @JsonKey(ignore: true) + _$$StoredCacaoImplCopyWith<_$StoredCacaoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_sign/lib/models/cacao_models.g.dart b/packages/reown_sign/lib/models/cacao_models.g.dart new file mode 100644 index 0000000..2c9a100 --- /dev/null +++ b/packages/reown_sign/lib/models/cacao_models.g.dart @@ -0,0 +1,154 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'cacao_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$CacaoRequestPayloadImpl _$$CacaoRequestPayloadImplFromJson( + Map json) => + _$CacaoRequestPayloadImpl( + domain: json['domain'] as String, + aud: json['aud'] as String, + version: json['version'] as String, + nonce: json['nonce'] as String, + iat: json['iat'] as String, + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + ); + +Map _$$CacaoRequestPayloadImplToJson( + _$CacaoRequestPayloadImpl instance) { + final val = { + 'domain': instance.domain, + 'aud': instance.aud, + 'version': instance.version, + 'nonce': instance.nonce, + 'iat': instance.iat, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('nbf', instance.nbf); + writeNotNull('exp', instance.exp); + writeNotNull('statement', instance.statement); + writeNotNull('requestId', instance.requestId); + writeNotNull('resources', instance.resources); + return val; +} + +_$CacaoPayloadImpl _$$CacaoPayloadImplFromJson(Map json) => + _$CacaoPayloadImpl( + iss: json['iss'] as String, + domain: json['domain'] as String, + aud: json['aud'] as String, + version: json['version'] as String, + nonce: json['nonce'] as String, + iat: json['iat'] as String, + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + ); + +Map _$$CacaoPayloadImplToJson(_$CacaoPayloadImpl instance) { + final val = { + 'iss': instance.iss, + 'domain': instance.domain, + 'aud': instance.aud, + 'version': instance.version, + 'nonce': instance.nonce, + 'iat': instance.iat, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('nbf', instance.nbf); + writeNotNull('exp', instance.exp); + writeNotNull('statement', instance.statement); + writeNotNull('requestId', instance.requestId); + writeNotNull('resources', instance.resources); + return val; +} + +_$CacaoHeaderImpl _$$CacaoHeaderImplFromJson(Map json) => + _$CacaoHeaderImpl( + t: json['t'] as String? ?? 'eip4361', + ); + +Map _$$CacaoHeaderImplToJson(_$CacaoHeaderImpl instance) => + { + 't': instance.t, + }; + +_$CacaoSignatureImpl _$$CacaoSignatureImplFromJson(Map json) => + _$CacaoSignatureImpl( + t: json['t'] as String, + s: json['s'] as String, + m: json['m'] as String?, + ); + +Map _$$CacaoSignatureImplToJson( + _$CacaoSignatureImpl instance) { + final val = { + 't': instance.t, + 's': instance.s, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('m', instance.m); + return val; +} + +_$CacaoImpl _$$CacaoImplFromJson(Map json) => _$CacaoImpl( + h: CacaoHeader.fromJson(json['h'] as Map), + p: CacaoPayload.fromJson(json['p'] as Map), + s: CacaoSignature.fromJson(json['s'] as Map), + ); + +Map _$$CacaoImplToJson(_$CacaoImpl instance) => + { + 'h': instance.h.toJson(), + 'p': instance.p.toJson(), + 's': instance.s.toJson(), + }; + +_$StoredCacaoImpl _$$StoredCacaoImplFromJson(Map json) => + _$StoredCacaoImpl( + id: (json['id'] as num).toInt(), + pairingTopic: json['pairingTopic'] as String, + h: CacaoHeader.fromJson(json['h'] as Map), + p: CacaoPayload.fromJson(json['p'] as Map), + s: CacaoSignature.fromJson(json['s'] as Map), + ); + +Map _$$StoredCacaoImplToJson(_$StoredCacaoImpl instance) => + { + 'id': instance.id, + 'pairingTopic': instance.pairingTopic, + 'h': instance.h.toJson(), + 'p': instance.p.toJson(), + 's': instance.s.toJson(), + }; diff --git a/packages/reown_sign/lib/models/common_auth_models.freezed.dart b/packages/reown_sign/lib/models/common_auth_models.freezed.dart new file mode 100644 index 0000000..d362c91 --- /dev/null +++ b/packages/reown_sign/lib/models/common_auth_models.freezed.dart @@ -0,0 +1,1633 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'common_auth_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +AuthPublicKey _$AuthPublicKeyFromJson(Map json) { + return _AuthPublicKey.fromJson(json); +} + +/// @nodoc +mixin _$AuthPublicKey { + String get publicKey => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AuthPublicKeyCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthPublicKeyCopyWith<$Res> { + factory $AuthPublicKeyCopyWith( + AuthPublicKey value, $Res Function(AuthPublicKey) then) = + _$AuthPublicKeyCopyWithImpl<$Res, AuthPublicKey>; + @useResult + $Res call({String publicKey}); +} + +/// @nodoc +class _$AuthPublicKeyCopyWithImpl<$Res, $Val extends AuthPublicKey> + implements $AuthPublicKeyCopyWith<$Res> { + _$AuthPublicKeyCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? publicKey = null, + }) { + return _then(_value.copyWith( + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AuthPublicKeyImplCopyWith<$Res> + implements $AuthPublicKeyCopyWith<$Res> { + factory _$$AuthPublicKeyImplCopyWith( + _$AuthPublicKeyImpl value, $Res Function(_$AuthPublicKeyImpl) then) = + __$$AuthPublicKeyImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String publicKey}); +} + +/// @nodoc +class __$$AuthPublicKeyImplCopyWithImpl<$Res> + extends _$AuthPublicKeyCopyWithImpl<$Res, _$AuthPublicKeyImpl> + implements _$$AuthPublicKeyImplCopyWith<$Res> { + __$$AuthPublicKeyImplCopyWithImpl( + _$AuthPublicKeyImpl _value, $Res Function(_$AuthPublicKeyImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? publicKey = null, + }) { + return _then(_$AuthPublicKeyImpl( + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$AuthPublicKeyImpl implements _AuthPublicKey { + const _$AuthPublicKeyImpl({required this.publicKey}); + + factory _$AuthPublicKeyImpl.fromJson(Map json) => + _$$AuthPublicKeyImplFromJson(json); + + @override + final String publicKey; + + @override + String toString() { + return 'AuthPublicKey(publicKey: $publicKey)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthPublicKeyImpl && + (identical(other.publicKey, publicKey) || + other.publicKey == publicKey)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, publicKey); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AuthPublicKeyImplCopyWith<_$AuthPublicKeyImpl> get copyWith => + __$$AuthPublicKeyImplCopyWithImpl<_$AuthPublicKeyImpl>(this, _$identity); + + @override + Map toJson() { + return _$$AuthPublicKeyImplToJson( + this, + ); + } +} + +abstract class _AuthPublicKey implements AuthPublicKey { + const factory _AuthPublicKey({required final String publicKey}) = + _$AuthPublicKeyImpl; + + factory _AuthPublicKey.fromJson(Map json) = + _$AuthPublicKeyImpl.fromJson; + + @override + String get publicKey; + @override + @JsonKey(ignore: true) + _$$AuthPublicKeyImplCopyWith<_$AuthPublicKeyImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CacaoRequestPayload _$CacaoRequestPayloadFromJson(Map json) { + return _CacaoRequestPayload.fromJson(json); +} + +/// @nodoc +mixin _$CacaoRequestPayload { + String get domain => throw _privateConstructorUsedError; + String get aud => throw _privateConstructorUsedError; + String get version => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + String get iat => throw _privateConstructorUsedError; + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoRequestPayloadCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoRequestPayloadCopyWith<$Res> { + factory $CacaoRequestPayloadCopyWith( + CacaoRequestPayload value, $Res Function(CacaoRequestPayload) then) = + _$CacaoRequestPayloadCopyWithImpl<$Res, CacaoRequestPayload>; + @useResult + $Res call( + {String domain, + String aud, + String version, + String nonce, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class _$CacaoRequestPayloadCopyWithImpl<$Res, $Val extends CacaoRequestPayload> + implements $CacaoRequestPayloadCopyWith<$Res> { + _$CacaoRequestPayloadCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? domain = null, + Object? aud = null, + Object? version = null, + Object? nonce = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_value.copyWith( + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CacaoRequestPayloadImplCopyWith<$Res> + implements $CacaoRequestPayloadCopyWith<$Res> { + factory _$$CacaoRequestPayloadImplCopyWith(_$CacaoRequestPayloadImpl value, + $Res Function(_$CacaoRequestPayloadImpl) then) = + __$$CacaoRequestPayloadImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String domain, + String aud, + String version, + String nonce, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class __$$CacaoRequestPayloadImplCopyWithImpl<$Res> + extends _$CacaoRequestPayloadCopyWithImpl<$Res, _$CacaoRequestPayloadImpl> + implements _$$CacaoRequestPayloadImplCopyWith<$Res> { + __$$CacaoRequestPayloadImplCopyWithImpl(_$CacaoRequestPayloadImpl _value, + $Res Function(_$CacaoRequestPayloadImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? domain = null, + Object? aud = null, + Object? version = null, + Object? nonce = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_$CacaoRequestPayloadImpl( + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoRequestPayloadImpl implements _CacaoRequestPayload { + const _$CacaoRequestPayloadImpl( + {required this.domain, + required this.aud, + required this.version, + required this.nonce, + required this.iat, + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources}) + : _resources = resources; + + factory _$CacaoRequestPayloadImpl.fromJson(Map json) => + _$$CacaoRequestPayloadImplFromJson(json); + + @override + final String domain; + @override + final String aud; + @override + final String version; + @override + final String nonce; + @override + final String iat; + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'CacaoRequestPayload(domain: $domain, aud: $aud, version: $version, nonce: $nonce, iat: $iat, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoRequestPayloadImpl && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.aud, aud) || other.aud == aud) && + (identical(other.version, version) || other.version == version) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.iat, iat) || other.iat == iat) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + domain, + aud, + version, + nonce, + iat, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoRequestPayloadImplCopyWith<_$CacaoRequestPayloadImpl> get copyWith => + __$$CacaoRequestPayloadImplCopyWithImpl<_$CacaoRequestPayloadImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$CacaoRequestPayloadImplToJson( + this, + ); + } +} + +abstract class _CacaoRequestPayload implements CacaoRequestPayload { + const factory _CacaoRequestPayload( + {required final String domain, + required final String aud, + required final String version, + required final String nonce, + required final String iat, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources}) = _$CacaoRequestPayloadImpl; + + factory _CacaoRequestPayload.fromJson(Map json) = + _$CacaoRequestPayloadImpl.fromJson; + + @override + String get domain; + @override + String get aud; + @override + String get version; + @override + String get nonce; + @override + String get iat; + @override + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + @JsonKey(ignore: true) + _$$CacaoRequestPayloadImplCopyWith<_$CacaoRequestPayloadImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CacaoPayload _$CacaoPayloadFromJson(Map json) { + return _CacaoPayload.fromJson(json); +} + +/// @nodoc +mixin _$CacaoPayload { + String get iss => throw _privateConstructorUsedError; + String get domain => throw _privateConstructorUsedError; + String get aud => throw _privateConstructorUsedError; + String get version => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + String get iat => throw _privateConstructorUsedError; + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoPayloadCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoPayloadCopyWith<$Res> { + factory $CacaoPayloadCopyWith( + CacaoPayload value, $Res Function(CacaoPayload) then) = + _$CacaoPayloadCopyWithImpl<$Res, CacaoPayload>; + @useResult + $Res call( + {String iss, + String domain, + String aud, + String version, + String nonce, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class _$CacaoPayloadCopyWithImpl<$Res, $Val extends CacaoPayload> + implements $CacaoPayloadCopyWith<$Res> { + _$CacaoPayloadCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? iss = null, + Object? domain = null, + Object? aud = null, + Object? version = null, + Object? nonce = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_value.copyWith( + iss: null == iss + ? _value.iss + : iss // ignore: cast_nullable_to_non_nullable + as String, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CacaoPayloadImplCopyWith<$Res> + implements $CacaoPayloadCopyWith<$Res> { + factory _$$CacaoPayloadImplCopyWith( + _$CacaoPayloadImpl value, $Res Function(_$CacaoPayloadImpl) then) = + __$$CacaoPayloadImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String iss, + String domain, + String aud, + String version, + String nonce, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class __$$CacaoPayloadImplCopyWithImpl<$Res> + extends _$CacaoPayloadCopyWithImpl<$Res, _$CacaoPayloadImpl> + implements _$$CacaoPayloadImplCopyWith<$Res> { + __$$CacaoPayloadImplCopyWithImpl( + _$CacaoPayloadImpl _value, $Res Function(_$CacaoPayloadImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? iss = null, + Object? domain = null, + Object? aud = null, + Object? version = null, + Object? nonce = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_$CacaoPayloadImpl( + iss: null == iss + ? _value.iss + : iss // ignore: cast_nullable_to_non_nullable + as String, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoPayloadImpl implements _CacaoPayload { + const _$CacaoPayloadImpl( + {required this.iss, + required this.domain, + required this.aud, + required this.version, + required this.nonce, + required this.iat, + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources}) + : _resources = resources; + + factory _$CacaoPayloadImpl.fromJson(Map json) => + _$$CacaoPayloadImplFromJson(json); + + @override + final String iss; + @override + final String domain; + @override + final String aud; + @override + final String version; + @override + final String nonce; + @override + final String iat; + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'CacaoPayload(iss: $iss, domain: $domain, aud: $aud, version: $version, nonce: $nonce, iat: $iat, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoPayloadImpl && + (identical(other.iss, iss) || other.iss == iss) && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.aud, aud) || other.aud == aud) && + (identical(other.version, version) || other.version == version) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.iat, iat) || other.iat == iat) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + iss, + domain, + aud, + version, + nonce, + iat, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoPayloadImplCopyWith<_$CacaoPayloadImpl> get copyWith => + __$$CacaoPayloadImplCopyWithImpl<_$CacaoPayloadImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CacaoPayloadImplToJson( + this, + ); + } +} + +abstract class _CacaoPayload implements CacaoPayload { + const factory _CacaoPayload( + {required final String iss, + required final String domain, + required final String aud, + required final String version, + required final String nonce, + required final String iat, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources}) = _$CacaoPayloadImpl; + + factory _CacaoPayload.fromJson(Map json) = + _$CacaoPayloadImpl.fromJson; + + @override + String get iss; + @override + String get domain; + @override + String get aud; + @override + String get version; + @override + String get nonce; + @override + String get iat; + @override + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + @JsonKey(ignore: true) + _$$CacaoPayloadImplCopyWith<_$CacaoPayloadImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CacaoHeader _$CacaoHeaderFromJson(Map json) { + return _CacaoHeader.fromJson(json); +} + +/// @nodoc +mixin _$CacaoHeader { + String get t => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoHeaderCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoHeaderCopyWith<$Res> { + factory $CacaoHeaderCopyWith( + CacaoHeader value, $Res Function(CacaoHeader) then) = + _$CacaoHeaderCopyWithImpl<$Res, CacaoHeader>; + @useResult + $Res call({String t}); +} + +/// @nodoc +class _$CacaoHeaderCopyWithImpl<$Res, $Val extends CacaoHeader> + implements $CacaoHeaderCopyWith<$Res> { + _$CacaoHeaderCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? t = null, + }) { + return _then(_value.copyWith( + t: null == t + ? _value.t + : t // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CacaoHeaderImplCopyWith<$Res> + implements $CacaoHeaderCopyWith<$Res> { + factory _$$CacaoHeaderImplCopyWith( + _$CacaoHeaderImpl value, $Res Function(_$CacaoHeaderImpl) then) = + __$$CacaoHeaderImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String t}); +} + +/// @nodoc +class __$$CacaoHeaderImplCopyWithImpl<$Res> + extends _$CacaoHeaderCopyWithImpl<$Res, _$CacaoHeaderImpl> + implements _$$CacaoHeaderImplCopyWith<$Res> { + __$$CacaoHeaderImplCopyWithImpl( + _$CacaoHeaderImpl _value, $Res Function(_$CacaoHeaderImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? t = null, + }) { + return _then(_$CacaoHeaderImpl( + t: null == t + ? _value.t + : t // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoHeaderImpl implements _CacaoHeader { + const _$CacaoHeaderImpl({this.t = 'eip4361'}); + + factory _$CacaoHeaderImpl.fromJson(Map json) => + _$$CacaoHeaderImplFromJson(json); + + @override + @JsonKey() + final String t; + + @override + String toString() { + return 'CacaoHeader(t: $t)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoHeaderImpl && + (identical(other.t, t) || other.t == t)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, t); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoHeaderImplCopyWith<_$CacaoHeaderImpl> get copyWith => + __$$CacaoHeaderImplCopyWithImpl<_$CacaoHeaderImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CacaoHeaderImplToJson( + this, + ); + } +} + +abstract class _CacaoHeader implements CacaoHeader { + const factory _CacaoHeader({final String t}) = _$CacaoHeaderImpl; + + factory _CacaoHeader.fromJson(Map json) = + _$CacaoHeaderImpl.fromJson; + + @override + String get t; + @override + @JsonKey(ignore: true) + _$$CacaoHeaderImplCopyWith<_$CacaoHeaderImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CacaoSignature _$CacaoSignatureFromJson(Map json) { + return _CacaoSignature.fromJson(json); +} + +/// @nodoc +mixin _$CacaoSignature { + String get t => throw _privateConstructorUsedError; + String get s => throw _privateConstructorUsedError; + String? get m => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoSignatureCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoSignatureCopyWith<$Res> { + factory $CacaoSignatureCopyWith( + CacaoSignature value, $Res Function(CacaoSignature) then) = + _$CacaoSignatureCopyWithImpl<$Res, CacaoSignature>; + @useResult + $Res call({String t, String s, String? m}); +} + +/// @nodoc +class _$CacaoSignatureCopyWithImpl<$Res, $Val extends CacaoSignature> + implements $CacaoSignatureCopyWith<$Res> { + _$CacaoSignatureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? t = null, + Object? s = null, + Object? m = freezed, + }) { + return _then(_value.copyWith( + t: null == t + ? _value.t + : t // ignore: cast_nullable_to_non_nullable + as String, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as String, + m: freezed == m + ? _value.m + : m // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CacaoSignatureImplCopyWith<$Res> + implements $CacaoSignatureCopyWith<$Res> { + factory _$$CacaoSignatureImplCopyWith(_$CacaoSignatureImpl value, + $Res Function(_$CacaoSignatureImpl) then) = + __$$CacaoSignatureImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String t, String s, String? m}); +} + +/// @nodoc +class __$$CacaoSignatureImplCopyWithImpl<$Res> + extends _$CacaoSignatureCopyWithImpl<$Res, _$CacaoSignatureImpl> + implements _$$CacaoSignatureImplCopyWith<$Res> { + __$$CacaoSignatureImplCopyWithImpl( + _$CacaoSignatureImpl _value, $Res Function(_$CacaoSignatureImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? t = null, + Object? s = null, + Object? m = freezed, + }) { + return _then(_$CacaoSignatureImpl( + t: null == t + ? _value.t + : t // ignore: cast_nullable_to_non_nullable + as String, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as String, + m: freezed == m + ? _value.m + : m // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoSignatureImpl implements _CacaoSignature { + const _$CacaoSignatureImpl({required this.t, required this.s, this.m}); + + factory _$CacaoSignatureImpl.fromJson(Map json) => + _$$CacaoSignatureImplFromJson(json); + + @override + final String t; + @override + final String s; + @override + final String? m; + + @override + String toString() { + return 'CacaoSignature(t: $t, s: $s, m: $m)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoSignatureImpl && + (identical(other.t, t) || other.t == t) && + (identical(other.s, s) || other.s == s) && + (identical(other.m, m) || other.m == m)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, t, s, m); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoSignatureImplCopyWith<_$CacaoSignatureImpl> get copyWith => + __$$CacaoSignatureImplCopyWithImpl<_$CacaoSignatureImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$CacaoSignatureImplToJson( + this, + ); + } +} + +abstract class _CacaoSignature implements CacaoSignature { + const factory _CacaoSignature( + {required final String t, + required final String s, + final String? m}) = _$CacaoSignatureImpl; + + factory _CacaoSignature.fromJson(Map json) = + _$CacaoSignatureImpl.fromJson; + + @override + String get t; + @override + String get s; + @override + String? get m; + @override + @JsonKey(ignore: true) + _$$CacaoSignatureImplCopyWith<_$CacaoSignatureImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Cacao _$CacaoFromJson(Map json) { + return _Cacao.fromJson(json); +} + +/// @nodoc +mixin _$Cacao { + CacaoHeader get h => throw _privateConstructorUsedError; + CacaoPayload get p => throw _privateConstructorUsedError; + CacaoSignature get s => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CacaoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CacaoCopyWith<$Res> { + factory $CacaoCopyWith(Cacao value, $Res Function(Cacao) then) = + _$CacaoCopyWithImpl<$Res, Cacao>; + @useResult + $Res call({CacaoHeader h, CacaoPayload p, CacaoSignature s}); + + $CacaoHeaderCopyWith<$Res> get h; + $CacaoPayloadCopyWith<$Res> get p; + $CacaoSignatureCopyWith<$Res> get s; +} + +/// @nodoc +class _$CacaoCopyWithImpl<$Res, $Val extends Cacao> + implements $CacaoCopyWith<$Res> { + _$CacaoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? h = null, + Object? p = null, + Object? s = null, + }) { + return _then(_value.copyWith( + h: null == h + ? _value.h + : h // ignore: cast_nullable_to_non_nullable + as CacaoHeader, + p: null == p + ? _value.p + : p // ignore: cast_nullable_to_non_nullable + as CacaoPayload, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as CacaoSignature, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $CacaoHeaderCopyWith<$Res> get h { + return $CacaoHeaderCopyWith<$Res>(_value.h, (value) { + return _then(_value.copyWith(h: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoPayloadCopyWith<$Res> get p { + return $CacaoPayloadCopyWith<$Res>(_value.p, (value) { + return _then(_value.copyWith(p: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoSignatureCopyWith<$Res> get s { + return $CacaoSignatureCopyWith<$Res>(_value.s, (value) { + return _then(_value.copyWith(s: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$CacaoImplCopyWith<$Res> implements $CacaoCopyWith<$Res> { + factory _$$CacaoImplCopyWith( + _$CacaoImpl value, $Res Function(_$CacaoImpl) then) = + __$$CacaoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({CacaoHeader h, CacaoPayload p, CacaoSignature s}); + + @override + $CacaoHeaderCopyWith<$Res> get h; + @override + $CacaoPayloadCopyWith<$Res> get p; + @override + $CacaoSignatureCopyWith<$Res> get s; +} + +/// @nodoc +class __$$CacaoImplCopyWithImpl<$Res> + extends _$CacaoCopyWithImpl<$Res, _$CacaoImpl> + implements _$$CacaoImplCopyWith<$Res> { + __$$CacaoImplCopyWithImpl( + _$CacaoImpl _value, $Res Function(_$CacaoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? h = null, + Object? p = null, + Object? s = null, + }) { + return _then(_$CacaoImpl( + h: null == h + ? _value.h + : h // ignore: cast_nullable_to_non_nullable + as CacaoHeader, + p: null == p + ? _value.p + : p // ignore: cast_nullable_to_non_nullable + as CacaoPayload, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as CacaoSignature, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$CacaoImpl implements _Cacao { + const _$CacaoImpl({required this.h, required this.p, required this.s}); + + factory _$CacaoImpl.fromJson(Map json) => + _$$CacaoImplFromJson(json); + + @override + final CacaoHeader h; + @override + final CacaoPayload p; + @override + final CacaoSignature s; + + @override + String toString() { + return 'Cacao(h: $h, p: $p, s: $s)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CacaoImpl && + (identical(other.h, h) || other.h == h) && + (identical(other.p, p) || other.p == p) && + (identical(other.s, s) || other.s == s)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, h, p, s); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CacaoImplCopyWith<_$CacaoImpl> get copyWith => + __$$CacaoImplCopyWithImpl<_$CacaoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CacaoImplToJson( + this, + ); + } +} + +abstract class _Cacao implements Cacao { + const factory _Cacao( + {required final CacaoHeader h, + required final CacaoPayload p, + required final CacaoSignature s}) = _$CacaoImpl; + + factory _Cacao.fromJson(Map json) = _$CacaoImpl.fromJson; + + @override + CacaoHeader get h; + @override + CacaoPayload get p; + @override + CacaoSignature get s; + @override + @JsonKey(ignore: true) + _$$CacaoImplCopyWith<_$CacaoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +StoredCacao _$StoredCacaoFromJson(Map json) { + return _StoredCacao.fromJson(json); +} + +/// @nodoc +mixin _$StoredCacao { + int get id => throw _privateConstructorUsedError; + String get pairingTopic => throw _privateConstructorUsedError; + CacaoHeader get h => throw _privateConstructorUsedError; + CacaoPayload get p => throw _privateConstructorUsedError; + CacaoSignature get s => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $StoredCacaoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $StoredCacaoCopyWith<$Res> { + factory $StoredCacaoCopyWith( + StoredCacao value, $Res Function(StoredCacao) then) = + _$StoredCacaoCopyWithImpl<$Res, StoredCacao>; + @useResult + $Res call( + {int id, + String pairingTopic, + CacaoHeader h, + CacaoPayload p, + CacaoSignature s}); + + $CacaoHeaderCopyWith<$Res> get h; + $CacaoPayloadCopyWith<$Res> get p; + $CacaoSignatureCopyWith<$Res> get s; +} + +/// @nodoc +class _$StoredCacaoCopyWithImpl<$Res, $Val extends StoredCacao> + implements $StoredCacaoCopyWith<$Res> { + _$StoredCacaoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? pairingTopic = null, + Object? h = null, + Object? p = null, + Object? s = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + h: null == h + ? _value.h + : h // ignore: cast_nullable_to_non_nullable + as CacaoHeader, + p: null == p + ? _value.p + : p // ignore: cast_nullable_to_non_nullable + as CacaoPayload, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as CacaoSignature, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $CacaoHeaderCopyWith<$Res> get h { + return $CacaoHeaderCopyWith<$Res>(_value.h, (value) { + return _then(_value.copyWith(h: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoPayloadCopyWith<$Res> get p { + return $CacaoPayloadCopyWith<$Res>(_value.p, (value) { + return _then(_value.copyWith(p: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoSignatureCopyWith<$Res> get s { + return $CacaoSignatureCopyWith<$Res>(_value.s, (value) { + return _then(_value.copyWith(s: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$StoredCacaoImplCopyWith<$Res> + implements $StoredCacaoCopyWith<$Res> { + factory _$$StoredCacaoImplCopyWith( + _$StoredCacaoImpl value, $Res Function(_$StoredCacaoImpl) then) = + __$$StoredCacaoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String pairingTopic, + CacaoHeader h, + CacaoPayload p, + CacaoSignature s}); + + @override + $CacaoHeaderCopyWith<$Res> get h; + @override + $CacaoPayloadCopyWith<$Res> get p; + @override + $CacaoSignatureCopyWith<$Res> get s; +} + +/// @nodoc +class __$$StoredCacaoImplCopyWithImpl<$Res> + extends _$StoredCacaoCopyWithImpl<$Res, _$StoredCacaoImpl> + implements _$$StoredCacaoImplCopyWith<$Res> { + __$$StoredCacaoImplCopyWithImpl( + _$StoredCacaoImpl _value, $Res Function(_$StoredCacaoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? pairingTopic = null, + Object? h = null, + Object? p = null, + Object? s = null, + }) { + return _then(_$StoredCacaoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + h: null == h + ? _value.h + : h // ignore: cast_nullable_to_non_nullable + as CacaoHeader, + p: null == p + ? _value.p + : p // ignore: cast_nullable_to_non_nullable + as CacaoPayload, + s: null == s + ? _value.s + : s // ignore: cast_nullable_to_non_nullable + as CacaoSignature, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$StoredCacaoImpl implements _StoredCacao { + const _$StoredCacaoImpl( + {required this.id, + required this.pairingTopic, + required this.h, + required this.p, + required this.s}); + + factory _$StoredCacaoImpl.fromJson(Map json) => + _$$StoredCacaoImplFromJson(json); + + @override + final int id; + @override + final String pairingTopic; + @override + final CacaoHeader h; + @override + final CacaoPayload p; + @override + final CacaoSignature s; + + @override + String toString() { + return 'StoredCacao(id: $id, pairingTopic: $pairingTopic, h: $h, p: $p, s: $s)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$StoredCacaoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.pairingTopic, pairingTopic) || + other.pairingTopic == pairingTopic) && + (identical(other.h, h) || other.h == h) && + (identical(other.p, p) || other.p == p) && + (identical(other.s, s) || other.s == s)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, pairingTopic, h, p, s); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$StoredCacaoImplCopyWith<_$StoredCacaoImpl> get copyWith => + __$$StoredCacaoImplCopyWithImpl<_$StoredCacaoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$StoredCacaoImplToJson( + this, + ); + } +} + +abstract class _StoredCacao implements StoredCacao { + const factory _StoredCacao( + {required final int id, + required final String pairingTopic, + required final CacaoHeader h, + required final CacaoPayload p, + required final CacaoSignature s}) = _$StoredCacaoImpl; + + factory _StoredCacao.fromJson(Map json) = + _$StoredCacaoImpl.fromJson; + + @override + int get id; + @override + String get pairingTopic; + @override + CacaoHeader get h; + @override + CacaoPayload get p; + @override + CacaoSignature get s; + @override + @JsonKey(ignore: true) + _$$StoredCacaoImplCopyWith<_$StoredCacaoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_sign/lib/models/common_auth_models.g.dart b/packages/reown_sign/lib/models/common_auth_models.g.dart new file mode 100644 index 0000000..02d49a5 --- /dev/null +++ b/packages/reown_sign/lib/models/common_auth_models.g.dart @@ -0,0 +1,164 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'common_auth_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AuthPublicKeyImpl _$$AuthPublicKeyImplFromJson(Map json) => + _$AuthPublicKeyImpl( + publicKey: json['publicKey'] as String, + ); + +Map _$$AuthPublicKeyImplToJson(_$AuthPublicKeyImpl instance) => + { + 'publicKey': instance.publicKey, + }; + +_$CacaoRequestPayloadImpl _$$CacaoRequestPayloadImplFromJson( + Map json) => + _$CacaoRequestPayloadImpl( + domain: json['domain'] as String, + aud: json['aud'] as String, + version: json['version'] as String, + nonce: json['nonce'] as String, + iat: json['iat'] as String, + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + ); + +Map _$$CacaoRequestPayloadImplToJson( + _$CacaoRequestPayloadImpl instance) { + final val = { + 'domain': instance.domain, + 'aud': instance.aud, + 'version': instance.version, + 'nonce': instance.nonce, + 'iat': instance.iat, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('nbf', instance.nbf); + writeNotNull('exp', instance.exp); + writeNotNull('statement', instance.statement); + writeNotNull('requestId', instance.requestId); + writeNotNull('resources', instance.resources); + return val; +} + +_$CacaoPayloadImpl _$$CacaoPayloadImplFromJson(Map json) => + _$CacaoPayloadImpl( + iss: json['iss'] as String, + domain: json['domain'] as String, + aud: json['aud'] as String, + version: json['version'] as String, + nonce: json['nonce'] as String, + iat: json['iat'] as String, + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + ); + +Map _$$CacaoPayloadImplToJson(_$CacaoPayloadImpl instance) { + final val = { + 'iss': instance.iss, + 'domain': instance.domain, + 'aud': instance.aud, + 'version': instance.version, + 'nonce': instance.nonce, + 'iat': instance.iat, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('nbf', instance.nbf); + writeNotNull('exp', instance.exp); + writeNotNull('statement', instance.statement); + writeNotNull('requestId', instance.requestId); + writeNotNull('resources', instance.resources); + return val; +} + +_$CacaoHeaderImpl _$$CacaoHeaderImplFromJson(Map json) => + _$CacaoHeaderImpl( + t: json['t'] as String? ?? 'eip4361', + ); + +Map _$$CacaoHeaderImplToJson(_$CacaoHeaderImpl instance) => + { + 't': instance.t, + }; + +_$CacaoSignatureImpl _$$CacaoSignatureImplFromJson(Map json) => + _$CacaoSignatureImpl( + t: json['t'] as String, + s: json['s'] as String, + m: json['m'] as String?, + ); + +Map _$$CacaoSignatureImplToJson( + _$CacaoSignatureImpl instance) { + final val = { + 't': instance.t, + 's': instance.s, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('m', instance.m); + return val; +} + +_$CacaoImpl _$$CacaoImplFromJson(Map json) => _$CacaoImpl( + h: CacaoHeader.fromJson(json['h'] as Map), + p: CacaoPayload.fromJson(json['p'] as Map), + s: CacaoSignature.fromJson(json['s'] as Map), + ); + +Map _$$CacaoImplToJson(_$CacaoImpl instance) => + { + 'h': instance.h.toJson(), + 'p': instance.p.toJson(), + 's': instance.s.toJson(), + }; + +_$StoredCacaoImpl _$$StoredCacaoImplFromJson(Map json) => + _$StoredCacaoImpl( + id: (json['id'] as num).toInt(), + pairingTopic: json['pairingTopic'] as String, + h: CacaoHeader.fromJson(json['h'] as Map), + p: CacaoPayload.fromJson(json['p'] as Map), + s: CacaoSignature.fromJson(json['s'] as Map), + ); + +Map _$$StoredCacaoImplToJson(_$StoredCacaoImpl instance) => + { + 'id': instance.id, + 'pairingTopic': instance.pairingTopic, + 'h': instance.h.toJson(), + 'p': instance.p.toJson(), + 's': instance.s.toJson(), + }; diff --git a/packages/reown_sign/lib/models/json_rpc_models.dart b/packages/reown_sign/lib/models/json_rpc_models.dart new file mode 100644 index 0000000..7ae5efa --- /dev/null +++ b/packages/reown_sign/lib/models/json_rpc_models.dart @@ -0,0 +1,196 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/models/cacao_models.dart'; +import 'package:reown_sign/models/session_auth_models.dart'; +import 'package:reown_sign/models/proposal_models.dart'; +import 'package:reown_sign/models/session_models.dart'; + +part 'json_rpc_models.g.dart'; +part 'json_rpc_models.freezed.dart'; + +@freezed +class WcPairingDeleteRequest with _$WcPairingDeleteRequest { + @JsonSerializable() + const factory WcPairingDeleteRequest({ + required int code, + required String message, + }) = _WcPairingDeleteRequest; + + factory WcPairingDeleteRequest.fromJson(Map json) => + _$WcPairingDeleteRequestFromJson(json); +} + +@freezed +class WcPairingPingRequest with _$WcPairingPingRequest { + @JsonSerializable() + const factory WcPairingPingRequest({ + required Map data, + }) = _WcPairingPingRequest; + + factory WcPairingPingRequest.fromJson(Map json) => + _$WcPairingPingRequestFromJson(json); +} + +@freezed +class WcSessionProposeRequest with _$WcSessionProposeRequest { + @JsonSerializable(includeIfNull: false) + const factory WcSessionProposeRequest({ + required List relays, + required Map requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + required ConnectionMetadata proposer, + }) = _WcSessionProposeRequest; + + factory WcSessionProposeRequest.fromJson(Map json) => + _$WcSessionProposeRequestFromJson(json); +} + +@freezed +class WcSessionProposeResponse with _$WcSessionProposeResponse { + @JsonSerializable() + const factory WcSessionProposeResponse({ + required Relay relay, + required String responderPublicKey, + }) = _WcSessionProposeResponse; + + factory WcSessionProposeResponse.fromJson(Map json) => + _$WcSessionProposeResponseFromJson(json); +} + +@freezed +class WcSessionSettleRequest with _$WcSessionSettleRequest { + @JsonSerializable(includeIfNull: false) + const factory WcSessionSettleRequest({ + required Relay relay, + required Map namespaces, + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + required int expiry, + required ConnectionMetadata controller, + }) = _WcSessionSettleRequest; + + factory WcSessionSettleRequest.fromJson(Map json) => + _$WcSessionSettleRequestFromJson(json); +} + +@freezed +class WcSessionUpdateRequest with _$WcSessionUpdateRequest { + @JsonSerializable() + const factory WcSessionUpdateRequest({ + required Map namespaces, + }) = _WcSessionUpdateRequest; + + factory WcSessionUpdateRequest.fromJson(Map json) => + _$WcSessionUpdateRequestFromJson(json); +} + +@freezed +class WcSessionExtendRequest with _$WcSessionExtendRequest { + @JsonSerializable(includeIfNull: false) + const factory WcSessionExtendRequest({ + Map? data, + }) = _WcSessionExtendRequest; + + factory WcSessionExtendRequest.fromJson(Map json) => + _$WcSessionExtendRequestFromJson(json); +} + +@freezed +class WcSessionDeleteRequest with _$WcSessionDeleteRequest { + @JsonSerializable(includeIfNull: false) + const factory WcSessionDeleteRequest({ + required int code, + required String message, + String? data, + }) = _WcSessionDeleteRequest; + + factory WcSessionDeleteRequest.fromJson(Map json) => + _$WcSessionDeleteRequestFromJson(json); +} + +@freezed +class WcSessionPingRequest with _$WcSessionPingRequest { + @JsonSerializable(includeIfNull: false) + const factory WcSessionPingRequest({ + Map? data, + }) = _WcSessionPingRequest; + + factory WcSessionPingRequest.fromJson(Map json) => + _$WcSessionPingRequestFromJson(json); +} + +@freezed +class WcSessionRequestRequest with _$WcSessionRequestRequest { + @JsonSerializable() + const factory WcSessionRequestRequest({ + required String chainId, + required SessionRequestParams request, + }) = _WcSessionRequestRequest; + + factory WcSessionRequestRequest.fromJson(Map json) => + _$WcSessionRequestRequestFromJson(json); +} + +@freezed +class SessionRequestParams with _$SessionRequestParams { + @JsonSerializable() + const factory SessionRequestParams({ + required String method, + required dynamic params, + }) = _SessionRequestParams; + + factory SessionRequestParams.fromJson(Map json) => + _$SessionRequestParamsFromJson(json); +} + +@freezed +class WcSessionEventRequest with _$WcSessionEventRequest { + @JsonSerializable() + const factory WcSessionEventRequest({ + required String chainId, + required SessionEventParams event, + }) = _WcSessionEventRequest; + + factory WcSessionEventRequest.fromJson(Map json) => + _$WcSessionEventRequestFromJson(json); +} + +@freezed +class SessionEventParams with _$SessionEventParams { + @JsonSerializable() + const factory SessionEventParams({ + required String name, + required dynamic data, + }) = _SessionEventParams; + + factory SessionEventParams.fromJson(Map json) => + _$SessionEventParamsFromJson(json); +} + +@freezed +class WcSessionAuthRequestParams with _$WcSessionAuthRequestParams { + @JsonSerializable() + const factory WcSessionAuthRequestParams({ + required SessionAuthPayload authPayload, + required ConnectionMetadata requester, + required int expiryTimestamp, + }) = _WcSessionAuthRequestParams; + + factory WcSessionAuthRequestParams.fromJson(Map json) => + _$WcSessionAuthRequestParamsFromJson(json); +} + +@freezed +class WcSessionAuthRequestResult with _$WcSessionAuthRequestResult { + @JsonSerializable() + const factory WcSessionAuthRequestResult({ + required List cacaos, + required ConnectionMetadata responder, + }) = _WcSessionAuthRequestResult; + + factory WcSessionAuthRequestResult.fromJson(Map json) => + _$WcSessionAuthRequestResultFromJson(json); +} diff --git a/packages/reown_sign/lib/models/json_rpc_models.freezed.dart b/packages/reown_sign/lib/models/json_rpc_models.freezed.dart new file mode 100644 index 0000000..0f78a8b --- /dev/null +++ b/packages/reown_sign/lib/models/json_rpc_models.freezed.dart @@ -0,0 +1,2821 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'json_rpc_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +WcPairingDeleteRequest _$WcPairingDeleteRequestFromJson( + Map json) { + return _WcPairingDeleteRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcPairingDeleteRequest { + int get code => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcPairingDeleteRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcPairingDeleteRequestCopyWith<$Res> { + factory $WcPairingDeleteRequestCopyWith(WcPairingDeleteRequest value, + $Res Function(WcPairingDeleteRequest) then) = + _$WcPairingDeleteRequestCopyWithImpl<$Res, WcPairingDeleteRequest>; + @useResult + $Res call({int code, String message}); +} + +/// @nodoc +class _$WcPairingDeleteRequestCopyWithImpl<$Res, + $Val extends WcPairingDeleteRequest> + implements $WcPairingDeleteRequestCopyWith<$Res> { + _$WcPairingDeleteRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? message = null, + }) { + return _then(_value.copyWith( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WcPairingDeleteRequestImplCopyWith<$Res> + implements $WcPairingDeleteRequestCopyWith<$Res> { + factory _$$WcPairingDeleteRequestImplCopyWith( + _$WcPairingDeleteRequestImpl value, + $Res Function(_$WcPairingDeleteRequestImpl) then) = + __$$WcPairingDeleteRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int code, String message}); +} + +/// @nodoc +class __$$WcPairingDeleteRequestImplCopyWithImpl<$Res> + extends _$WcPairingDeleteRequestCopyWithImpl<$Res, + _$WcPairingDeleteRequestImpl> + implements _$$WcPairingDeleteRequestImplCopyWith<$Res> { + __$$WcPairingDeleteRequestImplCopyWithImpl( + _$WcPairingDeleteRequestImpl _value, + $Res Function(_$WcPairingDeleteRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? message = null, + }) { + return _then(_$WcPairingDeleteRequestImpl( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$WcPairingDeleteRequestImpl implements _WcPairingDeleteRequest { + const _$WcPairingDeleteRequestImpl( + {required this.code, required this.message}); + + factory _$WcPairingDeleteRequestImpl.fromJson(Map json) => + _$$WcPairingDeleteRequestImplFromJson(json); + + @override + final int code; + @override + final String message; + + @override + String toString() { + return 'WcPairingDeleteRequest(code: $code, message: $message)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcPairingDeleteRequestImpl && + (identical(other.code, code) || other.code == code) && + (identical(other.message, message) || other.message == message)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, code, message); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcPairingDeleteRequestImplCopyWith<_$WcPairingDeleteRequestImpl> + get copyWith => __$$WcPairingDeleteRequestImplCopyWithImpl< + _$WcPairingDeleteRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcPairingDeleteRequestImplToJson( + this, + ); + } +} + +abstract class _WcPairingDeleteRequest implements WcPairingDeleteRequest { + const factory _WcPairingDeleteRequest( + {required final int code, + required final String message}) = _$WcPairingDeleteRequestImpl; + + factory _WcPairingDeleteRequest.fromJson(Map json) = + _$WcPairingDeleteRequestImpl.fromJson; + + @override + int get code; + @override + String get message; + @override + @JsonKey(ignore: true) + _$$WcPairingDeleteRequestImplCopyWith<_$WcPairingDeleteRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcPairingPingRequest _$WcPairingPingRequestFromJson(Map json) { + return _WcPairingPingRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcPairingPingRequest { + Map get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcPairingPingRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcPairingPingRequestCopyWith<$Res> { + factory $WcPairingPingRequestCopyWith(WcPairingPingRequest value, + $Res Function(WcPairingPingRequest) then) = + _$WcPairingPingRequestCopyWithImpl<$Res, WcPairingPingRequest>; + @useResult + $Res call({Map data}); +} + +/// @nodoc +class _$WcPairingPingRequestCopyWithImpl<$Res, + $Val extends WcPairingPingRequest> + implements $WcPairingPingRequestCopyWith<$Res> { + _$WcPairingPingRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = null, + }) { + return _then(_value.copyWith( + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WcPairingPingRequestImplCopyWith<$Res> + implements $WcPairingPingRequestCopyWith<$Res> { + factory _$$WcPairingPingRequestImplCopyWith(_$WcPairingPingRequestImpl value, + $Res Function(_$WcPairingPingRequestImpl) then) = + __$$WcPairingPingRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Map data}); +} + +/// @nodoc +class __$$WcPairingPingRequestImplCopyWithImpl<$Res> + extends _$WcPairingPingRequestCopyWithImpl<$Res, _$WcPairingPingRequestImpl> + implements _$$WcPairingPingRequestImplCopyWith<$Res> { + __$$WcPairingPingRequestImplCopyWithImpl(_$WcPairingPingRequestImpl _value, + $Res Function(_$WcPairingPingRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = null, + }) { + return _then(_$WcPairingPingRequestImpl( + data: null == data + ? _value._data + : data // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$WcPairingPingRequestImpl implements _WcPairingPingRequest { + const _$WcPairingPingRequestImpl({required final Map data}) + : _data = data; + + factory _$WcPairingPingRequestImpl.fromJson(Map json) => + _$$WcPairingPingRequestImplFromJson(json); + + final Map _data; + @override + Map get data { + if (_data is EqualUnmodifiableMapView) return _data; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_data); + } + + @override + String toString() { + return 'WcPairingPingRequest(data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcPairingPingRequestImpl && + const DeepCollectionEquality().equals(other._data, _data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_data)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcPairingPingRequestImplCopyWith<_$WcPairingPingRequestImpl> + get copyWith => + __$$WcPairingPingRequestImplCopyWithImpl<_$WcPairingPingRequestImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$WcPairingPingRequestImplToJson( + this, + ); + } +} + +abstract class _WcPairingPingRequest implements WcPairingPingRequest { + const factory _WcPairingPingRequest( + {required final Map data}) = _$WcPairingPingRequestImpl; + + factory _WcPairingPingRequest.fromJson(Map json) = + _$WcPairingPingRequestImpl.fromJson; + + @override + Map get data; + @override + @JsonKey(ignore: true) + _$$WcPairingPingRequestImplCopyWith<_$WcPairingPingRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionProposeRequest _$WcSessionProposeRequestFromJson( + Map json) { + return _WcSessionProposeRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionProposeRequest { + List get relays => throw _privateConstructorUsedError; + Map get requiredNamespaces => + throw _privateConstructorUsedError; + Map? get optionalNamespaces => + throw _privateConstructorUsedError; + Map? get sessionProperties => + throw _privateConstructorUsedError; + ConnectionMetadata get proposer => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionProposeRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionProposeRequestCopyWith<$Res> { + factory $WcSessionProposeRequestCopyWith(WcSessionProposeRequest value, + $Res Function(WcSessionProposeRequest) then) = + _$WcSessionProposeRequestCopyWithImpl<$Res, WcSessionProposeRequest>; + @useResult + $Res call( + {List relays, + Map requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + ConnectionMetadata proposer}); + + $ConnectionMetadataCopyWith<$Res> get proposer; +} + +/// @nodoc +class _$WcSessionProposeRequestCopyWithImpl<$Res, + $Val extends WcSessionProposeRequest> + implements $WcSessionProposeRequestCopyWith<$Res> { + _$WcSessionProposeRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? relays = null, + Object? requiredNamespaces = null, + Object? optionalNamespaces = freezed, + Object? sessionProperties = freezed, + Object? proposer = null, + }) { + return _then(_value.copyWith( + relays: null == relays + ? _value.relays + : relays // ignore: cast_nullable_to_non_nullable + as List, + requiredNamespaces: null == requiredNamespaces + ? _value.requiredNamespaces + : requiredNamespaces // ignore: cast_nullable_to_non_nullable + as Map, + optionalNamespaces: freezed == optionalNamespaces + ? _value.optionalNamespaces + : optionalNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + sessionProperties: freezed == sessionProperties + ? _value.sessionProperties + : sessionProperties // ignore: cast_nullable_to_non_nullable + as Map?, + proposer: null == proposer + ? _value.proposer + : proposer // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get proposer { + return $ConnectionMetadataCopyWith<$Res>(_value.proposer, (value) { + return _then(_value.copyWith(proposer: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$WcSessionProposeRequestImplCopyWith<$Res> + implements $WcSessionProposeRequestCopyWith<$Res> { + factory _$$WcSessionProposeRequestImplCopyWith( + _$WcSessionProposeRequestImpl value, + $Res Function(_$WcSessionProposeRequestImpl) then) = + __$$WcSessionProposeRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List relays, + Map requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + ConnectionMetadata proposer}); + + @override + $ConnectionMetadataCopyWith<$Res> get proposer; +} + +/// @nodoc +class __$$WcSessionProposeRequestImplCopyWithImpl<$Res> + extends _$WcSessionProposeRequestCopyWithImpl<$Res, + _$WcSessionProposeRequestImpl> + implements _$$WcSessionProposeRequestImplCopyWith<$Res> { + __$$WcSessionProposeRequestImplCopyWithImpl( + _$WcSessionProposeRequestImpl _value, + $Res Function(_$WcSessionProposeRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? relays = null, + Object? requiredNamespaces = null, + Object? optionalNamespaces = freezed, + Object? sessionProperties = freezed, + Object? proposer = null, + }) { + return _then(_$WcSessionProposeRequestImpl( + relays: null == relays + ? _value._relays + : relays // ignore: cast_nullable_to_non_nullable + as List, + requiredNamespaces: null == requiredNamespaces + ? _value._requiredNamespaces + : requiredNamespaces // ignore: cast_nullable_to_non_nullable + as Map, + optionalNamespaces: freezed == optionalNamespaces + ? _value._optionalNamespaces + : optionalNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + sessionProperties: freezed == sessionProperties + ? _value._sessionProperties + : sessionProperties // ignore: cast_nullable_to_non_nullable + as Map?, + proposer: null == proposer + ? _value.proposer + : proposer // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$WcSessionProposeRequestImpl implements _WcSessionProposeRequest { + const _$WcSessionProposeRequestImpl( + {required final List relays, + required final Map requiredNamespaces, + final Map? optionalNamespaces, + final Map? sessionProperties, + required this.proposer}) + : _relays = relays, + _requiredNamespaces = requiredNamespaces, + _optionalNamespaces = optionalNamespaces, + _sessionProperties = sessionProperties; + + factory _$WcSessionProposeRequestImpl.fromJson(Map json) => + _$$WcSessionProposeRequestImplFromJson(json); + + final List _relays; + @override + List get relays { + if (_relays is EqualUnmodifiableListView) return _relays; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_relays); + } + + final Map _requiredNamespaces; + @override + Map get requiredNamespaces { + if (_requiredNamespaces is EqualUnmodifiableMapView) + return _requiredNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_requiredNamespaces); + } + + final Map? _optionalNamespaces; + @override + Map? get optionalNamespaces { + final value = _optionalNamespaces; + if (value == null) return null; + if (_optionalNamespaces is EqualUnmodifiableMapView) + return _optionalNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final Map? _sessionProperties; + @override + Map? get sessionProperties { + final value = _sessionProperties; + if (value == null) return null; + if (_sessionProperties is EqualUnmodifiableMapView) + return _sessionProperties; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + final ConnectionMetadata proposer; + + @override + String toString() { + return 'WcSessionProposeRequest(relays: $relays, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, proposer: $proposer)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionProposeRequestImpl && + const DeepCollectionEquality().equals(other._relays, _relays) && + const DeepCollectionEquality() + .equals(other._requiredNamespaces, _requiredNamespaces) && + const DeepCollectionEquality() + .equals(other._optionalNamespaces, _optionalNamespaces) && + const DeepCollectionEquality() + .equals(other._sessionProperties, _sessionProperties) && + (identical(other.proposer, proposer) || + other.proposer == proposer)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_relays), + const DeepCollectionEquality().hash(_requiredNamespaces), + const DeepCollectionEquality().hash(_optionalNamespaces), + const DeepCollectionEquality().hash(_sessionProperties), + proposer); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionProposeRequestImplCopyWith<_$WcSessionProposeRequestImpl> + get copyWith => __$$WcSessionProposeRequestImplCopyWithImpl< + _$WcSessionProposeRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionProposeRequestImplToJson( + this, + ); + } +} + +abstract class _WcSessionProposeRequest implements WcSessionProposeRequest { + const factory _WcSessionProposeRequest( + {required final List relays, + required final Map requiredNamespaces, + final Map? optionalNamespaces, + final Map? sessionProperties, + required final ConnectionMetadata proposer}) = + _$WcSessionProposeRequestImpl; + + factory _WcSessionProposeRequest.fromJson(Map json) = + _$WcSessionProposeRequestImpl.fromJson; + + @override + List get relays; + @override + Map get requiredNamespaces; + @override + Map? get optionalNamespaces; + @override + Map? get sessionProperties; + @override + ConnectionMetadata get proposer; + @override + @JsonKey(ignore: true) + _$$WcSessionProposeRequestImplCopyWith<_$WcSessionProposeRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionProposeResponse _$WcSessionProposeResponseFromJson( + Map json) { + return _WcSessionProposeResponse.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionProposeResponse { + Relay get relay => throw _privateConstructorUsedError; + String get responderPublicKey => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionProposeResponseCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionProposeResponseCopyWith<$Res> { + factory $WcSessionProposeResponseCopyWith(WcSessionProposeResponse value, + $Res Function(WcSessionProposeResponse) then) = + _$WcSessionProposeResponseCopyWithImpl<$Res, WcSessionProposeResponse>; + @useResult + $Res call({Relay relay, String responderPublicKey}); +} + +/// @nodoc +class _$WcSessionProposeResponseCopyWithImpl<$Res, + $Val extends WcSessionProposeResponse> + implements $WcSessionProposeResponseCopyWith<$Res> { + _$WcSessionProposeResponseCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? relay = null, + Object? responderPublicKey = null, + }) { + return _then(_value.copyWith( + relay: null == relay + ? _value.relay + : relay // ignore: cast_nullable_to_non_nullable + as Relay, + responderPublicKey: null == responderPublicKey + ? _value.responderPublicKey + : responderPublicKey // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WcSessionProposeResponseImplCopyWith<$Res> + implements $WcSessionProposeResponseCopyWith<$Res> { + factory _$$WcSessionProposeResponseImplCopyWith( + _$WcSessionProposeResponseImpl value, + $Res Function(_$WcSessionProposeResponseImpl) then) = + __$$WcSessionProposeResponseImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Relay relay, String responderPublicKey}); +} + +/// @nodoc +class __$$WcSessionProposeResponseImplCopyWithImpl<$Res> + extends _$WcSessionProposeResponseCopyWithImpl<$Res, + _$WcSessionProposeResponseImpl> + implements _$$WcSessionProposeResponseImplCopyWith<$Res> { + __$$WcSessionProposeResponseImplCopyWithImpl( + _$WcSessionProposeResponseImpl _value, + $Res Function(_$WcSessionProposeResponseImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? relay = null, + Object? responderPublicKey = null, + }) { + return _then(_$WcSessionProposeResponseImpl( + relay: null == relay + ? _value.relay + : relay // ignore: cast_nullable_to_non_nullable + as Relay, + responderPublicKey: null == responderPublicKey + ? _value.responderPublicKey + : responderPublicKey // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$WcSessionProposeResponseImpl implements _WcSessionProposeResponse { + const _$WcSessionProposeResponseImpl( + {required this.relay, required this.responderPublicKey}); + + factory _$WcSessionProposeResponseImpl.fromJson(Map json) => + _$$WcSessionProposeResponseImplFromJson(json); + + @override + final Relay relay; + @override + final String responderPublicKey; + + @override + String toString() { + return 'WcSessionProposeResponse(relay: $relay, responderPublicKey: $responderPublicKey)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionProposeResponseImpl && + (identical(other.relay, relay) || other.relay == relay) && + (identical(other.responderPublicKey, responderPublicKey) || + other.responderPublicKey == responderPublicKey)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, relay, responderPublicKey); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionProposeResponseImplCopyWith<_$WcSessionProposeResponseImpl> + get copyWith => __$$WcSessionProposeResponseImplCopyWithImpl< + _$WcSessionProposeResponseImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionProposeResponseImplToJson( + this, + ); + } +} + +abstract class _WcSessionProposeResponse implements WcSessionProposeResponse { + const factory _WcSessionProposeResponse( + {required final Relay relay, + required final String responderPublicKey}) = + _$WcSessionProposeResponseImpl; + + factory _WcSessionProposeResponse.fromJson(Map json) = + _$WcSessionProposeResponseImpl.fromJson; + + @override + Relay get relay; + @override + String get responderPublicKey; + @override + @JsonKey(ignore: true) + _$$WcSessionProposeResponseImplCopyWith<_$WcSessionProposeResponseImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionSettleRequest _$WcSessionSettleRequestFromJson( + Map json) { + return _WcSessionSettleRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionSettleRequest { + Relay get relay => throw _privateConstructorUsedError; + Map get namespaces => throw _privateConstructorUsedError; + Map? get requiredNamespaces => + throw _privateConstructorUsedError; + Map? get optionalNamespaces => + throw _privateConstructorUsedError; + Map? get sessionProperties => + throw _privateConstructorUsedError; + int get expiry => throw _privateConstructorUsedError; + ConnectionMetadata get controller => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionSettleRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionSettleRequestCopyWith<$Res> { + factory $WcSessionSettleRequestCopyWith(WcSessionSettleRequest value, + $Res Function(WcSessionSettleRequest) then) = + _$WcSessionSettleRequestCopyWithImpl<$Res, WcSessionSettleRequest>; + @useResult + $Res call( + {Relay relay, + Map namespaces, + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + int expiry, + ConnectionMetadata controller}); + + $ConnectionMetadataCopyWith<$Res> get controller; +} + +/// @nodoc +class _$WcSessionSettleRequestCopyWithImpl<$Res, + $Val extends WcSessionSettleRequest> + implements $WcSessionSettleRequestCopyWith<$Res> { + _$WcSessionSettleRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? relay = null, + Object? namespaces = null, + Object? requiredNamespaces = freezed, + Object? optionalNamespaces = freezed, + Object? sessionProperties = freezed, + Object? expiry = null, + Object? controller = null, + }) { + return _then(_value.copyWith( + relay: null == relay + ? _value.relay + : relay // ignore: cast_nullable_to_non_nullable + as Relay, + namespaces: null == namespaces + ? _value.namespaces + : namespaces // ignore: cast_nullable_to_non_nullable + as Map, + requiredNamespaces: freezed == requiredNamespaces + ? _value.requiredNamespaces + : requiredNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + optionalNamespaces: freezed == optionalNamespaces + ? _value.optionalNamespaces + : optionalNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + sessionProperties: freezed == sessionProperties + ? _value.sessionProperties + : sessionProperties // ignore: cast_nullable_to_non_nullable + as Map?, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + controller: null == controller + ? _value.controller + : controller // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get controller { + return $ConnectionMetadataCopyWith<$Res>(_value.controller, (value) { + return _then(_value.copyWith(controller: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$WcSessionSettleRequestImplCopyWith<$Res> + implements $WcSessionSettleRequestCopyWith<$Res> { + factory _$$WcSessionSettleRequestImplCopyWith( + _$WcSessionSettleRequestImpl value, + $Res Function(_$WcSessionSettleRequestImpl) then) = + __$$WcSessionSettleRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Relay relay, + Map namespaces, + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + int expiry, + ConnectionMetadata controller}); + + @override + $ConnectionMetadataCopyWith<$Res> get controller; +} + +/// @nodoc +class __$$WcSessionSettleRequestImplCopyWithImpl<$Res> + extends _$WcSessionSettleRequestCopyWithImpl<$Res, + _$WcSessionSettleRequestImpl> + implements _$$WcSessionSettleRequestImplCopyWith<$Res> { + __$$WcSessionSettleRequestImplCopyWithImpl( + _$WcSessionSettleRequestImpl _value, + $Res Function(_$WcSessionSettleRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? relay = null, + Object? namespaces = null, + Object? requiredNamespaces = freezed, + Object? optionalNamespaces = freezed, + Object? sessionProperties = freezed, + Object? expiry = null, + Object? controller = null, + }) { + return _then(_$WcSessionSettleRequestImpl( + relay: null == relay + ? _value.relay + : relay // ignore: cast_nullable_to_non_nullable + as Relay, + namespaces: null == namespaces + ? _value._namespaces + : namespaces // ignore: cast_nullable_to_non_nullable + as Map, + requiredNamespaces: freezed == requiredNamespaces + ? _value._requiredNamespaces + : requiredNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + optionalNamespaces: freezed == optionalNamespaces + ? _value._optionalNamespaces + : optionalNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + sessionProperties: freezed == sessionProperties + ? _value._sessionProperties + : sessionProperties // ignore: cast_nullable_to_non_nullable + as Map?, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + controller: null == controller + ? _value.controller + : controller // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$WcSessionSettleRequestImpl implements _WcSessionSettleRequest { + const _$WcSessionSettleRequestImpl( + {required this.relay, + required final Map namespaces, + final Map? requiredNamespaces, + final Map? optionalNamespaces, + final Map? sessionProperties, + required this.expiry, + required this.controller}) + : _namespaces = namespaces, + _requiredNamespaces = requiredNamespaces, + _optionalNamespaces = optionalNamespaces, + _sessionProperties = sessionProperties; + + factory _$WcSessionSettleRequestImpl.fromJson(Map json) => + _$$WcSessionSettleRequestImplFromJson(json); + + @override + final Relay relay; + final Map _namespaces; + @override + Map get namespaces { + if (_namespaces is EqualUnmodifiableMapView) return _namespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_namespaces); + } + + final Map? _requiredNamespaces; + @override + Map? get requiredNamespaces { + final value = _requiredNamespaces; + if (value == null) return null; + if (_requiredNamespaces is EqualUnmodifiableMapView) + return _requiredNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final Map? _optionalNamespaces; + @override + Map? get optionalNamespaces { + final value = _optionalNamespaces; + if (value == null) return null; + if (_optionalNamespaces is EqualUnmodifiableMapView) + return _optionalNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final Map? _sessionProperties; + @override + Map? get sessionProperties { + final value = _sessionProperties; + if (value == null) return null; + if (_sessionProperties is EqualUnmodifiableMapView) + return _sessionProperties; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + final int expiry; + @override + final ConnectionMetadata controller; + + @override + String toString() { + return 'WcSessionSettleRequest(relay: $relay, namespaces: $namespaces, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, expiry: $expiry, controller: $controller)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionSettleRequestImpl && + (identical(other.relay, relay) || other.relay == relay) && + const DeepCollectionEquality() + .equals(other._namespaces, _namespaces) && + const DeepCollectionEquality() + .equals(other._requiredNamespaces, _requiredNamespaces) && + const DeepCollectionEquality() + .equals(other._optionalNamespaces, _optionalNamespaces) && + const DeepCollectionEquality() + .equals(other._sessionProperties, _sessionProperties) && + (identical(other.expiry, expiry) || other.expiry == expiry) && + (identical(other.controller, controller) || + other.controller == controller)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + relay, + const DeepCollectionEquality().hash(_namespaces), + const DeepCollectionEquality().hash(_requiredNamespaces), + const DeepCollectionEquality().hash(_optionalNamespaces), + const DeepCollectionEquality().hash(_sessionProperties), + expiry, + controller); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionSettleRequestImplCopyWith<_$WcSessionSettleRequestImpl> + get copyWith => __$$WcSessionSettleRequestImplCopyWithImpl< + _$WcSessionSettleRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionSettleRequestImplToJson( + this, + ); + } +} + +abstract class _WcSessionSettleRequest implements WcSessionSettleRequest { + const factory _WcSessionSettleRequest( + {required final Relay relay, + required final Map namespaces, + final Map? requiredNamespaces, + final Map? optionalNamespaces, + final Map? sessionProperties, + required final int expiry, + required final ConnectionMetadata controller}) = + _$WcSessionSettleRequestImpl; + + factory _WcSessionSettleRequest.fromJson(Map json) = + _$WcSessionSettleRequestImpl.fromJson; + + @override + Relay get relay; + @override + Map get namespaces; + @override + Map? get requiredNamespaces; + @override + Map? get optionalNamespaces; + @override + Map? get sessionProperties; + @override + int get expiry; + @override + ConnectionMetadata get controller; + @override + @JsonKey(ignore: true) + _$$WcSessionSettleRequestImplCopyWith<_$WcSessionSettleRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionUpdateRequest _$WcSessionUpdateRequestFromJson( + Map json) { + return _WcSessionUpdateRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionUpdateRequest { + Map get namespaces => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionUpdateRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionUpdateRequestCopyWith<$Res> { + factory $WcSessionUpdateRequestCopyWith(WcSessionUpdateRequest value, + $Res Function(WcSessionUpdateRequest) then) = + _$WcSessionUpdateRequestCopyWithImpl<$Res, WcSessionUpdateRequest>; + @useResult + $Res call({Map namespaces}); +} + +/// @nodoc +class _$WcSessionUpdateRequestCopyWithImpl<$Res, + $Val extends WcSessionUpdateRequest> + implements $WcSessionUpdateRequestCopyWith<$Res> { + _$WcSessionUpdateRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? namespaces = null, + }) { + return _then(_value.copyWith( + namespaces: null == namespaces + ? _value.namespaces + : namespaces // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WcSessionUpdateRequestImplCopyWith<$Res> + implements $WcSessionUpdateRequestCopyWith<$Res> { + factory _$$WcSessionUpdateRequestImplCopyWith( + _$WcSessionUpdateRequestImpl value, + $Res Function(_$WcSessionUpdateRequestImpl) then) = + __$$WcSessionUpdateRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Map namespaces}); +} + +/// @nodoc +class __$$WcSessionUpdateRequestImplCopyWithImpl<$Res> + extends _$WcSessionUpdateRequestCopyWithImpl<$Res, + _$WcSessionUpdateRequestImpl> + implements _$$WcSessionUpdateRequestImplCopyWith<$Res> { + __$$WcSessionUpdateRequestImplCopyWithImpl( + _$WcSessionUpdateRequestImpl _value, + $Res Function(_$WcSessionUpdateRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? namespaces = null, + }) { + return _then(_$WcSessionUpdateRequestImpl( + namespaces: null == namespaces + ? _value._namespaces + : namespaces // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$WcSessionUpdateRequestImpl implements _WcSessionUpdateRequest { + const _$WcSessionUpdateRequestImpl( + {required final Map namespaces}) + : _namespaces = namespaces; + + factory _$WcSessionUpdateRequestImpl.fromJson(Map json) => + _$$WcSessionUpdateRequestImplFromJson(json); + + final Map _namespaces; + @override + Map get namespaces { + if (_namespaces is EqualUnmodifiableMapView) return _namespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_namespaces); + } + + @override + String toString() { + return 'WcSessionUpdateRequest(namespaces: $namespaces)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionUpdateRequestImpl && + const DeepCollectionEquality() + .equals(other._namespaces, _namespaces)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_namespaces)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionUpdateRequestImplCopyWith<_$WcSessionUpdateRequestImpl> + get copyWith => __$$WcSessionUpdateRequestImplCopyWithImpl< + _$WcSessionUpdateRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionUpdateRequestImplToJson( + this, + ); + } +} + +abstract class _WcSessionUpdateRequest implements WcSessionUpdateRequest { + const factory _WcSessionUpdateRequest( + {required final Map namespaces}) = + _$WcSessionUpdateRequestImpl; + + factory _WcSessionUpdateRequest.fromJson(Map json) = + _$WcSessionUpdateRequestImpl.fromJson; + + @override + Map get namespaces; + @override + @JsonKey(ignore: true) + _$$WcSessionUpdateRequestImplCopyWith<_$WcSessionUpdateRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionExtendRequest _$WcSessionExtendRequestFromJson( + Map json) { + return _WcSessionExtendRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionExtendRequest { + Map? get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionExtendRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionExtendRequestCopyWith<$Res> { + factory $WcSessionExtendRequestCopyWith(WcSessionExtendRequest value, + $Res Function(WcSessionExtendRequest) then) = + _$WcSessionExtendRequestCopyWithImpl<$Res, WcSessionExtendRequest>; + @useResult + $Res call({Map? data}); +} + +/// @nodoc +class _$WcSessionExtendRequestCopyWithImpl<$Res, + $Val extends WcSessionExtendRequest> + implements $WcSessionExtendRequestCopyWith<$Res> { + _$WcSessionExtendRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = freezed, + }) { + return _then(_value.copyWith( + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as Map?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WcSessionExtendRequestImplCopyWith<$Res> + implements $WcSessionExtendRequestCopyWith<$Res> { + factory _$$WcSessionExtendRequestImplCopyWith( + _$WcSessionExtendRequestImpl value, + $Res Function(_$WcSessionExtendRequestImpl) then) = + __$$WcSessionExtendRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Map? data}); +} + +/// @nodoc +class __$$WcSessionExtendRequestImplCopyWithImpl<$Res> + extends _$WcSessionExtendRequestCopyWithImpl<$Res, + _$WcSessionExtendRequestImpl> + implements _$$WcSessionExtendRequestImplCopyWith<$Res> { + __$$WcSessionExtendRequestImplCopyWithImpl( + _$WcSessionExtendRequestImpl _value, + $Res Function(_$WcSessionExtendRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = freezed, + }) { + return _then(_$WcSessionExtendRequestImpl( + data: freezed == data + ? _value._data + : data // ignore: cast_nullable_to_non_nullable + as Map?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$WcSessionExtendRequestImpl implements _WcSessionExtendRequest { + const _$WcSessionExtendRequestImpl({final Map? data}) + : _data = data; + + factory _$WcSessionExtendRequestImpl.fromJson(Map json) => + _$$WcSessionExtendRequestImplFromJson(json); + + final Map? _data; + @override + Map? get data { + final value = _data; + if (value == null) return null; + if (_data is EqualUnmodifiableMapView) return _data; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + String toString() { + return 'WcSessionExtendRequest(data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionExtendRequestImpl && + const DeepCollectionEquality().equals(other._data, _data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_data)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionExtendRequestImplCopyWith<_$WcSessionExtendRequestImpl> + get copyWith => __$$WcSessionExtendRequestImplCopyWithImpl< + _$WcSessionExtendRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionExtendRequestImplToJson( + this, + ); + } +} + +abstract class _WcSessionExtendRequest implements WcSessionExtendRequest { + const factory _WcSessionExtendRequest({final Map? data}) = + _$WcSessionExtendRequestImpl; + + factory _WcSessionExtendRequest.fromJson(Map json) = + _$WcSessionExtendRequestImpl.fromJson; + + @override + Map? get data; + @override + @JsonKey(ignore: true) + _$$WcSessionExtendRequestImplCopyWith<_$WcSessionExtendRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionDeleteRequest _$WcSessionDeleteRequestFromJson( + Map json) { + return _WcSessionDeleteRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionDeleteRequest { + int get code => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + String? get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionDeleteRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionDeleteRequestCopyWith<$Res> { + factory $WcSessionDeleteRequestCopyWith(WcSessionDeleteRequest value, + $Res Function(WcSessionDeleteRequest) then) = + _$WcSessionDeleteRequestCopyWithImpl<$Res, WcSessionDeleteRequest>; + @useResult + $Res call({int code, String message, String? data}); +} + +/// @nodoc +class _$WcSessionDeleteRequestCopyWithImpl<$Res, + $Val extends WcSessionDeleteRequest> + implements $WcSessionDeleteRequestCopyWith<$Res> { + _$WcSessionDeleteRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? message = null, + Object? data = freezed, + }) { + return _then(_value.copyWith( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WcSessionDeleteRequestImplCopyWith<$Res> + implements $WcSessionDeleteRequestCopyWith<$Res> { + factory _$$WcSessionDeleteRequestImplCopyWith( + _$WcSessionDeleteRequestImpl value, + $Res Function(_$WcSessionDeleteRequestImpl) then) = + __$$WcSessionDeleteRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int code, String message, String? data}); +} + +/// @nodoc +class __$$WcSessionDeleteRequestImplCopyWithImpl<$Res> + extends _$WcSessionDeleteRequestCopyWithImpl<$Res, + _$WcSessionDeleteRequestImpl> + implements _$$WcSessionDeleteRequestImplCopyWith<$Res> { + __$$WcSessionDeleteRequestImplCopyWithImpl( + _$WcSessionDeleteRequestImpl _value, + $Res Function(_$WcSessionDeleteRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? message = null, + Object? data = freezed, + }) { + return _then(_$WcSessionDeleteRequestImpl( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$WcSessionDeleteRequestImpl implements _WcSessionDeleteRequest { + const _$WcSessionDeleteRequestImpl( + {required this.code, required this.message, this.data}); + + factory _$WcSessionDeleteRequestImpl.fromJson(Map json) => + _$$WcSessionDeleteRequestImplFromJson(json); + + @override + final int code; + @override + final String message; + @override + final String? data; + + @override + String toString() { + return 'WcSessionDeleteRequest(code: $code, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionDeleteRequestImpl && + (identical(other.code, code) || other.code == code) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, code, message, data); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionDeleteRequestImplCopyWith<_$WcSessionDeleteRequestImpl> + get copyWith => __$$WcSessionDeleteRequestImplCopyWithImpl< + _$WcSessionDeleteRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionDeleteRequestImplToJson( + this, + ); + } +} + +abstract class _WcSessionDeleteRequest implements WcSessionDeleteRequest { + const factory _WcSessionDeleteRequest( + {required final int code, + required final String message, + final String? data}) = _$WcSessionDeleteRequestImpl; + + factory _WcSessionDeleteRequest.fromJson(Map json) = + _$WcSessionDeleteRequestImpl.fromJson; + + @override + int get code; + @override + String get message; + @override + String? get data; + @override + @JsonKey(ignore: true) + _$$WcSessionDeleteRequestImplCopyWith<_$WcSessionDeleteRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionPingRequest _$WcSessionPingRequestFromJson(Map json) { + return _WcSessionPingRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionPingRequest { + Map? get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionPingRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionPingRequestCopyWith<$Res> { + factory $WcSessionPingRequestCopyWith(WcSessionPingRequest value, + $Res Function(WcSessionPingRequest) then) = + _$WcSessionPingRequestCopyWithImpl<$Res, WcSessionPingRequest>; + @useResult + $Res call({Map? data}); +} + +/// @nodoc +class _$WcSessionPingRequestCopyWithImpl<$Res, + $Val extends WcSessionPingRequest> + implements $WcSessionPingRequestCopyWith<$Res> { + _$WcSessionPingRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = freezed, + }) { + return _then(_value.copyWith( + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as Map?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WcSessionPingRequestImplCopyWith<$Res> + implements $WcSessionPingRequestCopyWith<$Res> { + factory _$$WcSessionPingRequestImplCopyWith(_$WcSessionPingRequestImpl value, + $Res Function(_$WcSessionPingRequestImpl) then) = + __$$WcSessionPingRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Map? data}); +} + +/// @nodoc +class __$$WcSessionPingRequestImplCopyWithImpl<$Res> + extends _$WcSessionPingRequestCopyWithImpl<$Res, _$WcSessionPingRequestImpl> + implements _$$WcSessionPingRequestImplCopyWith<$Res> { + __$$WcSessionPingRequestImplCopyWithImpl(_$WcSessionPingRequestImpl _value, + $Res Function(_$WcSessionPingRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = freezed, + }) { + return _then(_$WcSessionPingRequestImpl( + data: freezed == data + ? _value._data + : data // ignore: cast_nullable_to_non_nullable + as Map?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$WcSessionPingRequestImpl implements _WcSessionPingRequest { + const _$WcSessionPingRequestImpl({final Map? data}) + : _data = data; + + factory _$WcSessionPingRequestImpl.fromJson(Map json) => + _$$WcSessionPingRequestImplFromJson(json); + + final Map? _data; + @override + Map? get data { + final value = _data; + if (value == null) return null; + if (_data is EqualUnmodifiableMapView) return _data; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + String toString() { + return 'WcSessionPingRequest(data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionPingRequestImpl && + const DeepCollectionEquality().equals(other._data, _data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_data)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionPingRequestImplCopyWith<_$WcSessionPingRequestImpl> + get copyWith => + __$$WcSessionPingRequestImplCopyWithImpl<_$WcSessionPingRequestImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$WcSessionPingRequestImplToJson( + this, + ); + } +} + +abstract class _WcSessionPingRequest implements WcSessionPingRequest { + const factory _WcSessionPingRequest({final Map? data}) = + _$WcSessionPingRequestImpl; + + factory _WcSessionPingRequest.fromJson(Map json) = + _$WcSessionPingRequestImpl.fromJson; + + @override + Map? get data; + @override + @JsonKey(ignore: true) + _$$WcSessionPingRequestImplCopyWith<_$WcSessionPingRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionRequestRequest _$WcSessionRequestRequestFromJson( + Map json) { + return _WcSessionRequestRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionRequestRequest { + String get chainId => throw _privateConstructorUsedError; + SessionRequestParams get request => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionRequestRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionRequestRequestCopyWith<$Res> { + factory $WcSessionRequestRequestCopyWith(WcSessionRequestRequest value, + $Res Function(WcSessionRequestRequest) then) = + _$WcSessionRequestRequestCopyWithImpl<$Res, WcSessionRequestRequest>; + @useResult + $Res call({String chainId, SessionRequestParams request}); + + $SessionRequestParamsCopyWith<$Res> get request; +} + +/// @nodoc +class _$WcSessionRequestRequestCopyWithImpl<$Res, + $Val extends WcSessionRequestRequest> + implements $WcSessionRequestRequestCopyWith<$Res> { + _$WcSessionRequestRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chainId = null, + Object? request = null, + }) { + return _then(_value.copyWith( + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + request: null == request + ? _value.request + : request // ignore: cast_nullable_to_non_nullable + as SessionRequestParams, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $SessionRequestParamsCopyWith<$Res> get request { + return $SessionRequestParamsCopyWith<$Res>(_value.request, (value) { + return _then(_value.copyWith(request: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$WcSessionRequestRequestImplCopyWith<$Res> + implements $WcSessionRequestRequestCopyWith<$Res> { + factory _$$WcSessionRequestRequestImplCopyWith( + _$WcSessionRequestRequestImpl value, + $Res Function(_$WcSessionRequestRequestImpl) then) = + __$$WcSessionRequestRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String chainId, SessionRequestParams request}); + + @override + $SessionRequestParamsCopyWith<$Res> get request; +} + +/// @nodoc +class __$$WcSessionRequestRequestImplCopyWithImpl<$Res> + extends _$WcSessionRequestRequestCopyWithImpl<$Res, + _$WcSessionRequestRequestImpl> + implements _$$WcSessionRequestRequestImplCopyWith<$Res> { + __$$WcSessionRequestRequestImplCopyWithImpl( + _$WcSessionRequestRequestImpl _value, + $Res Function(_$WcSessionRequestRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chainId = null, + Object? request = null, + }) { + return _then(_$WcSessionRequestRequestImpl( + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + request: null == request + ? _value.request + : request // ignore: cast_nullable_to_non_nullable + as SessionRequestParams, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$WcSessionRequestRequestImpl implements _WcSessionRequestRequest { + const _$WcSessionRequestRequestImpl( + {required this.chainId, required this.request}); + + factory _$WcSessionRequestRequestImpl.fromJson(Map json) => + _$$WcSessionRequestRequestImplFromJson(json); + + @override + final String chainId; + @override + final SessionRequestParams request; + + @override + String toString() { + return 'WcSessionRequestRequest(chainId: $chainId, request: $request)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionRequestRequestImpl && + (identical(other.chainId, chainId) || other.chainId == chainId) && + (identical(other.request, request) || other.request == request)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, chainId, request); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionRequestRequestImplCopyWith<_$WcSessionRequestRequestImpl> + get copyWith => __$$WcSessionRequestRequestImplCopyWithImpl< + _$WcSessionRequestRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionRequestRequestImplToJson( + this, + ); + } +} + +abstract class _WcSessionRequestRequest implements WcSessionRequestRequest { + const factory _WcSessionRequestRequest( + {required final String chainId, + required final SessionRequestParams request}) = + _$WcSessionRequestRequestImpl; + + factory _WcSessionRequestRequest.fromJson(Map json) = + _$WcSessionRequestRequestImpl.fromJson; + + @override + String get chainId; + @override + SessionRequestParams get request; + @override + @JsonKey(ignore: true) + _$$WcSessionRequestRequestImplCopyWith<_$WcSessionRequestRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SessionRequestParams _$SessionRequestParamsFromJson(Map json) { + return _SessionRequestParams.fromJson(json); +} + +/// @nodoc +mixin _$SessionRequestParams { + String get method => throw _privateConstructorUsedError; + dynamic get params => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SessionRequestParamsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SessionRequestParamsCopyWith<$Res> { + factory $SessionRequestParamsCopyWith(SessionRequestParams value, + $Res Function(SessionRequestParams) then) = + _$SessionRequestParamsCopyWithImpl<$Res, SessionRequestParams>; + @useResult + $Res call({String method, dynamic params}); +} + +/// @nodoc +class _$SessionRequestParamsCopyWithImpl<$Res, + $Val extends SessionRequestParams> + implements $SessionRequestParamsCopyWith<$Res> { + _$SessionRequestParamsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? method = null, + Object? params = freezed, + }) { + return _then(_value.copyWith( + method: null == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as dynamic, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SessionRequestParamsImplCopyWith<$Res> + implements $SessionRequestParamsCopyWith<$Res> { + factory _$$SessionRequestParamsImplCopyWith(_$SessionRequestParamsImpl value, + $Res Function(_$SessionRequestParamsImpl) then) = + __$$SessionRequestParamsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String method, dynamic params}); +} + +/// @nodoc +class __$$SessionRequestParamsImplCopyWithImpl<$Res> + extends _$SessionRequestParamsCopyWithImpl<$Res, _$SessionRequestParamsImpl> + implements _$$SessionRequestParamsImplCopyWith<$Res> { + __$$SessionRequestParamsImplCopyWithImpl(_$SessionRequestParamsImpl _value, + $Res Function(_$SessionRequestParamsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? method = null, + Object? params = freezed, + }) { + return _then(_$SessionRequestParamsImpl( + method: null == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as dynamic, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$SessionRequestParamsImpl implements _SessionRequestParams { + const _$SessionRequestParamsImpl( + {required this.method, required this.params}); + + factory _$SessionRequestParamsImpl.fromJson(Map json) => + _$$SessionRequestParamsImplFromJson(json); + + @override + final String method; + @override + final dynamic params; + + @override + String toString() { + return 'SessionRequestParams(method: $method, params: $params)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SessionRequestParamsImpl && + (identical(other.method, method) || other.method == method) && + const DeepCollectionEquality().equals(other.params, params)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, method, const DeepCollectionEquality().hash(params)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SessionRequestParamsImplCopyWith<_$SessionRequestParamsImpl> + get copyWith => + __$$SessionRequestParamsImplCopyWithImpl<_$SessionRequestParamsImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SessionRequestParamsImplToJson( + this, + ); + } +} + +abstract class _SessionRequestParams implements SessionRequestParams { + const factory _SessionRequestParams( + {required final String method, + required final dynamic params}) = _$SessionRequestParamsImpl; + + factory _SessionRequestParams.fromJson(Map json) = + _$SessionRequestParamsImpl.fromJson; + + @override + String get method; + @override + dynamic get params; + @override + @JsonKey(ignore: true) + _$$SessionRequestParamsImplCopyWith<_$SessionRequestParamsImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionEventRequest _$WcSessionEventRequestFromJson( + Map json) { + return _WcSessionEventRequest.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionEventRequest { + String get chainId => throw _privateConstructorUsedError; + SessionEventParams get event => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionEventRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionEventRequestCopyWith<$Res> { + factory $WcSessionEventRequestCopyWith(WcSessionEventRequest value, + $Res Function(WcSessionEventRequest) then) = + _$WcSessionEventRequestCopyWithImpl<$Res, WcSessionEventRequest>; + @useResult + $Res call({String chainId, SessionEventParams event}); + + $SessionEventParamsCopyWith<$Res> get event; +} + +/// @nodoc +class _$WcSessionEventRequestCopyWithImpl<$Res, + $Val extends WcSessionEventRequest> + implements $WcSessionEventRequestCopyWith<$Res> { + _$WcSessionEventRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chainId = null, + Object? event = null, + }) { + return _then(_value.copyWith( + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + event: null == event + ? _value.event + : event // ignore: cast_nullable_to_non_nullable + as SessionEventParams, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $SessionEventParamsCopyWith<$Res> get event { + return $SessionEventParamsCopyWith<$Res>(_value.event, (value) { + return _then(_value.copyWith(event: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$WcSessionEventRequestImplCopyWith<$Res> + implements $WcSessionEventRequestCopyWith<$Res> { + factory _$$WcSessionEventRequestImplCopyWith( + _$WcSessionEventRequestImpl value, + $Res Function(_$WcSessionEventRequestImpl) then) = + __$$WcSessionEventRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String chainId, SessionEventParams event}); + + @override + $SessionEventParamsCopyWith<$Res> get event; +} + +/// @nodoc +class __$$WcSessionEventRequestImplCopyWithImpl<$Res> + extends _$WcSessionEventRequestCopyWithImpl<$Res, + _$WcSessionEventRequestImpl> + implements _$$WcSessionEventRequestImplCopyWith<$Res> { + __$$WcSessionEventRequestImplCopyWithImpl(_$WcSessionEventRequestImpl _value, + $Res Function(_$WcSessionEventRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chainId = null, + Object? event = null, + }) { + return _then(_$WcSessionEventRequestImpl( + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + event: null == event + ? _value.event + : event // ignore: cast_nullable_to_non_nullable + as SessionEventParams, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$WcSessionEventRequestImpl implements _WcSessionEventRequest { + const _$WcSessionEventRequestImpl( + {required this.chainId, required this.event}); + + factory _$WcSessionEventRequestImpl.fromJson(Map json) => + _$$WcSessionEventRequestImplFromJson(json); + + @override + final String chainId; + @override + final SessionEventParams event; + + @override + String toString() { + return 'WcSessionEventRequest(chainId: $chainId, event: $event)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionEventRequestImpl && + (identical(other.chainId, chainId) || other.chainId == chainId) && + (identical(other.event, event) || other.event == event)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, chainId, event); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionEventRequestImplCopyWith<_$WcSessionEventRequestImpl> + get copyWith => __$$WcSessionEventRequestImplCopyWithImpl< + _$WcSessionEventRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionEventRequestImplToJson( + this, + ); + } +} + +abstract class _WcSessionEventRequest implements WcSessionEventRequest { + const factory _WcSessionEventRequest( + {required final String chainId, + required final SessionEventParams event}) = _$WcSessionEventRequestImpl; + + factory _WcSessionEventRequest.fromJson(Map json) = + _$WcSessionEventRequestImpl.fromJson; + + @override + String get chainId; + @override + SessionEventParams get event; + @override + @JsonKey(ignore: true) + _$$WcSessionEventRequestImplCopyWith<_$WcSessionEventRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SessionEventParams _$SessionEventParamsFromJson(Map json) { + return _SessionEventParams.fromJson(json); +} + +/// @nodoc +mixin _$SessionEventParams { + String get name => throw _privateConstructorUsedError; + dynamic get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SessionEventParamsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SessionEventParamsCopyWith<$Res> { + factory $SessionEventParamsCopyWith( + SessionEventParams value, $Res Function(SessionEventParams) then) = + _$SessionEventParamsCopyWithImpl<$Res, SessionEventParams>; + @useResult + $Res call({String name, dynamic data}); +} + +/// @nodoc +class _$SessionEventParamsCopyWithImpl<$Res, $Val extends SessionEventParams> + implements $SessionEventParamsCopyWith<$Res> { + _$SessionEventParamsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? data = freezed, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as dynamic, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SessionEventParamsImplCopyWith<$Res> + implements $SessionEventParamsCopyWith<$Res> { + factory _$$SessionEventParamsImplCopyWith(_$SessionEventParamsImpl value, + $Res Function(_$SessionEventParamsImpl) then) = + __$$SessionEventParamsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, dynamic data}); +} + +/// @nodoc +class __$$SessionEventParamsImplCopyWithImpl<$Res> + extends _$SessionEventParamsCopyWithImpl<$Res, _$SessionEventParamsImpl> + implements _$$SessionEventParamsImplCopyWith<$Res> { + __$$SessionEventParamsImplCopyWithImpl(_$SessionEventParamsImpl _value, + $Res Function(_$SessionEventParamsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? data = freezed, + }) { + return _then(_$SessionEventParamsImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as dynamic, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$SessionEventParamsImpl implements _SessionEventParams { + const _$SessionEventParamsImpl({required this.name, required this.data}); + + factory _$SessionEventParamsImpl.fromJson(Map json) => + _$$SessionEventParamsImplFromJson(json); + + @override + final String name; + @override + final dynamic data; + + @override + String toString() { + return 'SessionEventParams(name: $name, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SessionEventParamsImpl && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other.data, data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, name, const DeepCollectionEquality().hash(data)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SessionEventParamsImplCopyWith<_$SessionEventParamsImpl> get copyWith => + __$$SessionEventParamsImplCopyWithImpl<_$SessionEventParamsImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SessionEventParamsImplToJson( + this, + ); + } +} + +abstract class _SessionEventParams implements SessionEventParams { + const factory _SessionEventParams( + {required final String name, + required final dynamic data}) = _$SessionEventParamsImpl; + + factory _SessionEventParams.fromJson(Map json) = + _$SessionEventParamsImpl.fromJson; + + @override + String get name; + @override + dynamic get data; + @override + @JsonKey(ignore: true) + _$$SessionEventParamsImplCopyWith<_$SessionEventParamsImpl> get copyWith => + throw _privateConstructorUsedError; +} + +WcSessionAuthRequestParams _$WcSessionAuthRequestParamsFromJson( + Map json) { + return _WcSessionAuthRequestParams.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionAuthRequestParams { + SessionAuthPayload get authPayload => throw _privateConstructorUsedError; + ConnectionMetadata get requester => throw _privateConstructorUsedError; + int get expiryTimestamp => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionAuthRequestParamsCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionAuthRequestParamsCopyWith<$Res> { + factory $WcSessionAuthRequestParamsCopyWith(WcSessionAuthRequestParams value, + $Res Function(WcSessionAuthRequestParams) then) = + _$WcSessionAuthRequestParamsCopyWithImpl<$Res, + WcSessionAuthRequestParams>; + @useResult + $Res call( + {SessionAuthPayload authPayload, + ConnectionMetadata requester, + int expiryTimestamp}); + + $SessionAuthPayloadCopyWith<$Res> get authPayload; + $ConnectionMetadataCopyWith<$Res> get requester; +} + +/// @nodoc +class _$WcSessionAuthRequestParamsCopyWithImpl<$Res, + $Val extends WcSessionAuthRequestParams> + implements $WcSessionAuthRequestParamsCopyWith<$Res> { + _$WcSessionAuthRequestParamsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? authPayload = null, + Object? requester = null, + Object? expiryTimestamp = null, + }) { + return _then(_value.copyWith( + authPayload: null == authPayload + ? _value.authPayload + : authPayload // ignore: cast_nullable_to_non_nullable + as SessionAuthPayload, + requester: null == requester + ? _value.requester + : requester // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + expiryTimestamp: null == expiryTimestamp + ? _value.expiryTimestamp + : expiryTimestamp // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $SessionAuthPayloadCopyWith<$Res> get authPayload { + return $SessionAuthPayloadCopyWith<$Res>(_value.authPayload, (value) { + return _then(_value.copyWith(authPayload: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get requester { + return $ConnectionMetadataCopyWith<$Res>(_value.requester, (value) { + return _then(_value.copyWith(requester: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$WcSessionAuthRequestParamsImplCopyWith<$Res> + implements $WcSessionAuthRequestParamsCopyWith<$Res> { + factory _$$WcSessionAuthRequestParamsImplCopyWith( + _$WcSessionAuthRequestParamsImpl value, + $Res Function(_$WcSessionAuthRequestParamsImpl) then) = + __$$WcSessionAuthRequestParamsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {SessionAuthPayload authPayload, + ConnectionMetadata requester, + int expiryTimestamp}); + + @override + $SessionAuthPayloadCopyWith<$Res> get authPayload; + @override + $ConnectionMetadataCopyWith<$Res> get requester; +} + +/// @nodoc +class __$$WcSessionAuthRequestParamsImplCopyWithImpl<$Res> + extends _$WcSessionAuthRequestParamsCopyWithImpl<$Res, + _$WcSessionAuthRequestParamsImpl> + implements _$$WcSessionAuthRequestParamsImplCopyWith<$Res> { + __$$WcSessionAuthRequestParamsImplCopyWithImpl( + _$WcSessionAuthRequestParamsImpl _value, + $Res Function(_$WcSessionAuthRequestParamsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? authPayload = null, + Object? requester = null, + Object? expiryTimestamp = null, + }) { + return _then(_$WcSessionAuthRequestParamsImpl( + authPayload: null == authPayload + ? _value.authPayload + : authPayload // ignore: cast_nullable_to_non_nullable + as SessionAuthPayload, + requester: null == requester + ? _value.requester + : requester // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + expiryTimestamp: null == expiryTimestamp + ? _value.expiryTimestamp + : expiryTimestamp // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$WcSessionAuthRequestParamsImpl implements _WcSessionAuthRequestParams { + const _$WcSessionAuthRequestParamsImpl( + {required this.authPayload, + required this.requester, + required this.expiryTimestamp}); + + factory _$WcSessionAuthRequestParamsImpl.fromJson( + Map json) => + _$$WcSessionAuthRequestParamsImplFromJson(json); + + @override + final SessionAuthPayload authPayload; + @override + final ConnectionMetadata requester; + @override + final int expiryTimestamp; + + @override + String toString() { + return 'WcSessionAuthRequestParams(authPayload: $authPayload, requester: $requester, expiryTimestamp: $expiryTimestamp)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionAuthRequestParamsImpl && + (identical(other.authPayload, authPayload) || + other.authPayload == authPayload) && + (identical(other.requester, requester) || + other.requester == requester) && + (identical(other.expiryTimestamp, expiryTimestamp) || + other.expiryTimestamp == expiryTimestamp)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, authPayload, requester, expiryTimestamp); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionAuthRequestParamsImplCopyWith<_$WcSessionAuthRequestParamsImpl> + get copyWith => __$$WcSessionAuthRequestParamsImplCopyWithImpl< + _$WcSessionAuthRequestParamsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionAuthRequestParamsImplToJson( + this, + ); + } +} + +abstract class _WcSessionAuthRequestParams + implements WcSessionAuthRequestParams { + const factory _WcSessionAuthRequestParams( + {required final SessionAuthPayload authPayload, + required final ConnectionMetadata requester, + required final int expiryTimestamp}) = _$WcSessionAuthRequestParamsImpl; + + factory _WcSessionAuthRequestParams.fromJson(Map json) = + _$WcSessionAuthRequestParamsImpl.fromJson; + + @override + SessionAuthPayload get authPayload; + @override + ConnectionMetadata get requester; + @override + int get expiryTimestamp; + @override + @JsonKey(ignore: true) + _$$WcSessionAuthRequestParamsImplCopyWith<_$WcSessionAuthRequestParamsImpl> + get copyWith => throw _privateConstructorUsedError; +} + +WcSessionAuthRequestResult _$WcSessionAuthRequestResultFromJson( + Map json) { + return _WcSessionAuthRequestResult.fromJson(json); +} + +/// @nodoc +mixin _$WcSessionAuthRequestResult { + List get cacaos => throw _privateConstructorUsedError; + ConnectionMetadata get responder => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WcSessionAuthRequestResultCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WcSessionAuthRequestResultCopyWith<$Res> { + factory $WcSessionAuthRequestResultCopyWith(WcSessionAuthRequestResult value, + $Res Function(WcSessionAuthRequestResult) then) = + _$WcSessionAuthRequestResultCopyWithImpl<$Res, + WcSessionAuthRequestResult>; + @useResult + $Res call({List cacaos, ConnectionMetadata responder}); + + $ConnectionMetadataCopyWith<$Res> get responder; +} + +/// @nodoc +class _$WcSessionAuthRequestResultCopyWithImpl<$Res, + $Val extends WcSessionAuthRequestResult> + implements $WcSessionAuthRequestResultCopyWith<$Res> { + _$WcSessionAuthRequestResultCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? cacaos = null, + Object? responder = null, + }) { + return _then(_value.copyWith( + cacaos: null == cacaos + ? _value.cacaos + : cacaos // ignore: cast_nullable_to_non_nullable + as List, + responder: null == responder + ? _value.responder + : responder // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get responder { + return $ConnectionMetadataCopyWith<$Res>(_value.responder, (value) { + return _then(_value.copyWith(responder: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$WcSessionAuthRequestResultImplCopyWith<$Res> + implements $WcSessionAuthRequestResultCopyWith<$Res> { + factory _$$WcSessionAuthRequestResultImplCopyWith( + _$WcSessionAuthRequestResultImpl value, + $Res Function(_$WcSessionAuthRequestResultImpl) then) = + __$$WcSessionAuthRequestResultImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List cacaos, ConnectionMetadata responder}); + + @override + $ConnectionMetadataCopyWith<$Res> get responder; +} + +/// @nodoc +class __$$WcSessionAuthRequestResultImplCopyWithImpl<$Res> + extends _$WcSessionAuthRequestResultCopyWithImpl<$Res, + _$WcSessionAuthRequestResultImpl> + implements _$$WcSessionAuthRequestResultImplCopyWith<$Res> { + __$$WcSessionAuthRequestResultImplCopyWithImpl( + _$WcSessionAuthRequestResultImpl _value, + $Res Function(_$WcSessionAuthRequestResultImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? cacaos = null, + Object? responder = null, + }) { + return _then(_$WcSessionAuthRequestResultImpl( + cacaos: null == cacaos + ? _value._cacaos + : cacaos // ignore: cast_nullable_to_non_nullable + as List, + responder: null == responder + ? _value.responder + : responder // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$WcSessionAuthRequestResultImpl implements _WcSessionAuthRequestResult { + const _$WcSessionAuthRequestResultImpl( + {required final List cacaos, required this.responder}) + : _cacaos = cacaos; + + factory _$WcSessionAuthRequestResultImpl.fromJson( + Map json) => + _$$WcSessionAuthRequestResultImplFromJson(json); + + final List _cacaos; + @override + List get cacaos { + if (_cacaos is EqualUnmodifiableListView) return _cacaos; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_cacaos); + } + + @override + final ConnectionMetadata responder; + + @override + String toString() { + return 'WcSessionAuthRequestResult(cacaos: $cacaos, responder: $responder)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WcSessionAuthRequestResultImpl && + const DeepCollectionEquality().equals(other._cacaos, _cacaos) && + (identical(other.responder, responder) || + other.responder == responder)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_cacaos), responder); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WcSessionAuthRequestResultImplCopyWith<_$WcSessionAuthRequestResultImpl> + get copyWith => __$$WcSessionAuthRequestResultImplCopyWithImpl< + _$WcSessionAuthRequestResultImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WcSessionAuthRequestResultImplToJson( + this, + ); + } +} + +abstract class _WcSessionAuthRequestResult + implements WcSessionAuthRequestResult { + const factory _WcSessionAuthRequestResult( + {required final List cacaos, + required final ConnectionMetadata responder}) = + _$WcSessionAuthRequestResultImpl; + + factory _WcSessionAuthRequestResult.fromJson(Map json) = + _$WcSessionAuthRequestResultImpl.fromJson; + + @override + List get cacaos; + @override + ConnectionMetadata get responder; + @override + @JsonKey(ignore: true) + _$$WcSessionAuthRequestResultImplCopyWith<_$WcSessionAuthRequestResultImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/reown_sign/lib/models/json_rpc_models.g.dart b/packages/reown_sign/lib/models/json_rpc_models.g.dart new file mode 100644 index 0000000..d739854 --- /dev/null +++ b/packages/reown_sign/lib/models/json_rpc_models.g.dart @@ -0,0 +1,312 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'json_rpc_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$WcPairingDeleteRequestImpl _$$WcPairingDeleteRequestImplFromJson( + Map json) => + _$WcPairingDeleteRequestImpl( + code: (json['code'] as num).toInt(), + message: json['message'] as String, + ); + +Map _$$WcPairingDeleteRequestImplToJson( + _$WcPairingDeleteRequestImpl instance) => + { + 'code': instance.code, + 'message': instance.message, + }; + +_$WcPairingPingRequestImpl _$$WcPairingPingRequestImplFromJson( + Map json) => + _$WcPairingPingRequestImpl( + data: json['data'] as Map, + ); + +Map _$$WcPairingPingRequestImplToJson( + _$WcPairingPingRequestImpl instance) => + { + 'data': instance.data, + }; + +_$WcSessionProposeRequestImpl _$$WcSessionProposeRequestImplFromJson( + Map json) => + _$WcSessionProposeRequestImpl( + relays: (json['relays'] as List) + .map((e) => Relay.fromJson(e as Map)) + .toList(), + requiredNamespaces: + (json['requiredNamespaces'] as Map).map( + (k, e) => + MapEntry(k, RequiredNamespace.fromJson(e as Map)), + ), + optionalNamespaces: + (json['optionalNamespaces'] as Map?)?.map( + (k, e) => + MapEntry(k, RequiredNamespace.fromJson(e as Map)), + ), + sessionProperties: + (json['sessionProperties'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + proposer: + ConnectionMetadata.fromJson(json['proposer'] as Map), + ); + +Map _$$WcSessionProposeRequestImplToJson( + _$WcSessionProposeRequestImpl instance) { + final val = { + 'relays': instance.relays.map((e) => e.toJson()).toList(), + 'requiredNamespaces': + instance.requiredNamespaces.map((k, e) => MapEntry(k, e.toJson())), + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('optionalNamespaces', + instance.optionalNamespaces?.map((k, e) => MapEntry(k, e.toJson()))); + writeNotNull('sessionProperties', instance.sessionProperties); + val['proposer'] = instance.proposer.toJson(); + return val; +} + +_$WcSessionProposeResponseImpl _$$WcSessionProposeResponseImplFromJson( + Map json) => + _$WcSessionProposeResponseImpl( + relay: Relay.fromJson(json['relay'] as Map), + responderPublicKey: json['responderPublicKey'] as String, + ); + +Map _$$WcSessionProposeResponseImplToJson( + _$WcSessionProposeResponseImpl instance) => + { + 'relay': instance.relay.toJson(), + 'responderPublicKey': instance.responderPublicKey, + }; + +_$WcSessionSettleRequestImpl _$$WcSessionSettleRequestImplFromJson( + Map json) => + _$WcSessionSettleRequestImpl( + relay: Relay.fromJson(json['relay'] as Map), + namespaces: (json['namespaces'] as Map).map( + (k, e) => MapEntry(k, Namespace.fromJson(e as Map)), + ), + requiredNamespaces: + (json['requiredNamespaces'] as Map?)?.map( + (k, e) => + MapEntry(k, RequiredNamespace.fromJson(e as Map)), + ), + optionalNamespaces: + (json['optionalNamespaces'] as Map?)?.map( + (k, e) => + MapEntry(k, RequiredNamespace.fromJson(e as Map)), + ), + sessionProperties: + (json['sessionProperties'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + expiry: (json['expiry'] as num).toInt(), + controller: ConnectionMetadata.fromJson( + json['controller'] as Map), + ); + +Map _$$WcSessionSettleRequestImplToJson( + _$WcSessionSettleRequestImpl instance) { + final val = { + 'relay': instance.relay.toJson(), + 'namespaces': instance.namespaces.map((k, e) => MapEntry(k, e.toJson())), + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('requiredNamespaces', + instance.requiredNamespaces?.map((k, e) => MapEntry(k, e.toJson()))); + writeNotNull('optionalNamespaces', + instance.optionalNamespaces?.map((k, e) => MapEntry(k, e.toJson()))); + writeNotNull('sessionProperties', instance.sessionProperties); + val['expiry'] = instance.expiry; + val['controller'] = instance.controller.toJson(); + return val; +} + +_$WcSessionUpdateRequestImpl _$$WcSessionUpdateRequestImplFromJson( + Map json) => + _$WcSessionUpdateRequestImpl( + namespaces: (json['namespaces'] as Map).map( + (k, e) => MapEntry(k, Namespace.fromJson(e as Map)), + ), + ); + +Map _$$WcSessionUpdateRequestImplToJson( + _$WcSessionUpdateRequestImpl instance) => + { + 'namespaces': instance.namespaces.map((k, e) => MapEntry(k, e.toJson())), + }; + +_$WcSessionExtendRequestImpl _$$WcSessionExtendRequestImplFromJson( + Map json) => + _$WcSessionExtendRequestImpl( + data: json['data'] as Map?, + ); + +Map _$$WcSessionExtendRequestImplToJson( + _$WcSessionExtendRequestImpl instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('data', instance.data); + return val; +} + +_$WcSessionDeleteRequestImpl _$$WcSessionDeleteRequestImplFromJson( + Map json) => + _$WcSessionDeleteRequestImpl( + code: (json['code'] as num).toInt(), + message: json['message'] as String, + data: json['data'] as String?, + ); + +Map _$$WcSessionDeleteRequestImplToJson( + _$WcSessionDeleteRequestImpl instance) { + final val = { + 'code': instance.code, + 'message': instance.message, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('data', instance.data); + return val; +} + +_$WcSessionPingRequestImpl _$$WcSessionPingRequestImplFromJson( + Map json) => + _$WcSessionPingRequestImpl( + data: json['data'] as Map?, + ); + +Map _$$WcSessionPingRequestImplToJson( + _$WcSessionPingRequestImpl instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('data', instance.data); + return val; +} + +_$WcSessionRequestRequestImpl _$$WcSessionRequestRequestImplFromJson( + Map json) => + _$WcSessionRequestRequestImpl( + chainId: json['chainId'] as String, + request: SessionRequestParams.fromJson( + json['request'] as Map), + ); + +Map _$$WcSessionRequestRequestImplToJson( + _$WcSessionRequestRequestImpl instance) => + { + 'chainId': instance.chainId, + 'request': instance.request.toJson(), + }; + +_$SessionRequestParamsImpl _$$SessionRequestParamsImplFromJson( + Map json) => + _$SessionRequestParamsImpl( + method: json['method'] as String, + params: json['params'], + ); + +Map _$$SessionRequestParamsImplToJson( + _$SessionRequestParamsImpl instance) => + { + 'method': instance.method, + 'params': instance.params, + }; + +_$WcSessionEventRequestImpl _$$WcSessionEventRequestImplFromJson( + Map json) => + _$WcSessionEventRequestImpl( + chainId: json['chainId'] as String, + event: SessionEventParams.fromJson(json['event'] as Map), + ); + +Map _$$WcSessionEventRequestImplToJson( + _$WcSessionEventRequestImpl instance) => + { + 'chainId': instance.chainId, + 'event': instance.event.toJson(), + }; + +_$SessionEventParamsImpl _$$SessionEventParamsImplFromJson( + Map json) => + _$SessionEventParamsImpl( + name: json['name'] as String, + data: json['data'], + ); + +Map _$$SessionEventParamsImplToJson( + _$SessionEventParamsImpl instance) => + { + 'name': instance.name, + 'data': instance.data, + }; + +_$WcSessionAuthRequestParamsImpl _$$WcSessionAuthRequestParamsImplFromJson( + Map json) => + _$WcSessionAuthRequestParamsImpl( + authPayload: SessionAuthPayload.fromJson( + json['authPayload'] as Map), + requester: ConnectionMetadata.fromJson( + json['requester'] as Map), + expiryTimestamp: (json['expiryTimestamp'] as num).toInt(), + ); + +Map _$$WcSessionAuthRequestParamsImplToJson( + _$WcSessionAuthRequestParamsImpl instance) => + { + 'authPayload': instance.authPayload.toJson(), + 'requester': instance.requester.toJson(), + 'expiryTimestamp': instance.expiryTimestamp, + }; + +_$WcSessionAuthRequestResultImpl _$$WcSessionAuthRequestResultImplFromJson( + Map json) => + _$WcSessionAuthRequestResultImpl( + cacaos: (json['cacaos'] as List) + .map((e) => Cacao.fromJson(e as Map)) + .toList(), + responder: ConnectionMetadata.fromJson( + json['responder'] as Map), + ); + +Map _$$WcSessionAuthRequestResultImplToJson( + _$WcSessionAuthRequestResultImpl instance) => + { + 'cacaos': instance.cacaos.map((e) => e.toJson()).toList(), + 'responder': instance.responder.toJson(), + }; diff --git a/packages/reown_sign/lib/models/proposal_models.dart b/packages/reown_sign/lib/models/proposal_models.dart new file mode 100644 index 0000000..cb2cc11 --- /dev/null +++ b/packages/reown_sign/lib/models/proposal_models.dart @@ -0,0 +1,51 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/models/session_models.dart'; + +part 'proposal_models.g.dart'; +part 'proposal_models.freezed.dart'; + +@freezed +class RequiredNamespace with _$RequiredNamespace { + @JsonSerializable(includeIfNull: false) + const factory RequiredNamespace({ + List? chains, + required List methods, + required List events, + }) = _RequiredNamespace; + + factory RequiredNamespace.fromJson(Map json) => + _$RequiredNamespaceFromJson(json); +} + +@freezed +class SessionProposal with _$SessionProposal { + @JsonSerializable() + const factory SessionProposal({ + required int id, + required ProposalData params, + }) = _SessionProposal; + + factory SessionProposal.fromJson(Map json) => + _$SessionProposalFromJson(json); +} + +@freezed +class ProposalData with _$ProposalData { + @JsonSerializable(includeIfNull: false) + const factory ProposalData({ + required int id, + required int expiry, + required List relays, + required ConnectionMetadata proposer, + required Map requiredNamespaces, + required Map optionalNamespaces, + required String pairingTopic, + Map? sessionProperties, + Map? generatedNamespaces, + }) = _ProposalData; + + factory ProposalData.fromJson(Map json) => + _$ProposalDataFromJson(json); +} diff --git a/packages/reown_sign/lib/models/proposal_models.freezed.dart b/packages/reown_sign/lib/models/proposal_models.freezed.dart new file mode 100644 index 0000000..02ba5bd --- /dev/null +++ b/packages/reown_sign/lib/models/proposal_models.freezed.dart @@ -0,0 +1,762 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'proposal_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +RequiredNamespace _$RequiredNamespaceFromJson(Map json) { + return _RequiredNamespace.fromJson(json); +} + +/// @nodoc +mixin _$RequiredNamespace { + List? get chains => throw _privateConstructorUsedError; + List get methods => throw _privateConstructorUsedError; + List get events => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $RequiredNamespaceCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RequiredNamespaceCopyWith<$Res> { + factory $RequiredNamespaceCopyWith( + RequiredNamespace value, $Res Function(RequiredNamespace) then) = + _$RequiredNamespaceCopyWithImpl<$Res, RequiredNamespace>; + @useResult + $Res call({List? chains, List methods, List events}); +} + +/// @nodoc +class _$RequiredNamespaceCopyWithImpl<$Res, $Val extends RequiredNamespace> + implements $RequiredNamespaceCopyWith<$Res> { + _$RequiredNamespaceCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chains = freezed, + Object? methods = null, + Object? events = null, + }) { + return _then(_value.copyWith( + chains: freezed == chains + ? _value.chains + : chains // ignore: cast_nullable_to_non_nullable + as List?, + methods: null == methods + ? _value.methods + : methods // ignore: cast_nullable_to_non_nullable + as List, + events: null == events + ? _value.events + : events // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RequiredNamespaceImplCopyWith<$Res> + implements $RequiredNamespaceCopyWith<$Res> { + factory _$$RequiredNamespaceImplCopyWith(_$RequiredNamespaceImpl value, + $Res Function(_$RequiredNamespaceImpl) then) = + __$$RequiredNamespaceImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List? chains, List methods, List events}); +} + +/// @nodoc +class __$$RequiredNamespaceImplCopyWithImpl<$Res> + extends _$RequiredNamespaceCopyWithImpl<$Res, _$RequiredNamespaceImpl> + implements _$$RequiredNamespaceImplCopyWith<$Res> { + __$$RequiredNamespaceImplCopyWithImpl(_$RequiredNamespaceImpl _value, + $Res Function(_$RequiredNamespaceImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chains = freezed, + Object? methods = null, + Object? events = null, + }) { + return _then(_$RequiredNamespaceImpl( + chains: freezed == chains + ? _value._chains + : chains // ignore: cast_nullable_to_non_nullable + as List?, + methods: null == methods + ? _value._methods + : methods // ignore: cast_nullable_to_non_nullable + as List, + events: null == events + ? _value._events + : events // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$RequiredNamespaceImpl implements _RequiredNamespace { + const _$RequiredNamespaceImpl( + {final List? chains, + required final List methods, + required final List events}) + : _chains = chains, + _methods = methods, + _events = events; + + factory _$RequiredNamespaceImpl.fromJson(Map json) => + _$$RequiredNamespaceImplFromJson(json); + + final List? _chains; + @override + List? get chains { + final value = _chains; + if (value == null) return null; + if (_chains is EqualUnmodifiableListView) return _chains; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List _methods; + @override + List get methods { + if (_methods is EqualUnmodifiableListView) return _methods; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_methods); + } + + final List _events; + @override + List get events { + if (_events is EqualUnmodifiableListView) return _events; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_events); + } + + @override + String toString() { + return 'RequiredNamespace(chains: $chains, methods: $methods, events: $events)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RequiredNamespaceImpl && + const DeepCollectionEquality().equals(other._chains, _chains) && + const DeepCollectionEquality().equals(other._methods, _methods) && + const DeepCollectionEquality().equals(other._events, _events)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_chains), + const DeepCollectionEquality().hash(_methods), + const DeepCollectionEquality().hash(_events)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RequiredNamespaceImplCopyWith<_$RequiredNamespaceImpl> get copyWith => + __$$RequiredNamespaceImplCopyWithImpl<_$RequiredNamespaceImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$RequiredNamespaceImplToJson( + this, + ); + } +} + +abstract class _RequiredNamespace implements RequiredNamespace { + const factory _RequiredNamespace( + {final List? chains, + required final List methods, + required final List events}) = _$RequiredNamespaceImpl; + + factory _RequiredNamespace.fromJson(Map json) = + _$RequiredNamespaceImpl.fromJson; + + @override + List? get chains; + @override + List get methods; + @override + List get events; + @override + @JsonKey(ignore: true) + _$$RequiredNamespaceImplCopyWith<_$RequiredNamespaceImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SessionProposal _$SessionProposalFromJson(Map json) { + return _SessionProposal.fromJson(json); +} + +/// @nodoc +mixin _$SessionProposal { + int get id => throw _privateConstructorUsedError; + ProposalData get params => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SessionProposalCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SessionProposalCopyWith<$Res> { + factory $SessionProposalCopyWith( + SessionProposal value, $Res Function(SessionProposal) then) = + _$SessionProposalCopyWithImpl<$Res, SessionProposal>; + @useResult + $Res call({int id, ProposalData params}); + + $ProposalDataCopyWith<$Res> get params; +} + +/// @nodoc +class _$SessionProposalCopyWithImpl<$Res, $Val extends SessionProposal> + implements $SessionProposalCopyWith<$Res> { + _$SessionProposalCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? params = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + params: null == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as ProposalData, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ProposalDataCopyWith<$Res> get params { + return $ProposalDataCopyWith<$Res>(_value.params, (value) { + return _then(_value.copyWith(params: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SessionProposalImplCopyWith<$Res> + implements $SessionProposalCopyWith<$Res> { + factory _$$SessionProposalImplCopyWith(_$SessionProposalImpl value, + $Res Function(_$SessionProposalImpl) then) = + __$$SessionProposalImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id, ProposalData params}); + + @override + $ProposalDataCopyWith<$Res> get params; +} + +/// @nodoc +class __$$SessionProposalImplCopyWithImpl<$Res> + extends _$SessionProposalCopyWithImpl<$Res, _$SessionProposalImpl> + implements _$$SessionProposalImplCopyWith<$Res> { + __$$SessionProposalImplCopyWithImpl( + _$SessionProposalImpl _value, $Res Function(_$SessionProposalImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? params = null, + }) { + return _then(_$SessionProposalImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + params: null == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as ProposalData, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$SessionProposalImpl implements _SessionProposal { + const _$SessionProposalImpl({required this.id, required this.params}); + + factory _$SessionProposalImpl.fromJson(Map json) => + _$$SessionProposalImplFromJson(json); + + @override + final int id; + @override + final ProposalData params; + + @override + String toString() { + return 'SessionProposal(id: $id, params: $params)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SessionProposalImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.params, params) || other.params == params)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, params); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SessionProposalImplCopyWith<_$SessionProposalImpl> get copyWith => + __$$SessionProposalImplCopyWithImpl<_$SessionProposalImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SessionProposalImplToJson( + this, + ); + } +} + +abstract class _SessionProposal implements SessionProposal { + const factory _SessionProposal( + {required final int id, + required final ProposalData params}) = _$SessionProposalImpl; + + factory _SessionProposal.fromJson(Map json) = + _$SessionProposalImpl.fromJson; + + @override + int get id; + @override + ProposalData get params; + @override + @JsonKey(ignore: true) + _$$SessionProposalImplCopyWith<_$SessionProposalImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ProposalData _$ProposalDataFromJson(Map json) { + return _ProposalData.fromJson(json); +} + +/// @nodoc +mixin _$ProposalData { + int get id => throw _privateConstructorUsedError; + int get expiry => throw _privateConstructorUsedError; + List get relays => throw _privateConstructorUsedError; + ConnectionMetadata get proposer => throw _privateConstructorUsedError; + Map get requiredNamespaces => + throw _privateConstructorUsedError; + Map get optionalNamespaces => + throw _privateConstructorUsedError; + String get pairingTopic => throw _privateConstructorUsedError; + Map? get sessionProperties => + throw _privateConstructorUsedError; + Map? get generatedNamespaces => + throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ProposalDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProposalDataCopyWith<$Res> { + factory $ProposalDataCopyWith( + ProposalData value, $Res Function(ProposalData) then) = + _$ProposalDataCopyWithImpl<$Res, ProposalData>; + @useResult + $Res call( + {int id, + int expiry, + List relays, + ConnectionMetadata proposer, + Map requiredNamespaces, + Map optionalNamespaces, + String pairingTopic, + Map? sessionProperties, + Map? generatedNamespaces}); + + $ConnectionMetadataCopyWith<$Res> get proposer; +} + +/// @nodoc +class _$ProposalDataCopyWithImpl<$Res, $Val extends ProposalData> + implements $ProposalDataCopyWith<$Res> { + _$ProposalDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? expiry = null, + Object? relays = null, + Object? proposer = null, + Object? requiredNamespaces = null, + Object? optionalNamespaces = null, + Object? pairingTopic = null, + Object? sessionProperties = freezed, + Object? generatedNamespaces = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + relays: null == relays + ? _value.relays + : relays // ignore: cast_nullable_to_non_nullable + as List, + proposer: null == proposer + ? _value.proposer + : proposer // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + requiredNamespaces: null == requiredNamespaces + ? _value.requiredNamespaces + : requiredNamespaces // ignore: cast_nullable_to_non_nullable + as Map, + optionalNamespaces: null == optionalNamespaces + ? _value.optionalNamespaces + : optionalNamespaces // ignore: cast_nullable_to_non_nullable + as Map, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + sessionProperties: freezed == sessionProperties + ? _value.sessionProperties + : sessionProperties // ignore: cast_nullable_to_non_nullable + as Map?, + generatedNamespaces: freezed == generatedNamespaces + ? _value.generatedNamespaces + : generatedNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get proposer { + return $ConnectionMetadataCopyWith<$Res>(_value.proposer, (value) { + return _then(_value.copyWith(proposer: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ProposalDataImplCopyWith<$Res> + implements $ProposalDataCopyWith<$Res> { + factory _$$ProposalDataImplCopyWith( + _$ProposalDataImpl value, $Res Function(_$ProposalDataImpl) then) = + __$$ProposalDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + int expiry, + List relays, + ConnectionMetadata proposer, + Map requiredNamespaces, + Map optionalNamespaces, + String pairingTopic, + Map? sessionProperties, + Map? generatedNamespaces}); + + @override + $ConnectionMetadataCopyWith<$Res> get proposer; +} + +/// @nodoc +class __$$ProposalDataImplCopyWithImpl<$Res> + extends _$ProposalDataCopyWithImpl<$Res, _$ProposalDataImpl> + implements _$$ProposalDataImplCopyWith<$Res> { + __$$ProposalDataImplCopyWithImpl( + _$ProposalDataImpl _value, $Res Function(_$ProposalDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? expiry = null, + Object? relays = null, + Object? proposer = null, + Object? requiredNamespaces = null, + Object? optionalNamespaces = null, + Object? pairingTopic = null, + Object? sessionProperties = freezed, + Object? generatedNamespaces = freezed, + }) { + return _then(_$ProposalDataImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + relays: null == relays + ? _value._relays + : relays // ignore: cast_nullable_to_non_nullable + as List, + proposer: null == proposer + ? _value.proposer + : proposer // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + requiredNamespaces: null == requiredNamespaces + ? _value._requiredNamespaces + : requiredNamespaces // ignore: cast_nullable_to_non_nullable + as Map, + optionalNamespaces: null == optionalNamespaces + ? _value._optionalNamespaces + : optionalNamespaces // ignore: cast_nullable_to_non_nullable + as Map, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + sessionProperties: freezed == sessionProperties + ? _value._sessionProperties + : sessionProperties // ignore: cast_nullable_to_non_nullable + as Map?, + generatedNamespaces: freezed == generatedNamespaces + ? _value._generatedNamespaces + : generatedNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$ProposalDataImpl implements _ProposalData { + const _$ProposalDataImpl( + {required this.id, + required this.expiry, + required final List relays, + required this.proposer, + required final Map requiredNamespaces, + required final Map optionalNamespaces, + required this.pairingTopic, + final Map? sessionProperties, + final Map? generatedNamespaces}) + : _relays = relays, + _requiredNamespaces = requiredNamespaces, + _optionalNamespaces = optionalNamespaces, + _sessionProperties = sessionProperties, + _generatedNamespaces = generatedNamespaces; + + factory _$ProposalDataImpl.fromJson(Map json) => + _$$ProposalDataImplFromJson(json); + + @override + final int id; + @override + final int expiry; + final List _relays; + @override + List get relays { + if (_relays is EqualUnmodifiableListView) return _relays; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_relays); + } + + @override + final ConnectionMetadata proposer; + final Map _requiredNamespaces; + @override + Map get requiredNamespaces { + if (_requiredNamespaces is EqualUnmodifiableMapView) + return _requiredNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_requiredNamespaces); + } + + final Map _optionalNamespaces; + @override + Map get optionalNamespaces { + if (_optionalNamespaces is EqualUnmodifiableMapView) + return _optionalNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_optionalNamespaces); + } + + @override + final String pairingTopic; + final Map? _sessionProperties; + @override + Map? get sessionProperties { + final value = _sessionProperties; + if (value == null) return null; + if (_sessionProperties is EqualUnmodifiableMapView) + return _sessionProperties; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final Map? _generatedNamespaces; + @override + Map? get generatedNamespaces { + final value = _generatedNamespaces; + if (value == null) return null; + if (_generatedNamespaces is EqualUnmodifiableMapView) + return _generatedNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + String toString() { + return 'ProposalData(id: $id, expiry: $expiry, relays: $relays, proposer: $proposer, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, pairingTopic: $pairingTopic, sessionProperties: $sessionProperties, generatedNamespaces: $generatedNamespaces)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProposalDataImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.expiry, expiry) || other.expiry == expiry) && + const DeepCollectionEquality().equals(other._relays, _relays) && + (identical(other.proposer, proposer) || + other.proposer == proposer) && + const DeepCollectionEquality() + .equals(other._requiredNamespaces, _requiredNamespaces) && + const DeepCollectionEquality() + .equals(other._optionalNamespaces, _optionalNamespaces) && + (identical(other.pairingTopic, pairingTopic) || + other.pairingTopic == pairingTopic) && + const DeepCollectionEquality() + .equals(other._sessionProperties, _sessionProperties) && + const DeepCollectionEquality() + .equals(other._generatedNamespaces, _generatedNamespaces)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + id, + expiry, + const DeepCollectionEquality().hash(_relays), + proposer, + const DeepCollectionEquality().hash(_requiredNamespaces), + const DeepCollectionEquality().hash(_optionalNamespaces), + pairingTopic, + const DeepCollectionEquality().hash(_sessionProperties), + const DeepCollectionEquality().hash(_generatedNamespaces)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ProposalDataImplCopyWith<_$ProposalDataImpl> get copyWith => + __$$ProposalDataImplCopyWithImpl<_$ProposalDataImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ProposalDataImplToJson( + this, + ); + } +} + +abstract class _ProposalData implements ProposalData { + const factory _ProposalData( + {required final int id, + required final int expiry, + required final List relays, + required final ConnectionMetadata proposer, + required final Map requiredNamespaces, + required final Map optionalNamespaces, + required final String pairingTopic, + final Map? sessionProperties, + final Map? generatedNamespaces}) = _$ProposalDataImpl; + + factory _ProposalData.fromJson(Map json) = + _$ProposalDataImpl.fromJson; + + @override + int get id; + @override + int get expiry; + @override + List get relays; + @override + ConnectionMetadata get proposer; + @override + Map get requiredNamespaces; + @override + Map get optionalNamespaces; + @override + String get pairingTopic; + @override + Map? get sessionProperties; + @override + Map? get generatedNamespaces; + @override + @JsonKey(ignore: true) + _$$ProposalDataImplCopyWith<_$ProposalDataImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_sign/lib/models/proposal_models.g.dart b/packages/reown_sign/lib/models/proposal_models.g.dart new file mode 100644 index 0000000..4cdacce --- /dev/null +++ b/packages/reown_sign/lib/models/proposal_models.g.dart @@ -0,0 +1,103 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'proposal_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$RequiredNamespaceImpl _$$RequiredNamespaceImplFromJson( + Map json) => + _$RequiredNamespaceImpl( + chains: + (json['chains'] as List?)?.map((e) => e as String).toList(), + methods: + (json['methods'] as List).map((e) => e as String).toList(), + events: + (json['events'] as List).map((e) => e as String).toList(), + ); + +Map _$$RequiredNamespaceImplToJson( + _$RequiredNamespaceImpl instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('chains', instance.chains); + val['methods'] = instance.methods; + val['events'] = instance.events; + return val; +} + +_$SessionProposalImpl _$$SessionProposalImplFromJson( + Map json) => + _$SessionProposalImpl( + id: (json['id'] as num).toInt(), + params: ProposalData.fromJson(json['params'] as Map), + ); + +Map _$$SessionProposalImplToJson( + _$SessionProposalImpl instance) => + { + 'id': instance.id, + 'params': instance.params.toJson(), + }; + +_$ProposalDataImpl _$$ProposalDataImplFromJson(Map json) => + _$ProposalDataImpl( + id: (json['id'] as num).toInt(), + expiry: (json['expiry'] as num).toInt(), + relays: (json['relays'] as List) + .map((e) => Relay.fromJson(e as Map)) + .toList(), + proposer: + ConnectionMetadata.fromJson(json['proposer'] as Map), + requiredNamespaces: + (json['requiredNamespaces'] as Map).map( + (k, e) => + MapEntry(k, RequiredNamespace.fromJson(e as Map)), + ), + optionalNamespaces: + (json['optionalNamespaces'] as Map).map( + (k, e) => + MapEntry(k, RequiredNamespace.fromJson(e as Map)), + ), + pairingTopic: json['pairingTopic'] as String, + sessionProperties: + (json['sessionProperties'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + generatedNamespaces: + (json['generatedNamespaces'] as Map?)?.map( + (k, e) => MapEntry(k, Namespace.fromJson(e as Map)), + ), + ); + +Map _$$ProposalDataImplToJson(_$ProposalDataImpl instance) { + final val = { + 'id': instance.id, + 'expiry': instance.expiry, + 'relays': instance.relays.map((e) => e.toJson()).toList(), + 'proposer': instance.proposer.toJson(), + 'requiredNamespaces': + instance.requiredNamespaces.map((k, e) => MapEntry(k, e.toJson())), + 'optionalNamespaces': + instance.optionalNamespaces.map((k, e) => MapEntry(k, e.toJson())), + 'pairingTopic': instance.pairingTopic, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('sessionProperties', instance.sessionProperties); + writeNotNull('generatedNamespaces', + instance.generatedNamespaces?.map((k, e) => MapEntry(k, e.toJson()))); + return val; +} diff --git a/packages/reown_sign/lib/models/session_auth_events.dart b/packages/reown_sign/lib/models/session_auth_events.dart new file mode 100644 index 0000000..485ed82 --- /dev/null +++ b/packages/reown_sign/lib/models/session_auth_events.dart @@ -0,0 +1,74 @@ +import 'dart:convert'; + +import 'package:event/event.dart'; + +import 'package:reown_core/reown_core.dart'; + +import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/models/cacao_models.dart'; +import 'package:reown_sign/models/session_models.dart'; +import 'package:reown_sign/models/session_auth_models.dart'; + +class SessionAuthRequest extends EventArgs { + final int id; + final String topic; + final SessionAuthPayload authPayload; + final ConnectionMetadata requester; + final TransportType transportType; + final VerifyContext? verifyContext; + + SessionAuthRequest({ + required this.id, + required this.topic, + required this.authPayload, + required this.requester, + this.transportType = TransportType.relay, + this.verifyContext, + }); + + Map toJson() => { + 'id': id, + 'topic': topic, + 'authPayload': authPayload.toJson(), + 'requester': requester.toJson(), + 'transportType': transportType.name, + if (verifyContext != null) 'verifyContext': verifyContext!.toJson(), + }; + + @override + String toString() { + return 'SessionAuthRequest(${jsonEncode(toJson())})'; + } +} + +class SessionAuthResponse extends EventArgs { + final int id; + final String topic; + final List? auths; + final SessionData? session; + final ReownSignError? error; + final JsonRpcError? jsonRpcError; + + SessionAuthResponse({ + required this.id, + required this.topic, + this.auths, + this.session, + this.error, + this.jsonRpcError, + }); + + Map toJson() => { + 'id': id, + 'topic': topic, + if (auths != null) 'auths': auths, + if (session != null) 'session': session!.toJson(), + if (error != null) 'error': error!.toJson(), + if (jsonRpcError != null) 'jsonRpcError': jsonRpcError!.toJson(), + }; + + @override + String toString() { + return 'SessionAuthResponse(${jsonEncode(toJson())})'; + } +} diff --git a/packages/reown_sign/lib/models/session_auth_models.dart b/packages/reown_sign/lib/models/session_auth_models.dart new file mode 100644 index 0000000..d246207 --- /dev/null +++ b/packages/reown_sign/lib/models/session_auth_models.dart @@ -0,0 +1,139 @@ +import 'dart:async'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/verify/models/verify_context.dart'; + +import 'package:reown_sign/models/cacao_models.dart'; +import 'package:reown_sign/models/json_rpc_models.dart'; +import 'package:reown_sign/models/session_auth_events.dart'; +import 'package:reown_sign/models/basic_models.dart'; + +part 'session_auth_models.g.dart'; +part 'session_auth_models.freezed.dart'; + +class SessionAuthRequestResponse { + final int id; + final String pairingTopic; + final Completer completer; + final Uri? uri; + + SessionAuthRequestResponse({ + required this.id, + required this.pairingTopic, + required this.completer, + this.uri, + }); +} + +@freezed +class SessionAuthRequestParams with _$SessionAuthRequestParams { + @JsonSerializable(includeIfNull: false) + const factory SessionAuthRequestParams({ + required List chains, + required String domain, + required String nonce, + required String uri, + // + CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + @Default([]) List? methods, + }) = _SessionAuthRequestParams; + // + factory SessionAuthRequestParams.fromJson(Map json) => + _$SessionAuthRequestParamsFromJson(json); +} + +@freezed +class SessionAuthPayload with _$SessionAuthPayload { + @JsonSerializable(includeIfNull: false) + const factory SessionAuthPayload({ + required List chains, + required String domain, + required String nonce, + required String aud, + required String type, + // + required String version, + required String iat, + // + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + }) = _SessionAuthPayload; + + factory SessionAuthPayload.fromRequestParams( + SessionAuthRequestParams params, + ) { + final now = DateTime.now(); + return SessionAuthPayload( + chains: params.chains, + domain: params.domain, + nonce: params.nonce, + aud: params.uri, + type: params.type?.t ?? 'eip4361', + version: '1', + iat: DateTime.utc( + now.year, + now.month, + now.day, + now.hour, + now.minute, + now.second, + now.millisecond, + ).toIso8601String(), + nbf: params.nbf, + exp: params.exp, + statement: params.statement, + requestId: params.requestId, + resources: params.resources, + ); + } + + factory SessionAuthPayload.fromJson(Map json) => + _$SessionAuthPayloadFromJson(json); +} + +@freezed +class PendingSessionAuthRequest with _$PendingSessionAuthRequest { + @JsonSerializable(includeIfNull: false) + const factory PendingSessionAuthRequest({ + required int id, + required String pairingTopic, + required ConnectionMetadata requester, + required int expiryTimestamp, + required CacaoRequestPayload authPayload, + required VerifyContext verifyContext, + @Default(TransportType.relay) TransportType transportType, + }) = _PendingSessionAuthRequest; + + factory PendingSessionAuthRequest.fromJson(Map json) => + _$PendingSessionAuthRequestFromJson(json); +} + +class SessionAuthenticateCompleter { + final int id; + final String pairingTopic; + final String responseTopic; + final String selfPublicKey; + final String? walletUniversalLink; + final WcSessionAuthRequestParams request; + final Completer completer; + + SessionAuthenticateCompleter({ + required this.id, + required this.pairingTopic, + required this.responseTopic, + required this.selfPublicKey, + required this.walletUniversalLink, + required this.request, + required this.completer, + }); +} diff --git a/packages/reown_sign/lib/models/session_auth_models.freezed.dart b/packages/reown_sign/lib/models/session_auth_models.freezed.dart new file mode 100644 index 0000000..df31a0d --- /dev/null +++ b/packages/reown_sign/lib/models/session_auth_models.freezed.dart @@ -0,0 +1,1146 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'session_auth_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +SessionAuthRequestParams _$SessionAuthRequestParamsFromJson( + Map json) { + return _SessionAuthRequestParams.fromJson(json); +} + +/// @nodoc +mixin _$SessionAuthRequestParams { + List get chains => throw _privateConstructorUsedError; + String get domain => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + String get uri => throw _privateConstructorUsedError; // + CacaoHeader? get type => throw _privateConstructorUsedError; + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + int? get expiry => throw _privateConstructorUsedError; + List? get methods => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SessionAuthRequestParamsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SessionAuthRequestParamsCopyWith<$Res> { + factory $SessionAuthRequestParamsCopyWith(SessionAuthRequestParams value, + $Res Function(SessionAuthRequestParams) then) = + _$SessionAuthRequestParamsCopyWithImpl<$Res, SessionAuthRequestParams>; + @useResult + $Res call( + {List chains, + String domain, + String nonce, + String uri, + CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + List? methods}); + + $CacaoHeaderCopyWith<$Res>? get type; +} + +/// @nodoc +class _$SessionAuthRequestParamsCopyWithImpl<$Res, + $Val extends SessionAuthRequestParams> + implements $SessionAuthRequestParamsCopyWith<$Res> { + _$SessionAuthRequestParamsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chains = null, + Object? domain = null, + Object? nonce = null, + Object? uri = null, + Object? type = freezed, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + Object? expiry = freezed, + Object? methods = freezed, + }) { + return _then(_value.copyWith( + chains: null == chains + ? _value.chains + : chains // ignore: cast_nullable_to_non_nullable + as List, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + uri: null == uri + ? _value.uri + : uri // ignore: cast_nullable_to_non_nullable + as String, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as CacaoHeader?, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + expiry: freezed == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int?, + methods: freezed == methods + ? _value.methods + : methods // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $CacaoHeaderCopyWith<$Res>? get type { + if (_value.type == null) { + return null; + } + + return $CacaoHeaderCopyWith<$Res>(_value.type!, (value) { + return _then(_value.copyWith(type: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SessionAuthRequestParamsImplCopyWith<$Res> + implements $SessionAuthRequestParamsCopyWith<$Res> { + factory _$$SessionAuthRequestParamsImplCopyWith( + _$SessionAuthRequestParamsImpl value, + $Res Function(_$SessionAuthRequestParamsImpl) then) = + __$$SessionAuthRequestParamsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List chains, + String domain, + String nonce, + String uri, + CacaoHeader? type, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources, + int? expiry, + List? methods}); + + @override + $CacaoHeaderCopyWith<$Res>? get type; +} + +/// @nodoc +class __$$SessionAuthRequestParamsImplCopyWithImpl<$Res> + extends _$SessionAuthRequestParamsCopyWithImpl<$Res, + _$SessionAuthRequestParamsImpl> + implements _$$SessionAuthRequestParamsImplCopyWith<$Res> { + __$$SessionAuthRequestParamsImplCopyWithImpl( + _$SessionAuthRequestParamsImpl _value, + $Res Function(_$SessionAuthRequestParamsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chains = null, + Object? domain = null, + Object? nonce = null, + Object? uri = null, + Object? type = freezed, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + Object? expiry = freezed, + Object? methods = freezed, + }) { + return _then(_$SessionAuthRequestParamsImpl( + chains: null == chains + ? _value._chains + : chains // ignore: cast_nullable_to_non_nullable + as List, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + uri: null == uri + ? _value.uri + : uri // ignore: cast_nullable_to_non_nullable + as String, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as CacaoHeader?, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + expiry: freezed == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int?, + methods: freezed == methods + ? _value._methods + : methods // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$SessionAuthRequestParamsImpl implements _SessionAuthRequestParams { + const _$SessionAuthRequestParamsImpl( + {required final List chains, + required this.domain, + required this.nonce, + required this.uri, + this.type, + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources, + this.expiry, + final List? methods = const []}) + : _chains = chains, + _resources = resources, + _methods = methods; + + factory _$SessionAuthRequestParamsImpl.fromJson(Map json) => + _$$SessionAuthRequestParamsImplFromJson(json); + + final List _chains; + @override + List get chains { + if (_chains is EqualUnmodifiableListView) return _chains; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_chains); + } + + @override + final String domain; + @override + final String nonce; + @override + final String uri; +// + @override + final CacaoHeader? type; + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + final int? expiry; + final List? _methods; + @override + @JsonKey() + List? get methods { + final value = _methods; + if (value == null) return null; + if (_methods is EqualUnmodifiableListView) return _methods; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'SessionAuthRequestParams(chains: $chains, domain: $domain, nonce: $nonce, uri: $uri, type: $type, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources, expiry: $expiry, methods: $methods)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SessionAuthRequestParamsImpl && + const DeepCollectionEquality().equals(other._chains, _chains) && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.uri, uri) || other.uri == uri) && + (identical(other.type, type) || other.type == type) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources) && + (identical(other.expiry, expiry) || other.expiry == expiry) && + const DeepCollectionEquality().equals(other._methods, _methods)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_chains), + domain, + nonce, + uri, + type, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources), + expiry, + const DeepCollectionEquality().hash(_methods)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SessionAuthRequestParamsImplCopyWith<_$SessionAuthRequestParamsImpl> + get copyWith => __$$SessionAuthRequestParamsImplCopyWithImpl< + _$SessionAuthRequestParamsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SessionAuthRequestParamsImplToJson( + this, + ); + } +} + +abstract class _SessionAuthRequestParams implements SessionAuthRequestParams { + const factory _SessionAuthRequestParams( + {required final List chains, + required final String domain, + required final String nonce, + required final String uri, + final CacaoHeader? type, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources, + final int? expiry, + final List? methods}) = _$SessionAuthRequestParamsImpl; + + factory _SessionAuthRequestParams.fromJson(Map json) = + _$SessionAuthRequestParamsImpl.fromJson; + + @override + List get chains; + @override + String get domain; + @override + String get nonce; + @override + String get uri; + @override // + CacaoHeader? get type; + @override + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + int? get expiry; + @override + List? get methods; + @override + @JsonKey(ignore: true) + _$$SessionAuthRequestParamsImplCopyWith<_$SessionAuthRequestParamsImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SessionAuthPayload _$SessionAuthPayloadFromJson(Map json) { + return _SessionAuthPayload.fromJson(json); +} + +/// @nodoc +mixin _$SessionAuthPayload { + List get chains => throw _privateConstructorUsedError; + String get domain => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + String get aud => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; // + String get version => throw _privateConstructorUsedError; + String get iat => throw _privateConstructorUsedError; // + String? get nbf => throw _privateConstructorUsedError; + String? get exp => throw _privateConstructorUsedError; + String? get statement => throw _privateConstructorUsedError; + String? get requestId => throw _privateConstructorUsedError; + List? get resources => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SessionAuthPayloadCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SessionAuthPayloadCopyWith<$Res> { + factory $SessionAuthPayloadCopyWith( + SessionAuthPayload value, $Res Function(SessionAuthPayload) then) = + _$SessionAuthPayloadCopyWithImpl<$Res, SessionAuthPayload>; + @useResult + $Res call( + {List chains, + String domain, + String nonce, + String aud, + String type, + String version, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class _$SessionAuthPayloadCopyWithImpl<$Res, $Val extends SessionAuthPayload> + implements $SessionAuthPayloadCopyWith<$Res> { + _$SessionAuthPayloadCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chains = null, + Object? domain = null, + Object? nonce = null, + Object? aud = null, + Object? type = null, + Object? version = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_value.copyWith( + chains: null == chains + ? _value.chains + : chains // ignore: cast_nullable_to_non_nullable + as List, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value.resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SessionAuthPayloadImplCopyWith<$Res> + implements $SessionAuthPayloadCopyWith<$Res> { + factory _$$SessionAuthPayloadImplCopyWith(_$SessionAuthPayloadImpl value, + $Res Function(_$SessionAuthPayloadImpl) then) = + __$$SessionAuthPayloadImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List chains, + String domain, + String nonce, + String aud, + String type, + String version, + String iat, + String? nbf, + String? exp, + String? statement, + String? requestId, + List? resources}); +} + +/// @nodoc +class __$$SessionAuthPayloadImplCopyWithImpl<$Res> + extends _$SessionAuthPayloadCopyWithImpl<$Res, _$SessionAuthPayloadImpl> + implements _$$SessionAuthPayloadImplCopyWith<$Res> { + __$$SessionAuthPayloadImplCopyWithImpl(_$SessionAuthPayloadImpl _value, + $Res Function(_$SessionAuthPayloadImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chains = null, + Object? domain = null, + Object? nonce = null, + Object? aud = null, + Object? type = null, + Object? version = null, + Object? iat = null, + Object? nbf = freezed, + Object? exp = freezed, + Object? statement = freezed, + Object? requestId = freezed, + Object? resources = freezed, + }) { + return _then(_$SessionAuthPayloadImpl( + chains: null == chains + ? _value._chains + : chains // ignore: cast_nullable_to_non_nullable + as List, + domain: null == domain + ? _value.domain + : domain // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + aud: null == aud + ? _value.aud + : aud // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + iat: null == iat + ? _value.iat + : iat // ignore: cast_nullable_to_non_nullable + as String, + nbf: freezed == nbf + ? _value.nbf + : nbf // ignore: cast_nullable_to_non_nullable + as String?, + exp: freezed == exp + ? _value.exp + : exp // ignore: cast_nullable_to_non_nullable + as String?, + statement: freezed == statement + ? _value.statement + : statement // ignore: cast_nullable_to_non_nullable + as String?, + requestId: freezed == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String?, + resources: freezed == resources + ? _value._resources + : resources // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$SessionAuthPayloadImpl implements _SessionAuthPayload { + const _$SessionAuthPayloadImpl( + {required final List chains, + required this.domain, + required this.nonce, + required this.aud, + required this.type, + required this.version, + required this.iat, + this.nbf, + this.exp, + this.statement, + this.requestId, + final List? resources}) + : _chains = chains, + _resources = resources; + + factory _$SessionAuthPayloadImpl.fromJson(Map json) => + _$$SessionAuthPayloadImplFromJson(json); + + final List _chains; + @override + List get chains { + if (_chains is EqualUnmodifiableListView) return _chains; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_chains); + } + + @override + final String domain; + @override + final String nonce; + @override + final String aud; + @override + final String type; +// + @override + final String version; + @override + final String iat; +// + @override + final String? nbf; + @override + final String? exp; + @override + final String? statement; + @override + final String? requestId; + final List? _resources; + @override + List? get resources { + final value = _resources; + if (value == null) return null; + if (_resources is EqualUnmodifiableListView) return _resources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'SessionAuthPayload(chains: $chains, domain: $domain, nonce: $nonce, aud: $aud, type: $type, version: $version, iat: $iat, nbf: $nbf, exp: $exp, statement: $statement, requestId: $requestId, resources: $resources)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SessionAuthPayloadImpl && + const DeepCollectionEquality().equals(other._chains, _chains) && + (identical(other.domain, domain) || other.domain == domain) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.aud, aud) || other.aud == aud) && + (identical(other.type, type) || other.type == type) && + (identical(other.version, version) || other.version == version) && + (identical(other.iat, iat) || other.iat == iat) && + (identical(other.nbf, nbf) || other.nbf == nbf) && + (identical(other.exp, exp) || other.exp == exp) && + (identical(other.statement, statement) || + other.statement == statement) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && + const DeepCollectionEquality() + .equals(other._resources, _resources)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_chains), + domain, + nonce, + aud, + type, + version, + iat, + nbf, + exp, + statement, + requestId, + const DeepCollectionEquality().hash(_resources)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SessionAuthPayloadImplCopyWith<_$SessionAuthPayloadImpl> get copyWith => + __$$SessionAuthPayloadImplCopyWithImpl<_$SessionAuthPayloadImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SessionAuthPayloadImplToJson( + this, + ); + } +} + +abstract class _SessionAuthPayload implements SessionAuthPayload { + const factory _SessionAuthPayload( + {required final List chains, + required final String domain, + required final String nonce, + required final String aud, + required final String type, + required final String version, + required final String iat, + final String? nbf, + final String? exp, + final String? statement, + final String? requestId, + final List? resources}) = _$SessionAuthPayloadImpl; + + factory _SessionAuthPayload.fromJson(Map json) = + _$SessionAuthPayloadImpl.fromJson; + + @override + List get chains; + @override + String get domain; + @override + String get nonce; + @override + String get aud; + @override + String get type; + @override // + String get version; + @override + String get iat; + @override // + String? get nbf; + @override + String? get exp; + @override + String? get statement; + @override + String? get requestId; + @override + List? get resources; + @override + @JsonKey(ignore: true) + _$$SessionAuthPayloadImplCopyWith<_$SessionAuthPayloadImpl> get copyWith => + throw _privateConstructorUsedError; +} + +PendingSessionAuthRequest _$PendingSessionAuthRequestFromJson( + Map json) { + return _PendingSessionAuthRequest.fromJson(json); +} + +/// @nodoc +mixin _$PendingSessionAuthRequest { + int get id => throw _privateConstructorUsedError; + String get pairingTopic => throw _privateConstructorUsedError; + ConnectionMetadata get requester => throw _privateConstructorUsedError; + int get expiryTimestamp => throw _privateConstructorUsedError; + CacaoRequestPayload get authPayload => throw _privateConstructorUsedError; + VerifyContext get verifyContext => throw _privateConstructorUsedError; + TransportType get transportType => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PendingSessionAuthRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PendingSessionAuthRequestCopyWith<$Res> { + factory $PendingSessionAuthRequestCopyWith(PendingSessionAuthRequest value, + $Res Function(PendingSessionAuthRequest) then) = + _$PendingSessionAuthRequestCopyWithImpl<$Res, PendingSessionAuthRequest>; + @useResult + $Res call( + {int id, + String pairingTopic, + ConnectionMetadata requester, + int expiryTimestamp, + CacaoRequestPayload authPayload, + VerifyContext verifyContext, + TransportType transportType}); + + $ConnectionMetadataCopyWith<$Res> get requester; + $CacaoRequestPayloadCopyWith<$Res> get authPayload; + $VerifyContextCopyWith<$Res> get verifyContext; +} + +/// @nodoc +class _$PendingSessionAuthRequestCopyWithImpl<$Res, + $Val extends PendingSessionAuthRequest> + implements $PendingSessionAuthRequestCopyWith<$Res> { + _$PendingSessionAuthRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? pairingTopic = null, + Object? requester = null, + Object? expiryTimestamp = null, + Object? authPayload = null, + Object? verifyContext = null, + Object? transportType = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + requester: null == requester + ? _value.requester + : requester // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + expiryTimestamp: null == expiryTimestamp + ? _value.expiryTimestamp + : expiryTimestamp // ignore: cast_nullable_to_non_nullable + as int, + authPayload: null == authPayload + ? _value.authPayload + : authPayload // ignore: cast_nullable_to_non_nullable + as CacaoRequestPayload, + verifyContext: null == verifyContext + ? _value.verifyContext + : verifyContext // ignore: cast_nullable_to_non_nullable + as VerifyContext, + transportType: null == transportType + ? _value.transportType + : transportType // ignore: cast_nullable_to_non_nullable + as TransportType, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get requester { + return $ConnectionMetadataCopyWith<$Res>(_value.requester, (value) { + return _then(_value.copyWith(requester: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $CacaoRequestPayloadCopyWith<$Res> get authPayload { + return $CacaoRequestPayloadCopyWith<$Res>(_value.authPayload, (value) { + return _then(_value.copyWith(authPayload: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $VerifyContextCopyWith<$Res> get verifyContext { + return $VerifyContextCopyWith<$Res>(_value.verifyContext, (value) { + return _then(_value.copyWith(verifyContext: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$PendingSessionAuthRequestImplCopyWith<$Res> + implements $PendingSessionAuthRequestCopyWith<$Res> { + factory _$$PendingSessionAuthRequestImplCopyWith( + _$PendingSessionAuthRequestImpl value, + $Res Function(_$PendingSessionAuthRequestImpl) then) = + __$$PendingSessionAuthRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String pairingTopic, + ConnectionMetadata requester, + int expiryTimestamp, + CacaoRequestPayload authPayload, + VerifyContext verifyContext, + TransportType transportType}); + + @override + $ConnectionMetadataCopyWith<$Res> get requester; + @override + $CacaoRequestPayloadCopyWith<$Res> get authPayload; + @override + $VerifyContextCopyWith<$Res> get verifyContext; +} + +/// @nodoc +class __$$PendingSessionAuthRequestImplCopyWithImpl<$Res> + extends _$PendingSessionAuthRequestCopyWithImpl<$Res, + _$PendingSessionAuthRequestImpl> + implements _$$PendingSessionAuthRequestImplCopyWith<$Res> { + __$$PendingSessionAuthRequestImplCopyWithImpl( + _$PendingSessionAuthRequestImpl _value, + $Res Function(_$PendingSessionAuthRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? pairingTopic = null, + Object? requester = null, + Object? expiryTimestamp = null, + Object? authPayload = null, + Object? verifyContext = null, + Object? transportType = null, + }) { + return _then(_$PendingSessionAuthRequestImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + requester: null == requester + ? _value.requester + : requester // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + expiryTimestamp: null == expiryTimestamp + ? _value.expiryTimestamp + : expiryTimestamp // ignore: cast_nullable_to_non_nullable + as int, + authPayload: null == authPayload + ? _value.authPayload + : authPayload // ignore: cast_nullable_to_non_nullable + as CacaoRequestPayload, + verifyContext: null == verifyContext + ? _value.verifyContext + : verifyContext // ignore: cast_nullable_to_non_nullable + as VerifyContext, + transportType: null == transportType + ? _value.transportType + : transportType // ignore: cast_nullable_to_non_nullable + as TransportType, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$PendingSessionAuthRequestImpl implements _PendingSessionAuthRequest { + const _$PendingSessionAuthRequestImpl( + {required this.id, + required this.pairingTopic, + required this.requester, + required this.expiryTimestamp, + required this.authPayload, + required this.verifyContext, + this.transportType = TransportType.relay}); + + factory _$PendingSessionAuthRequestImpl.fromJson(Map json) => + _$$PendingSessionAuthRequestImplFromJson(json); + + @override + final int id; + @override + final String pairingTopic; + @override + final ConnectionMetadata requester; + @override + final int expiryTimestamp; + @override + final CacaoRequestPayload authPayload; + @override + final VerifyContext verifyContext; + @override + @JsonKey() + final TransportType transportType; + + @override + String toString() { + return 'PendingSessionAuthRequest(id: $id, pairingTopic: $pairingTopic, requester: $requester, expiryTimestamp: $expiryTimestamp, authPayload: $authPayload, verifyContext: $verifyContext, transportType: $transportType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PendingSessionAuthRequestImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.pairingTopic, pairingTopic) || + other.pairingTopic == pairingTopic) && + (identical(other.requester, requester) || + other.requester == requester) && + (identical(other.expiryTimestamp, expiryTimestamp) || + other.expiryTimestamp == expiryTimestamp) && + (identical(other.authPayload, authPayload) || + other.authPayload == authPayload) && + (identical(other.verifyContext, verifyContext) || + other.verifyContext == verifyContext) && + (identical(other.transportType, transportType) || + other.transportType == transportType)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, pairingTopic, requester, + expiryTimestamp, authPayload, verifyContext, transportType); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PendingSessionAuthRequestImplCopyWith<_$PendingSessionAuthRequestImpl> + get copyWith => __$$PendingSessionAuthRequestImplCopyWithImpl< + _$PendingSessionAuthRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$PendingSessionAuthRequestImplToJson( + this, + ); + } +} + +abstract class _PendingSessionAuthRequest implements PendingSessionAuthRequest { + const factory _PendingSessionAuthRequest( + {required final int id, + required final String pairingTopic, + required final ConnectionMetadata requester, + required final int expiryTimestamp, + required final CacaoRequestPayload authPayload, + required final VerifyContext verifyContext, + final TransportType transportType}) = _$PendingSessionAuthRequestImpl; + + factory _PendingSessionAuthRequest.fromJson(Map json) = + _$PendingSessionAuthRequestImpl.fromJson; + + @override + int get id; + @override + String get pairingTopic; + @override + ConnectionMetadata get requester; + @override + int get expiryTimestamp; + @override + CacaoRequestPayload get authPayload; + @override + VerifyContext get verifyContext; + @override + TransportType get transportType; + @override + @JsonKey(ignore: true) + _$$PendingSessionAuthRequestImplCopyWith<_$PendingSessionAuthRequestImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/reown_sign/lib/models/session_auth_models.g.dart b/packages/reown_sign/lib/models/session_auth_models.g.dart new file mode 100644 index 0000000..7b5a5ab --- /dev/null +++ b/packages/reown_sign/lib/models/session_auth_models.g.dart @@ -0,0 +1,138 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'session_auth_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SessionAuthRequestParamsImpl _$$SessionAuthRequestParamsImplFromJson( + Map json) => + _$SessionAuthRequestParamsImpl( + chains: + (json['chains'] as List).map((e) => e as String).toList(), + domain: json['domain'] as String, + nonce: json['nonce'] as String, + uri: json['uri'] as String, + type: json['type'] == null + ? null + : CacaoHeader.fromJson(json['type'] as Map), + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + expiry: (json['expiry'] as num?)?.toInt(), + methods: (json['methods'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + ); + +Map _$$SessionAuthRequestParamsImplToJson( + _$SessionAuthRequestParamsImpl instance) { + final val = { + 'chains': instance.chains, + 'domain': instance.domain, + 'nonce': instance.nonce, + 'uri': instance.uri, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('type', instance.type?.toJson()); + writeNotNull('nbf', instance.nbf); + writeNotNull('exp', instance.exp); + writeNotNull('statement', instance.statement); + writeNotNull('requestId', instance.requestId); + writeNotNull('resources', instance.resources); + writeNotNull('expiry', instance.expiry); + writeNotNull('methods', instance.methods); + return val; +} + +_$SessionAuthPayloadImpl _$$SessionAuthPayloadImplFromJson( + Map json) => + _$SessionAuthPayloadImpl( + chains: + (json['chains'] as List).map((e) => e as String).toList(), + domain: json['domain'] as String, + nonce: json['nonce'] as String, + aud: json['aud'] as String, + type: json['type'] as String, + version: json['version'] as String, + iat: json['iat'] as String, + nbf: json['nbf'] as String?, + exp: json['exp'] as String?, + statement: json['statement'] as String?, + requestId: json['requestId'] as String?, + resources: (json['resources'] as List?) + ?.map((e) => e as String) + .toList(), + ); + +Map _$$SessionAuthPayloadImplToJson( + _$SessionAuthPayloadImpl instance) { + final val = { + 'chains': instance.chains, + 'domain': instance.domain, + 'nonce': instance.nonce, + 'aud': instance.aud, + 'type': instance.type, + 'version': instance.version, + 'iat': instance.iat, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('nbf', instance.nbf); + writeNotNull('exp', instance.exp); + writeNotNull('statement', instance.statement); + writeNotNull('requestId', instance.requestId); + writeNotNull('resources', instance.resources); + return val; +} + +_$PendingSessionAuthRequestImpl _$$PendingSessionAuthRequestImplFromJson( + Map json) => + _$PendingSessionAuthRequestImpl( + id: (json['id'] as num).toInt(), + pairingTopic: json['pairingTopic'] as String, + requester: ConnectionMetadata.fromJson( + json['requester'] as Map), + expiryTimestamp: (json['expiryTimestamp'] as num).toInt(), + authPayload: CacaoRequestPayload.fromJson( + json['authPayload'] as Map), + verifyContext: + VerifyContext.fromJson(json['verifyContext'] as Map), + transportType: + $enumDecodeNullable(_$TransportTypeEnumMap, json['transportType']) ?? + TransportType.relay, + ); + +Map _$$PendingSessionAuthRequestImplToJson( + _$PendingSessionAuthRequestImpl instance) => + { + 'id': instance.id, + 'pairingTopic': instance.pairingTopic, + 'requester': instance.requester.toJson(), + 'expiryTimestamp': instance.expiryTimestamp, + 'authPayload': instance.authPayload.toJson(), + 'verifyContext': instance.verifyContext.toJson(), + 'transportType': _$TransportTypeEnumMap[instance.transportType]!, + }; + +const _$TransportTypeEnumMap = { + TransportType.relay: 'relay', + TransportType.linkMode: 'linkMode', +}; diff --git a/packages/reown_sign/lib/models/session_models.dart b/packages/reown_sign/lib/models/session_models.dart new file mode 100644 index 0000000..028872a --- /dev/null +++ b/packages/reown_sign/lib/models/session_models.dart @@ -0,0 +1,91 @@ +import 'dart:async'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/verify/models/verify_context.dart'; +import 'package:reown_sign/models/cacao_models.dart'; +import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/models/proposal_models.dart'; + +part 'session_models.g.dart'; +part 'session_models.freezed.dart'; + +class SessionProposalCompleter { + final int id; + final String selfPublicKey; + final String pairingTopic; + final Map requiredNamespaces; + final Map optionalNamespaces; + final Map? sessionProperties; + final Completer completer; + + const SessionProposalCompleter({ + required this.id, + required this.selfPublicKey, + required this.pairingTopic, + required this.requiredNamespaces, + required this.optionalNamespaces, + required this.completer, + this.sessionProperties, + }); + + @override + String toString() { + return 'SessionProposalCompleter(id: $id, selfPublicKey: $selfPublicKey, pairingTopic: $pairingTopic, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, completer: $completer)'; + } +} + +@freezed +class Namespace with _$Namespace { + @JsonSerializable(includeIfNull: false) + const factory Namespace({ + List? chains, + required List accounts, + required List methods, + required List events, + }) = _Namespace; + + factory Namespace.fromJson(Map json) => + _$NamespaceFromJson(json); +} + +@freezed +class SessionData with _$SessionData { + @JsonSerializable(includeIfNull: false) + const factory SessionData({ + required String topic, + required String pairingTopic, + required Relay relay, + required int expiry, + required bool acknowledged, + required String controller, + required Map namespaces, + required ConnectionMetadata self, + required ConnectionMetadata peer, + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + List? authentication, + @Default(TransportType.relay) TransportType transportType, + }) = _SessionData; + + factory SessionData.fromJson(Map json) => + _$SessionDataFromJson(json); +} + +@freezed +class SessionRequest with _$SessionRequest { + @JsonSerializable() + const factory SessionRequest({ + required int id, + required String topic, + required String method, + required String chainId, + required dynamic params, + required VerifyContext verifyContext, + @Default(TransportType.relay) TransportType transportType, + }) = _SessionRequest; + + factory SessionRequest.fromJson(Map json) => + _$SessionRequestFromJson(json); +} diff --git a/packages/reown_sign/lib/models/session_models.freezed.dart b/packages/reown_sign/lib/models/session_models.freezed.dart new file mode 100644 index 0000000..acfadb0 --- /dev/null +++ b/packages/reown_sign/lib/models/session_models.freezed.dart @@ -0,0 +1,1034 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'session_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Namespace _$NamespaceFromJson(Map json) { + return _Namespace.fromJson(json); +} + +/// @nodoc +mixin _$Namespace { + List? get chains => throw _privateConstructorUsedError; + List get accounts => throw _privateConstructorUsedError; + List get methods => throw _privateConstructorUsedError; + List get events => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $NamespaceCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NamespaceCopyWith<$Res> { + factory $NamespaceCopyWith(Namespace value, $Res Function(Namespace) then) = + _$NamespaceCopyWithImpl<$Res, Namespace>; + @useResult + $Res call( + {List? chains, + List accounts, + List methods, + List events}); +} + +/// @nodoc +class _$NamespaceCopyWithImpl<$Res, $Val extends Namespace> + implements $NamespaceCopyWith<$Res> { + _$NamespaceCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chains = freezed, + Object? accounts = null, + Object? methods = null, + Object? events = null, + }) { + return _then(_value.copyWith( + chains: freezed == chains + ? _value.chains + : chains // ignore: cast_nullable_to_non_nullable + as List?, + accounts: null == accounts + ? _value.accounts + : accounts // ignore: cast_nullable_to_non_nullable + as List, + methods: null == methods + ? _value.methods + : methods // ignore: cast_nullable_to_non_nullable + as List, + events: null == events + ? _value.events + : events // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$NamespaceImplCopyWith<$Res> + implements $NamespaceCopyWith<$Res> { + factory _$$NamespaceImplCopyWith( + _$NamespaceImpl value, $Res Function(_$NamespaceImpl) then) = + __$$NamespaceImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List? chains, + List accounts, + List methods, + List events}); +} + +/// @nodoc +class __$$NamespaceImplCopyWithImpl<$Res> + extends _$NamespaceCopyWithImpl<$Res, _$NamespaceImpl> + implements _$$NamespaceImplCopyWith<$Res> { + __$$NamespaceImplCopyWithImpl( + _$NamespaceImpl _value, $Res Function(_$NamespaceImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chains = freezed, + Object? accounts = null, + Object? methods = null, + Object? events = null, + }) { + return _then(_$NamespaceImpl( + chains: freezed == chains + ? _value._chains + : chains // ignore: cast_nullable_to_non_nullable + as List?, + accounts: null == accounts + ? _value._accounts + : accounts // ignore: cast_nullable_to_non_nullable + as List, + methods: null == methods + ? _value._methods + : methods // ignore: cast_nullable_to_non_nullable + as List, + events: null == events + ? _value._events + : events // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$NamespaceImpl implements _Namespace { + const _$NamespaceImpl( + {final List? chains, + required final List accounts, + required final List methods, + required final List events}) + : _chains = chains, + _accounts = accounts, + _methods = methods, + _events = events; + + factory _$NamespaceImpl.fromJson(Map json) => + _$$NamespaceImplFromJson(json); + + final List? _chains; + @override + List? get chains { + final value = _chains; + if (value == null) return null; + if (_chains is EqualUnmodifiableListView) return _chains; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List _accounts; + @override + List get accounts { + if (_accounts is EqualUnmodifiableListView) return _accounts; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_accounts); + } + + final List _methods; + @override + List get methods { + if (_methods is EqualUnmodifiableListView) return _methods; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_methods); + } + + final List _events; + @override + List get events { + if (_events is EqualUnmodifiableListView) return _events; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_events); + } + + @override + String toString() { + return 'Namespace(chains: $chains, accounts: $accounts, methods: $methods, events: $events)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NamespaceImpl && + const DeepCollectionEquality().equals(other._chains, _chains) && + const DeepCollectionEquality().equals(other._accounts, _accounts) && + const DeepCollectionEquality().equals(other._methods, _methods) && + const DeepCollectionEquality().equals(other._events, _events)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_chains), + const DeepCollectionEquality().hash(_accounts), + const DeepCollectionEquality().hash(_methods), + const DeepCollectionEquality().hash(_events)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NamespaceImplCopyWith<_$NamespaceImpl> get copyWith => + __$$NamespaceImplCopyWithImpl<_$NamespaceImpl>(this, _$identity); + + @override + Map toJson() { + return _$$NamespaceImplToJson( + this, + ); + } +} + +abstract class _Namespace implements Namespace { + const factory _Namespace( + {final List? chains, + required final List accounts, + required final List methods, + required final List events}) = _$NamespaceImpl; + + factory _Namespace.fromJson(Map json) = + _$NamespaceImpl.fromJson; + + @override + List? get chains; + @override + List get accounts; + @override + List get methods; + @override + List get events; + @override + @JsonKey(ignore: true) + _$$NamespaceImplCopyWith<_$NamespaceImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SessionData _$SessionDataFromJson(Map json) { + return _SessionData.fromJson(json); +} + +/// @nodoc +mixin _$SessionData { + String get topic => throw _privateConstructorUsedError; + String get pairingTopic => throw _privateConstructorUsedError; + Relay get relay => throw _privateConstructorUsedError; + int get expiry => throw _privateConstructorUsedError; + bool get acknowledged => throw _privateConstructorUsedError; + String get controller => throw _privateConstructorUsedError; + Map get namespaces => throw _privateConstructorUsedError; + ConnectionMetadata get self => throw _privateConstructorUsedError; + ConnectionMetadata get peer => throw _privateConstructorUsedError; + Map? get requiredNamespaces => + throw _privateConstructorUsedError; + Map? get optionalNamespaces => + throw _privateConstructorUsedError; + Map? get sessionProperties => + throw _privateConstructorUsedError; + List? get authentication => throw _privateConstructorUsedError; + TransportType get transportType => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SessionDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SessionDataCopyWith<$Res> { + factory $SessionDataCopyWith( + SessionData value, $Res Function(SessionData) then) = + _$SessionDataCopyWithImpl<$Res, SessionData>; + @useResult + $Res call( + {String topic, + String pairingTopic, + Relay relay, + int expiry, + bool acknowledged, + String controller, + Map namespaces, + ConnectionMetadata self, + ConnectionMetadata peer, + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + List? authentication, + TransportType transportType}); + + $ConnectionMetadataCopyWith<$Res> get self; + $ConnectionMetadataCopyWith<$Res> get peer; +} + +/// @nodoc +class _$SessionDataCopyWithImpl<$Res, $Val extends SessionData> + implements $SessionDataCopyWith<$Res> { + _$SessionDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? topic = null, + Object? pairingTopic = null, + Object? relay = null, + Object? expiry = null, + Object? acknowledged = null, + Object? controller = null, + Object? namespaces = null, + Object? self = null, + Object? peer = null, + Object? requiredNamespaces = freezed, + Object? optionalNamespaces = freezed, + Object? sessionProperties = freezed, + Object? authentication = freezed, + Object? transportType = null, + }) { + return _then(_value.copyWith( + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + relay: null == relay + ? _value.relay + : relay // ignore: cast_nullable_to_non_nullable + as Relay, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + acknowledged: null == acknowledged + ? _value.acknowledged + : acknowledged // ignore: cast_nullable_to_non_nullable + as bool, + controller: null == controller + ? _value.controller + : controller // ignore: cast_nullable_to_non_nullable + as String, + namespaces: null == namespaces + ? _value.namespaces + : namespaces // ignore: cast_nullable_to_non_nullable + as Map, + self: null == self + ? _value.self + : self // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + peer: null == peer + ? _value.peer + : peer // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + requiredNamespaces: freezed == requiredNamespaces + ? _value.requiredNamespaces + : requiredNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + optionalNamespaces: freezed == optionalNamespaces + ? _value.optionalNamespaces + : optionalNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + sessionProperties: freezed == sessionProperties + ? _value.sessionProperties + : sessionProperties // ignore: cast_nullable_to_non_nullable + as Map?, + authentication: freezed == authentication + ? _value.authentication + : authentication // ignore: cast_nullable_to_non_nullable + as List?, + transportType: null == transportType + ? _value.transportType + : transportType // ignore: cast_nullable_to_non_nullable + as TransportType, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get self { + return $ConnectionMetadataCopyWith<$Res>(_value.self, (value) { + return _then(_value.copyWith(self: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $ConnectionMetadataCopyWith<$Res> get peer { + return $ConnectionMetadataCopyWith<$Res>(_value.peer, (value) { + return _then(_value.copyWith(peer: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SessionDataImplCopyWith<$Res> + implements $SessionDataCopyWith<$Res> { + factory _$$SessionDataImplCopyWith( + _$SessionDataImpl value, $Res Function(_$SessionDataImpl) then) = + __$$SessionDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String topic, + String pairingTopic, + Relay relay, + int expiry, + bool acknowledged, + String controller, + Map namespaces, + ConnectionMetadata self, + ConnectionMetadata peer, + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + List? authentication, + TransportType transportType}); + + @override + $ConnectionMetadataCopyWith<$Res> get self; + @override + $ConnectionMetadataCopyWith<$Res> get peer; +} + +/// @nodoc +class __$$SessionDataImplCopyWithImpl<$Res> + extends _$SessionDataCopyWithImpl<$Res, _$SessionDataImpl> + implements _$$SessionDataImplCopyWith<$Res> { + __$$SessionDataImplCopyWithImpl( + _$SessionDataImpl _value, $Res Function(_$SessionDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? topic = null, + Object? pairingTopic = null, + Object? relay = null, + Object? expiry = null, + Object? acknowledged = null, + Object? controller = null, + Object? namespaces = null, + Object? self = null, + Object? peer = null, + Object? requiredNamespaces = freezed, + Object? optionalNamespaces = freezed, + Object? sessionProperties = freezed, + Object? authentication = freezed, + Object? transportType = null, + }) { + return _then(_$SessionDataImpl( + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + pairingTopic: null == pairingTopic + ? _value.pairingTopic + : pairingTopic // ignore: cast_nullable_to_non_nullable + as String, + relay: null == relay + ? _value.relay + : relay // ignore: cast_nullable_to_non_nullable + as Relay, + expiry: null == expiry + ? _value.expiry + : expiry // ignore: cast_nullable_to_non_nullable + as int, + acknowledged: null == acknowledged + ? _value.acknowledged + : acknowledged // ignore: cast_nullable_to_non_nullable + as bool, + controller: null == controller + ? _value.controller + : controller // ignore: cast_nullable_to_non_nullable + as String, + namespaces: null == namespaces + ? _value._namespaces + : namespaces // ignore: cast_nullable_to_non_nullable + as Map, + self: null == self + ? _value.self + : self // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + peer: null == peer + ? _value.peer + : peer // ignore: cast_nullable_to_non_nullable + as ConnectionMetadata, + requiredNamespaces: freezed == requiredNamespaces + ? _value._requiredNamespaces + : requiredNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + optionalNamespaces: freezed == optionalNamespaces + ? _value._optionalNamespaces + : optionalNamespaces // ignore: cast_nullable_to_non_nullable + as Map?, + sessionProperties: freezed == sessionProperties + ? _value._sessionProperties + : sessionProperties // ignore: cast_nullable_to_non_nullable + as Map?, + authentication: freezed == authentication + ? _value._authentication + : authentication // ignore: cast_nullable_to_non_nullable + as List?, + transportType: null == transportType + ? _value.transportType + : transportType // ignore: cast_nullable_to_non_nullable + as TransportType, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$SessionDataImpl implements _SessionData { + const _$SessionDataImpl( + {required this.topic, + required this.pairingTopic, + required this.relay, + required this.expiry, + required this.acknowledged, + required this.controller, + required final Map namespaces, + required this.self, + required this.peer, + final Map? requiredNamespaces, + final Map? optionalNamespaces, + final Map? sessionProperties, + final List? authentication, + this.transportType = TransportType.relay}) + : _namespaces = namespaces, + _requiredNamespaces = requiredNamespaces, + _optionalNamespaces = optionalNamespaces, + _sessionProperties = sessionProperties, + _authentication = authentication; + + factory _$SessionDataImpl.fromJson(Map json) => + _$$SessionDataImplFromJson(json); + + @override + final String topic; + @override + final String pairingTopic; + @override + final Relay relay; + @override + final int expiry; + @override + final bool acknowledged; + @override + final String controller; + final Map _namespaces; + @override + Map get namespaces { + if (_namespaces is EqualUnmodifiableMapView) return _namespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_namespaces); + } + + @override + final ConnectionMetadata self; + @override + final ConnectionMetadata peer; + final Map? _requiredNamespaces; + @override + Map? get requiredNamespaces { + final value = _requiredNamespaces; + if (value == null) return null; + if (_requiredNamespaces is EqualUnmodifiableMapView) + return _requiredNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final Map? _optionalNamespaces; + @override + Map? get optionalNamespaces { + final value = _optionalNamespaces; + if (value == null) return null; + if (_optionalNamespaces is EqualUnmodifiableMapView) + return _optionalNamespaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final Map? _sessionProperties; + @override + Map? get sessionProperties { + final value = _sessionProperties; + if (value == null) return null; + if (_sessionProperties is EqualUnmodifiableMapView) + return _sessionProperties; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final List? _authentication; + @override + List? get authentication { + final value = _authentication; + if (value == null) return null; + if (_authentication is EqualUnmodifiableListView) return _authentication; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + @JsonKey() + final TransportType transportType; + + @override + String toString() { + return 'SessionData(topic: $topic, pairingTopic: $pairingTopic, relay: $relay, expiry: $expiry, acknowledged: $acknowledged, controller: $controller, namespaces: $namespaces, self: $self, peer: $peer, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, authentication: $authentication, transportType: $transportType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SessionDataImpl && + (identical(other.topic, topic) || other.topic == topic) && + (identical(other.pairingTopic, pairingTopic) || + other.pairingTopic == pairingTopic) && + (identical(other.relay, relay) || other.relay == relay) && + (identical(other.expiry, expiry) || other.expiry == expiry) && + (identical(other.acknowledged, acknowledged) || + other.acknowledged == acknowledged) && + (identical(other.controller, controller) || + other.controller == controller) && + const DeepCollectionEquality() + .equals(other._namespaces, _namespaces) && + (identical(other.self, self) || other.self == self) && + (identical(other.peer, peer) || other.peer == peer) && + const DeepCollectionEquality() + .equals(other._requiredNamespaces, _requiredNamespaces) && + const DeepCollectionEquality() + .equals(other._optionalNamespaces, _optionalNamespaces) && + const DeepCollectionEquality() + .equals(other._sessionProperties, _sessionProperties) && + const DeepCollectionEquality() + .equals(other._authentication, _authentication) && + (identical(other.transportType, transportType) || + other.transportType == transportType)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + topic, + pairingTopic, + relay, + expiry, + acknowledged, + controller, + const DeepCollectionEquality().hash(_namespaces), + self, + peer, + const DeepCollectionEquality().hash(_requiredNamespaces), + const DeepCollectionEquality().hash(_optionalNamespaces), + const DeepCollectionEquality().hash(_sessionProperties), + const DeepCollectionEquality().hash(_authentication), + transportType); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SessionDataImplCopyWith<_$SessionDataImpl> get copyWith => + __$$SessionDataImplCopyWithImpl<_$SessionDataImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SessionDataImplToJson( + this, + ); + } +} + +abstract class _SessionData implements SessionData { + const factory _SessionData( + {required final String topic, + required final String pairingTopic, + required final Relay relay, + required final int expiry, + required final bool acknowledged, + required final String controller, + required final Map namespaces, + required final ConnectionMetadata self, + required final ConnectionMetadata peer, + final Map? requiredNamespaces, + final Map? optionalNamespaces, + final Map? sessionProperties, + final List? authentication, + final TransportType transportType}) = _$SessionDataImpl; + + factory _SessionData.fromJson(Map json) = + _$SessionDataImpl.fromJson; + + @override + String get topic; + @override + String get pairingTopic; + @override + Relay get relay; + @override + int get expiry; + @override + bool get acknowledged; + @override + String get controller; + @override + Map get namespaces; + @override + ConnectionMetadata get self; + @override + ConnectionMetadata get peer; + @override + Map? get requiredNamespaces; + @override + Map? get optionalNamespaces; + @override + Map? get sessionProperties; + @override + List? get authentication; + @override + TransportType get transportType; + @override + @JsonKey(ignore: true) + _$$SessionDataImplCopyWith<_$SessionDataImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SessionRequest _$SessionRequestFromJson(Map json) { + return _SessionRequest.fromJson(json); +} + +/// @nodoc +mixin _$SessionRequest { + int get id => throw _privateConstructorUsedError; + String get topic => throw _privateConstructorUsedError; + String get method => throw _privateConstructorUsedError; + String get chainId => throw _privateConstructorUsedError; + dynamic get params => throw _privateConstructorUsedError; + VerifyContext get verifyContext => throw _privateConstructorUsedError; + TransportType get transportType => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SessionRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SessionRequestCopyWith<$Res> { + factory $SessionRequestCopyWith( + SessionRequest value, $Res Function(SessionRequest) then) = + _$SessionRequestCopyWithImpl<$Res, SessionRequest>; + @useResult + $Res call( + {int id, + String topic, + String method, + String chainId, + dynamic params, + VerifyContext verifyContext, + TransportType transportType}); + + $VerifyContextCopyWith<$Res> get verifyContext; +} + +/// @nodoc +class _$SessionRequestCopyWithImpl<$Res, $Val extends SessionRequest> + implements $SessionRequestCopyWith<$Res> { + _$SessionRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? topic = null, + Object? method = null, + Object? chainId = null, + Object? params = freezed, + Object? verifyContext = null, + Object? transportType = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + method: null == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String, + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as dynamic, + verifyContext: null == verifyContext + ? _value.verifyContext + : verifyContext // ignore: cast_nullable_to_non_nullable + as VerifyContext, + transportType: null == transportType + ? _value.transportType + : transportType // ignore: cast_nullable_to_non_nullable + as TransportType, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $VerifyContextCopyWith<$Res> get verifyContext { + return $VerifyContextCopyWith<$Res>(_value.verifyContext, (value) { + return _then(_value.copyWith(verifyContext: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SessionRequestImplCopyWith<$Res> + implements $SessionRequestCopyWith<$Res> { + factory _$$SessionRequestImplCopyWith(_$SessionRequestImpl value, + $Res Function(_$SessionRequestImpl) then) = + __$$SessionRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String topic, + String method, + String chainId, + dynamic params, + VerifyContext verifyContext, + TransportType transportType}); + + @override + $VerifyContextCopyWith<$Res> get verifyContext; +} + +/// @nodoc +class __$$SessionRequestImplCopyWithImpl<$Res> + extends _$SessionRequestCopyWithImpl<$Res, _$SessionRequestImpl> + implements _$$SessionRequestImplCopyWith<$Res> { + __$$SessionRequestImplCopyWithImpl( + _$SessionRequestImpl _value, $Res Function(_$SessionRequestImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? topic = null, + Object? method = null, + Object? chainId = null, + Object? params = freezed, + Object? verifyContext = null, + Object? transportType = null, + }) { + return _then(_$SessionRequestImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + topic: null == topic + ? _value.topic + : topic // ignore: cast_nullable_to_non_nullable + as String, + method: null == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String, + chainId: null == chainId + ? _value.chainId + : chainId // ignore: cast_nullable_to_non_nullable + as String, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as dynamic, + verifyContext: null == verifyContext + ? _value.verifyContext + : verifyContext // ignore: cast_nullable_to_non_nullable + as VerifyContext, + transportType: null == transportType + ? _value.transportType + : transportType // ignore: cast_nullable_to_non_nullable + as TransportType, + )); + } +} + +/// @nodoc + +@JsonSerializable() +class _$SessionRequestImpl implements _SessionRequest { + const _$SessionRequestImpl( + {required this.id, + required this.topic, + required this.method, + required this.chainId, + required this.params, + required this.verifyContext, + this.transportType = TransportType.relay}); + + factory _$SessionRequestImpl.fromJson(Map json) => + _$$SessionRequestImplFromJson(json); + + @override + final int id; + @override + final String topic; + @override + final String method; + @override + final String chainId; + @override + final dynamic params; + @override + final VerifyContext verifyContext; + @override + @JsonKey() + final TransportType transportType; + + @override + String toString() { + return 'SessionRequest(id: $id, topic: $topic, method: $method, chainId: $chainId, params: $params, verifyContext: $verifyContext, transportType: $transportType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SessionRequestImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.topic, topic) || other.topic == topic) && + (identical(other.method, method) || other.method == method) && + (identical(other.chainId, chainId) || other.chainId == chainId) && + const DeepCollectionEquality().equals(other.params, params) && + (identical(other.verifyContext, verifyContext) || + other.verifyContext == verifyContext) && + (identical(other.transportType, transportType) || + other.transportType == transportType)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + id, + topic, + method, + chainId, + const DeepCollectionEquality().hash(params), + verifyContext, + transportType); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SessionRequestImplCopyWith<_$SessionRequestImpl> get copyWith => + __$$SessionRequestImplCopyWithImpl<_$SessionRequestImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SessionRequestImplToJson( + this, + ); + } +} + +abstract class _SessionRequest implements SessionRequest { + const factory _SessionRequest( + {required final int id, + required final String topic, + required final String method, + required final String chainId, + required final dynamic params, + required final VerifyContext verifyContext, + final TransportType transportType}) = _$SessionRequestImpl; + + factory _SessionRequest.fromJson(Map json) = + _$SessionRequestImpl.fromJson; + + @override + int get id; + @override + String get topic; + @override + String get method; + @override + String get chainId; + @override + dynamic get params; + @override + VerifyContext get verifyContext; + @override + TransportType get transportType; + @override + @JsonKey(ignore: true) + _$$SessionRequestImplCopyWith<_$SessionRequestImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reown_sign/lib/models/session_models.g.dart b/packages/reown_sign/lib/models/session_models.g.dart new file mode 100644 index 0000000..1f192c1 --- /dev/null +++ b/packages/reown_sign/lib/models/session_models.g.dart @@ -0,0 +1,131 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'session_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$NamespaceImpl _$$NamespaceImplFromJson(Map json) => + _$NamespaceImpl( + chains: + (json['chains'] as List?)?.map((e) => e as String).toList(), + accounts: + (json['accounts'] as List).map((e) => e as String).toList(), + methods: + (json['methods'] as List).map((e) => e as String).toList(), + events: + (json['events'] as List).map((e) => e as String).toList(), + ); + +Map _$$NamespaceImplToJson(_$NamespaceImpl instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('chains', instance.chains); + val['accounts'] = instance.accounts; + val['methods'] = instance.methods; + val['events'] = instance.events; + return val; +} + +_$SessionDataImpl _$$SessionDataImplFromJson(Map json) => + _$SessionDataImpl( + topic: json['topic'] as String, + pairingTopic: json['pairingTopic'] as String, + relay: Relay.fromJson(json['relay'] as Map), + expiry: (json['expiry'] as num).toInt(), + acknowledged: json['acknowledged'] as bool, + controller: json['controller'] as String, + namespaces: (json['namespaces'] as Map).map( + (k, e) => MapEntry(k, Namespace.fromJson(e as Map)), + ), + self: ConnectionMetadata.fromJson(json['self'] as Map), + peer: ConnectionMetadata.fromJson(json['peer'] as Map), + requiredNamespaces: + (json['requiredNamespaces'] as Map?)?.map( + (k, e) => + MapEntry(k, RequiredNamespace.fromJson(e as Map)), + ), + optionalNamespaces: + (json['optionalNamespaces'] as Map?)?.map( + (k, e) => + MapEntry(k, RequiredNamespace.fromJson(e as Map)), + ), + sessionProperties: + (json['sessionProperties'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + authentication: (json['authentication'] as List?) + ?.map((e) => Cacao.fromJson(e as Map)) + .toList(), + transportType: + $enumDecodeNullable(_$TransportTypeEnumMap, json['transportType']) ?? + TransportType.relay, + ); + +Map _$$SessionDataImplToJson(_$SessionDataImpl instance) { + final val = { + 'topic': instance.topic, + 'pairingTopic': instance.pairingTopic, + 'relay': instance.relay.toJson(), + 'expiry': instance.expiry, + 'acknowledged': instance.acknowledged, + 'controller': instance.controller, + 'namespaces': instance.namespaces.map((k, e) => MapEntry(k, e.toJson())), + 'self': instance.self.toJson(), + 'peer': instance.peer.toJson(), + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('requiredNamespaces', + instance.requiredNamespaces?.map((k, e) => MapEntry(k, e.toJson()))); + writeNotNull('optionalNamespaces', + instance.optionalNamespaces?.map((k, e) => MapEntry(k, e.toJson()))); + writeNotNull('sessionProperties', instance.sessionProperties); + writeNotNull('authentication', + instance.authentication?.map((e) => e.toJson()).toList()); + val['transportType'] = _$TransportTypeEnumMap[instance.transportType]!; + return val; +} + +const _$TransportTypeEnumMap = { + TransportType.relay: 'relay', + TransportType.linkMode: 'linkMode', +}; + +_$SessionRequestImpl _$$SessionRequestImplFromJson(Map json) => + _$SessionRequestImpl( + id: (json['id'] as num).toInt(), + topic: json['topic'] as String, + method: json['method'] as String, + chainId: json['chainId'] as String, + params: json['params'], + verifyContext: + VerifyContext.fromJson(json['verifyContext'] as Map), + transportType: + $enumDecodeNullable(_$TransportTypeEnumMap, json['transportType']) ?? + TransportType.relay, + ); + +Map _$$SessionRequestImplToJson( + _$SessionRequestImpl instance) => + { + 'id': instance.id, + 'topic': instance.topic, + 'method': instance.method, + 'chainId': instance.chainId, + 'params': instance.params, + 'verifyContext': instance.verifyContext.toJson(), + 'transportType': _$TransportTypeEnumMap[instance.transportType]!, + }; diff --git a/packages/reown_sign/lib/models/sign_client_events.dart b/packages/reown_sign/lib/models/sign_client_events.dart new file mode 100644 index 0000000..b309450 --- /dev/null +++ b/packages/reown_sign/lib/models/sign_client_events.dart @@ -0,0 +1,205 @@ +import 'dart:convert'; + +import 'package:event/event.dart'; +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/verify/models/verify_context.dart'; +import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/models/proposal_models.dart'; +import 'package:reown_sign/models/session_models.dart'; + +class SessionProposalEvent extends EventArgs { + int id; + ProposalData params; + VerifyContext? verifyContext; + + SessionProposalEvent( + this.id, + this.params, [ + this.verifyContext, + ]); + + Map toJson() => { + 'id': id, + 'params': params.toJson(), + 'verifyContext': verifyContext?.toJson(), + }; + + @override + String toString() { + return 'SessionProposalEvent(${jsonEncode(toJson())})'; + } +} + +class SessionProposalErrorEvent extends EventArgs { + int id; + Map requiredNamespaces; + Map namespaces; + ReownSignError error; + + SessionProposalErrorEvent( + this.id, + this.requiredNamespaces, + this.namespaces, + this.error, + ); + + @override + String toString() { + return 'SessionProposalErrorEvent(id: $id, requiredNamespaces: $requiredNamespaces, namespaces: $namespaces, error: $error)'; + } +} + +class SessionConnect extends EventArgs { + SessionData session; + + SessionConnect( + this.session, + ); + + @override + String toString() { + return 'SessionConnect(session: $session)'; + } +} + +class SessionUpdate extends EventArgs { + int id; + String topic; + Map namespaces; + + SessionUpdate( + this.id, + this.topic, + this.namespaces, + ); + + @override + String toString() { + return 'SessionUpdate(id: $id, topic: $topic, namespaces: $namespaces)'; + } +} + +class SessionExtend extends EventArgs { + int id; + String topic; + + SessionExtend(this.id, this.topic); + + @override + String toString() { + return 'SessionExtend(id: $id, topic: $topic)'; + } +} + +class SessionPing extends EventArgs { + int id; + String topic; + + SessionPing(this.id, this.topic); + + @override + String toString() { + return 'SessionPing(id: $id, topic: $topic)'; + } +} + +class SessionDelete extends EventArgs { + String topic; + int? id; + + SessionDelete( + this.topic, { + this.id, + }); + + @override + String toString() { + return 'SessionDelete(topic: $topic, id: $id)'; + } +} + +class SessionExpire extends EventArgs { + final String topic; + + SessionExpire(this.topic); + + @override + String toString() { + return 'SessionExpire(topic: $topic)'; + } +} + +class SessionRequestEvent extends EventArgs { + int id; + String topic; + String method; + String chainId; + dynamic params; + TransportType transportType; + + SessionRequestEvent( + this.id, + this.topic, + this.method, + this.chainId, + this.params, + this.transportType, + ); + + factory SessionRequestEvent.fromSessionRequest(SessionRequest request) { + return SessionRequestEvent( + request.id, + request.topic, + request.method, + request.chainId, + request.params, + request.transportType, + ); + } + + Map toJson() => { + 'id': id, + 'topic': topic, + 'method': method, + 'chainId': chainId, + 'params': params, + 'transportType': transportType.name, + }; + + @override + String toString() { + return 'SessionRequestEvent(${jsonEncode(toJson())})'; + } +} + +class SessionEvent extends EventArgs { + int id; + String topic; + String name; + String chainId; + dynamic data; + + SessionEvent( + this.id, + this.topic, + this.name, + this.chainId, + this.data, + ); + + @override + String toString() { + return 'SessionEvent(id: $id, topic: $topic, name: $name, chainId: $chainId, data: $data)'; + } +} + +class ProposalExpire extends EventArgs { + final int id; + + ProposalExpire(this.id); + + @override + String toString() { + return 'ProposalExpire(id: $id)'; + } +} diff --git a/packages/reown_sign/lib/models/sign_client_models.dart b/packages/reown_sign/lib/models/sign_client_models.dart new file mode 100644 index 0000000..4e5d2e6 --- /dev/null +++ b/packages/reown_sign/lib/models/sign_client_models.dart @@ -0,0 +1,35 @@ +import 'dart:async'; + +import 'package:reown_sign/models/session_models.dart'; + +class ConnectResponse { + final String pairingTopic; + final Completer session; + final Uri? uri; + + ConnectResponse({ + required this.pairingTopic, + required this.session, + this.uri, + }); + + @override + String toString() { + return 'ConnectResponse(pairingTopic: $pairingTopic, session: $session, uri: $uri)'; + } +} + +class ApproveResponse { + final String topic; + final SessionData? session; + + ApproveResponse({ + required this.topic, + required this.session, + }); + + @override + String toString() { + return 'ApproveResponse(topic: $topic, session: $session)'; + } +} diff --git a/packages/reown_sign/lib/reown_sign.dart b/packages/reown_sign/lib/reown_sign.dart new file mode 100644 index 0000000..48431de --- /dev/null +++ b/packages/reown_sign/lib/reown_sign.dart @@ -0,0 +1,31 @@ +library reown_sign; + +// models +export 'models/proposal_models.dart'; +export 'models/session_models.dart'; +export 'models/json_rpc_models.dart'; +export 'models/sign_client_models.dart'; +export 'models/sign_client_events.dart'; +export 'models/cacao_models.dart'; +export 'models/session_auth_events.dart'; +export 'models/session_auth_models.dart'; +export 'models/basic_models.dart'; +// utils +export 'utils/auth_utils.dart'; +export 'utils/address_utils.dart'; +export 'utils/auth_signature.dart'; +export 'utils/auth_api_validators.dart'; +export 'utils/namespace_utils.dart'; +export 'utils/extensions.dart'; +// +export 'store/i_sessions.dart'; +export 'store/sessions.dart'; +export 'i_sign_client.dart'; +export 'sign_client.dart'; +export 'i_sign_engine.dart'; +export 'sign_engine.dart'; + +export 'package:web3dart/web3dart.dart'; +export 'package:web3dart/crypto.dart' hide bytesToHex; +export 'package:web3dart/json_rpc.dart'; +export 'package:logger/logger.dart'; diff --git a/packages/reown_sign/lib/sign_client.dart b/packages/reown_sign/lib/sign_client.dart new file mode 100644 index 0000000..280238a --- /dev/null +++ b/packages/reown_sign/lib/sign_client.dart @@ -0,0 +1,614 @@ +import 'package:event/event.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_sign/reown_sign.dart'; + +class ReownSignClient implements IReownSignClient { + bool _initialized = false; + + @override + final String protocol = 'wc'; + @override + final int version = 2; + + @override + Event get onSessionDelete => engine.onSessionDelete; + @override + Event get onSessionConnect => engine.onSessionConnect; + @override + Event get onSessionEvent => engine.onSessionEvent; + @override + Event get onSessionExpire => engine.onSessionExpire; + @override + Event get onSessionExtend => engine.onSessionExtend; + @override + Event get onSessionPing => engine.onSessionPing; + @override + Event get onSessionProposal => engine.onSessionProposal; + @override + Event get onSessionProposalError => + engine.onSessionProposalError; + @override + Event get onProposalExpire => engine.onProposalExpire; + @override + Event get onSessionRequest => engine.onSessionRequest; + @override + Event get onSessionUpdate => engine.onSessionUpdate; + + @override + IReownCore get core => engine.core; + @override + PairingMetadata get metadata => engine.metadata; + @override + IGenericStore get proposals => engine.proposals; + @override + ISessions get sessions => engine.sessions; + @override + IGenericStore get pendingRequests => engine.pendingRequests; + + @override + late IReownSign engine; + + static Future createInstance({ + required String projectId, + String relayUrl = ReownConstants.DEFAULT_RELAY_URL, + required PairingMetadata metadata, + bool memoryStore = false, + LogLevel logLevel = LogLevel.nothing, + }) async { + final client = ReownSignClient( + core: ReownCore( + projectId: projectId, + relayUrl: relayUrl, + memoryStore: memoryStore, + logLevel: logLevel, + ), + metadata: metadata, + ); + await client.init(); + + return client; + } + + ReownSignClient({ + required IReownCore core, + required PairingMetadata metadata, + }) { + engine = ReownSign( + core: core, + metadata: metadata, + proposals: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PROPOSALS, + version: StoreVersions.VERSION_PROPOSALS, + fromJson: (dynamic value) { + return ProposalData.fromJson(value); + }, + ), + sessions: Sessions( + storage: core.storage, + context: StoreVersions.CONTEXT_SESSIONS, + version: StoreVersions.VERSION_SESSIONS, + fromJson: (dynamic value) { + return SessionData.fromJson(value); + }, + ), + pendingRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PENDING_REQUESTS, + version: StoreVersions.VERSION_PENDING_REQUESTS, + fromJson: (dynamic value) { + return SessionRequest.fromJson(value); + }, + ), + pairingTopics: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PAIRING_TOPICS, + version: StoreVersions.VERSION_PAIRING_TOPICS, + fromJson: (dynamic value) { + return value; + }, + ), + authKeys: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_KEYS, + version: StoreVersions.VERSION_AUTH_KEYS, + fromJson: (dynamic value) { + return AuthPublicKey.fromJson(value); + }, + ), + sessionAuthRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_REQUESTS, + version: StoreVersions.VERSION_AUTH_REQUESTS, + fromJson: (dynamic value) { + return PendingSessionAuthRequest.fromJson(value); + }, + ), + ); + } + + @override + Future init() async { + if (_initialized) { + return; + } + + await core.start(); + await engine.init(); + + _initialized = true; + } + + @override + Future connect({ + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + String? pairingTopic, + List? relays, + List>? methods = ReownSign.DEFAULT_METHODS, + }) async { + try { + return await engine.connect( + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: sessionProperties, + pairingTopic: pairingTopic, + relays: relays, + methods: methods, + ); + } catch (e) { + // print(e); + rethrow; + } + } + + @override + Future pair({ + required Uri uri, + }) async { + try { + return await engine.pair(uri: uri); + } catch (e) { + rethrow; + } + } + + @override + Future approve({ + required int id, + required Map namespaces, + Map? sessionProperties, + String? relayProtocol, + }) async { + try { + return await engine.approveSession( + id: id, + namespaces: namespaces, + sessionProperties: sessionProperties, + relayProtocol: relayProtocol, + ); + } catch (e) { + rethrow; + } + } + + @override + Future reject({ + required int id, + required ReownSignError reason, + }) async { + try { + return await engine.rejectSession( + id: id, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + Future update({ + required String topic, + required Map namespaces, + }) async { + try { + return await engine.updateSession( + topic: topic, + namespaces: namespaces, + ); + } catch (e) { + // final error = e as WCError; + rethrow; + } + } + + @override + Future extend({ + required String topic, + }) async { + try { + return await engine.extendSession(topic: topic); + } catch (e) { + rethrow; + } + } + + @override + void registerRequestHandler({ + required String chainId, + required String method, + void Function(String, dynamic)? handler, + }) { + try { + return engine.registerRequestHandler( + chainId: chainId, + method: method, + handler: handler, + ); + } catch (e) { + rethrow; + } + } + + @override + Future request({ + required String topic, + required String chainId, + required SessionRequestParams request, + }) async { + try { + return await engine.request( + topic: topic, + chainId: chainId, + request: request, + ); + } catch (e) { + rethrow; + } + } + + @override + Future> requestReadContract({ + required DeployedContract deployedContract, + required String functionName, + required String rpcUrl, + EthereumAddress? sender, + List parameters = const [], + }) async { + try { + return await engine.requestReadContract( + sender: sender, + deployedContract: deployedContract, + functionName: functionName, + rpcUrl: rpcUrl, + parameters: parameters, + ); + } catch (e) { + rethrow; + } + } + + @override + Future requestWriteContract({ + required String topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + required Transaction transaction, + List parameters = const [], + String? method, + }) async { + try { + return await engine.requestWriteContract( + topic: topic, + chainId: chainId, + deployedContract: deployedContract, + functionName: functionName, + transaction: transaction, + method: method, + parameters: parameters, + ); + } catch (e) { + rethrow; + } + } + + @override + Future respond({ + required String topic, + required JsonRpcResponse response, + }) { + try { + return engine.respondSessionRequest( + topic: topic, + response: response, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerEventHandler({ + required String chainId, + required String event, + dynamic Function(String, dynamic)? handler, + }) { + try { + return engine.registerEventHandler( + chainId: chainId, + event: event, + handler: handler, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerEventEmitter({ + required String chainId, + required String event, + }) { + try { + return engine.registerEventEmitter( + chainId: chainId, + event: event, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerAccount({ + required String chainId, + required String accountAddress, + }) { + try { + return engine.registerAccount( + chainId: chainId, + accountAddress: accountAddress, + ); + } catch (e) { + rethrow; + } + } + + @override + Future emit({ + required String topic, + required String chainId, + required SessionEventParams event, + }) async { + try { + return await engine.emitSessionEvent( + topic: topic, + chainId: chainId, + event: event, + ); + } catch (e) { + rethrow; + } + } + + @override + Future ping({ + required String topic, + }) async { + try { + return await engine.ping(topic: topic); + } catch (e) { + rethrow; + } + } + + @override + Future disconnect({ + required String topic, + required ReownSignError reason, + }) async { + try { + return await engine.disconnectSession( + topic: topic, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + SessionData? find({ + required Map requiredNamespaces, + }) { + try { + return engine.find(requiredNamespaces: requiredNamespaces); + } catch (e) { + rethrow; + } + } + + @override + Map getActiveSessions() { + try { + return engine.getActiveSessions(); + } catch (e) { + rethrow; + } + } + + @override + Map getSessionsForPairing({ + required String pairingTopic, + }) { + try { + return engine.getSessionsForPairing( + pairingTopic: pairingTopic, + ); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionProposals() { + try { + return engine.getPendingSessionProposals(); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionRequests() { + try { + return engine.getPendingSessionRequests(); + } catch (e) { + rethrow; + } + } + + @override + IPairingStore get pairings => core.pairing.getStore(); + + @override + Map getPendingSessionAuthRequests() { + try { + return engine.getPendingSessionAuthRequests(); + } catch (e) { + rethrow; + } + } + + @override + IGenericStore get sessionAuthRequests => + engine.sessionAuthRequests; + + @override + Event get onSessionAuthResponse => + engine.onSessionAuthResponse; + + @override + Event get onSessionAuthRequest => + engine.onSessionAuthRequest; + + // NEW ONE-CLICK AUTH METHOD FOR DAPPS + @override + Future authenticate({ + required SessionAuthRequestParams params, + String? walletUniversalLink, + String? pairingTopic, + List>? methods = const [ + [MethodConstants.WC_SESSION_AUTHENTICATE] + ], + }) { + try { + return engine.authenticate( + params: params, + walletUniversalLink: walletUniversalLink, + pairingTopic: pairingTopic, + methods: methods, + ); + } catch (e) { + rethrow; + } + } + + @override + Future approveSessionAuthenticate({ + required int id, + List? auths, + }) { + try { + return engine.approveSessionAuthenticate( + id: id, + auths: auths, + ); + } catch (e) { + rethrow; + } + } + + @override + Future rejectSessionAuthenticate({ + required int id, + required ReownSignError reason, + }) { + try { + return engine.rejectSessionAuthenticate( + id: id, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + IGenericStore get authKeys => engine.authKeys; + + @override + Future validateSignedCacao({ + required Cacao cacao, + required String projectId, + }) { + try { + return engine.validateSignedCacao( + cacao: cacao, + projectId: projectId, + ); + } catch (e) { + rethrow; + } + } + + @override + String formatAuthMessage({ + required String iss, + required CacaoRequestPayload cacaoPayload, + }) { + try { + return engine.formatAuthMessage( + iss: iss, + cacaoPayload: cacaoPayload, + ); + } catch (e) { + rethrow; + } + } + + @override + Future dispatchEnvelope(String url) async { + return engine.dispatchEnvelope(url); + } + + @override + Future redirectToDapp({ + required String topic, + required Redirect? redirect, + }) { + return engine.redirectToDapp( + topic: topic, + redirect: redirect, + ); + } + + @override + Future redirectToWallet({ + required String topic, + required Redirect? redirect, + }) { + return engine.redirectToWallet( + topic: topic, + redirect: redirect, + ); + } + + @override + IGenericStore get pairingTopics => engine.pairingTopics; +} diff --git a/packages/reown_sign/lib/sign_engine.dart b/packages/reown_sign/lib/sign_engine.dart new file mode 100644 index 0000000..a61e01a --- /dev/null +++ b/packages/reown_sign/lib/sign_engine.dart @@ -0,0 +1,2807 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; + +import 'package:event/event.dart'; +import 'package:http/http.dart' as http; +import 'package:reown_core/pairing/utils/json_rpc_utils.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +import 'package:reown_sign/reown_sign.dart'; +import 'package:reown_sign/utils/sign_api_validator_utils.dart'; + +import 'package:reown_sign/utils/recaps_utils.dart'; +import 'package:reown_sign/utils/constants.dart'; + +class ReownSign implements IReownSign { + static const List> DEFAULT_METHODS = [ + [ + MethodConstants.WC_SESSION_PROPOSE, + MethodConstants.WC_SESSION_REQUEST, + ], + ]; + + bool _initialized = false; + + @override + final Event onSessionConnect = Event(); + @override + final Event onSessionProposal = + Event(); + @override + final Event onSessionProposalError = + Event(); + @override + final Event onProposalExpire = + Event(); + @override + final Event onSessionUpdate = Event(); + @override + final Event onSessionExtend = Event(); + @override + final Event onSessionExpire = Event(); + @override + final Event onSessionRequest = + Event(); + @override + final Event onSessionEvent = Event(); + @override + final Event onSessionPing = Event(); + @override + final Event onSessionDelete = Event(); + + @override + final IReownCore core; + @override + final PairingMetadata metadata; + @override + final IGenericStore proposals; + @override + final ISessions sessions; + @override + final IGenericStore pendingRequests; + + List pendingProposals = []; + + @override + late IGenericStore authKeys; + + @override + late IGenericStore pairingTopics; + + // NEW 1-CA METHOD + @override + late IGenericStore sessionAuthRequests; + @override + final Event onSessionAuthRequest = + Event(); + @override + final Event onSessionAuthResponse = + Event(); + + List pendingSessionAuthRequests = []; + + ReownSign({ + required this.core, + required this.metadata, + required this.proposals, + required this.sessions, + required this.pendingRequests, + required this.sessionAuthRequests, + required this.authKeys, + required this.pairingTopics, + }); + + @override + Future init() async { + if (_initialized) { + return; + } + + await core.pairing.init(); + await core.verify.init(verifyUrl: metadata.verifyUrl); + await proposals.init(); + await sessions.init(); + await pendingRequests.init(); + await sessionAuthRequests.init(); + await authKeys.init(); + await pairingTopics.init(); + + _registerInternalEvents(); + _registerRelayClientFunctions(); + await _cleanup(); + + await _resubscribeAll(); + + _initialized = true; + } + + @override + Future checkAndExpire() async { + for (var session in sessions.getAll()) { + await core.expirer.checkAndExpire(session.topic); + } + } + + @override + Future connect({ + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + String? pairingTopic, + List? relays, + List>? methods = DEFAULT_METHODS, + }) async { + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + await _isValidConnect( + requiredNamespaces: requiredNamespaces ?? {}, + optionalNamespaces: optionalNamespaces ?? {}, + sessionProperties: sessionProperties, + pairingTopic: pairingTopic, + relays: relays, + ); + String? pTopic = pairingTopic; + Uri? uri; + + if (pTopic == null) { + final CreateResponse newTopicAndUri = await core.pairing.create( + methods: methods, + ); + pTopic = newTopicAndUri.topic; + uri = newTopicAndUri.uri; + // print('connect generated topic: $topic'); + } else { + core.pairing.isValidPairingTopic(topic: pTopic); + } + + final publicKey = await core.crypto.generateKeyPair(); + final int id = JsonRpcUtils.payloadId(); + + final request = WcSessionProposeRequest( + relays: relays ?? [Relay(ReownConstants.RELAYER_DEFAULT_PROTOCOL)], + requiredNamespaces: requiredNamespaces ?? {}, + optionalNamespaces: optionalNamespaces ?? {}, + proposer: ConnectionMetadata( + publicKey: publicKey, + metadata: metadata, + ), + sessionProperties: sessionProperties, + ); + + final expiry = ReownCoreUtils.calculateExpiry( + ReownConstants.FIVE_MINUTES, + ); + final ProposalData proposal = ProposalData( + id: id, + expiry: expiry, + relays: request.relays, + proposer: request.proposer, + requiredNamespaces: request.requiredNamespaces, + optionalNamespaces: request.optionalNamespaces ?? {}, + sessionProperties: request.sessionProperties, + pairingTopic: pTopic, + ); + await _setProposal( + id, + proposal, + ); + + Completer completer = Completer(); + + pendingProposals.add( + SessionProposalCompleter( + id: id, + selfPublicKey: publicKey, + pairingTopic: pTopic, + requiredNamespaces: request.requiredNamespaces, + optionalNamespaces: request.optionalNamespaces ?? {}, + sessionProperties: request.sessionProperties, + completer: completer, + ), + ); + _connectResponseHandler( + pTopic, + request, + id, + ); + + final ConnectResponse resp = ConnectResponse( + pairingTopic: pTopic, + session: completer, + uri: uri, + ); + + return resp; + } + + Future _connectResponseHandler( + String topic, + WcSessionProposeRequest request, + int requestId, + ) async { + // print("sending proposal for $topic"); + // print('connectResponseHandler requestId: $requestId'); + try { + final Map response = await core.pairing.sendRequest( + topic, + MethodConstants.WC_SESSION_PROPOSE, + request.toJson(), + id: requestId, + ); + final String peerPublicKey = response['responderPublicKey']; + + final ProposalData proposal = proposals.get( + requestId.toString(), + )!; + final String sessionTopic = await core.crypto.generateSharedKey( + proposal.proposer.publicKey, + peerPublicKey, + ); + // print('connectResponseHandler session topic: $sessionTopic'); + + // Delete the proposal, we are done with it + await _deleteProposal(requestId); + + await core.relayClient.subscribe(topic: sessionTopic); + await core.pairing.activate(topic: topic); + } catch (e) { + // Get the completer and finish it with an error + pendingProposals.removeLast().completer.completeError(e); + } + } + + @override + Future pair({ + required Uri uri, + }) async { + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + try { + return await core.pairing.pair( + uri: uri, + ); + } on ReownCoreError catch (e) { + throw e.toSignError(); + } + } + + /// Approves a proposal with the id provided in the parameters. + /// Assumes the proposal is already created. + @override + Future approveSession({ + required int id, + required Map namespaces, + Map? sessionProperties, + String? relayProtocol, + }) async { + // print('sign approveSession'); + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + await _isValidApprove( + id: id, + namespaces: namespaces, + sessionProperties: sessionProperties, + relayProtocol: relayProtocol, + ); + + final ProposalData proposal = proposals.get( + id.toString(), + )!; + + final String selfPubKey = await core.crypto.generateKeyPair(); + final String peerPubKey = proposal.proposer.publicKey; + final String sessionTopic = await core.crypto.generateSharedKey( + selfPubKey, + peerPubKey, + ); + // print('approve session topic: $sessionTopic'); + final protocol = relayProtocol ?? ReownConstants.RELAYER_DEFAULT_PROTOCOL; + final relay = Relay(protocol); + + // Respond to the proposal + await core.pairing.sendResult( + id, + proposal.pairingTopic, + MethodConstants.WC_SESSION_PROPOSE, + WcSessionProposeResponse( + relay: relay, + responderPublicKey: selfPubKey, + ), + ); + await _deleteProposal(id); + await core.pairing.activate(topic: proposal.pairingTopic); + + await core.pairing.updateMetadata( + topic: proposal.pairingTopic, + metadata: proposal.proposer.metadata, + ); + + await core.relayClient.subscribe(topic: sessionTopic); + + final int expiry = ReownCoreUtils.calculateExpiry( + ReownConstants.SEVEN_DAYS, + ); + + SessionData session = SessionData( + topic: sessionTopic, + pairingTopic: proposal.pairingTopic, + relay: relay, + expiry: expiry, + acknowledged: false, + controller: selfPubKey, + namespaces: namespaces, + self: ConnectionMetadata( + publicKey: selfPubKey, + metadata: metadata, + ), + peer: proposal.proposer, + sessionProperties: proposal.sessionProperties, + transportType: TransportType.relay, + ); + + onSessionConnect.broadcast(SessionConnect(session)); + + await sessions.set(sessionTopic, session); + await _setSessionExpiry(sessionTopic, expiry); + + // `wc_sessionSettle` is not critical throughout the entire session. + final settleRequest = WcSessionSettleRequest( + relay: relay, + namespaces: namespaces, + sessionProperties: sessionProperties, + expiry: expiry, + controller: ConnectionMetadata( + publicKey: selfPubKey, + metadata: metadata, + ), + ); + bool acknowledged = await core.pairing + .sendRequest( + sessionTopic, + MethodConstants.WC_SESSION_SETTLE, + settleRequest.toJson(), + ) + // Sometimes we don't receive any response for a long time, + // in which case we manually time out to prevent waiting indefinitely. + .timeout(const Duration(seconds: 15)) + .catchError( + (_) { + return false; + }, + ); + + session = session.copyWith( + acknowledged: acknowledged, + ); + + if (acknowledged && sessions.has(sessionTopic)) { + // We directly update the latest value. + await sessions.set( + sessionTopic, + session, + ); + } + + return ApproveResponse( + topic: sessionTopic, + session: session, + ); + } + + @override + Future rejectSession({ + required int id, + required ReownSignError reason, + }) async { + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + await _isValidReject(id, reason); + + ProposalData? proposal = proposals.get(id.toString()); + if (proposal != null) { + // Attempt to send a response, if the pairing is not active, this will fail + // but we don't care + try { + final method = MethodConstants.WC_SESSION_PROPOSE; + final rpcOpts = MethodConstants.RPC_OPTS[method]; + await core.pairing.sendError( + id, + proposal.pairingTopic, + method, + JsonRpcError(code: reason.code, message: reason.message), + rpcOptions: rpcOpts?['reject'], + ); + } catch (_) { + // print('got here'); + } + } + await _deleteProposal(id); + } + + @override + Future updateSession({ + required String topic, + required Map namespaces, + }) async { + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + await _isValidUpdate( + topic, + namespaces, + ); + + await sessions.update( + topic, + namespaces: namespaces, + ); + + final updateRequest = WcSessionUpdateRequest( + namespaces: namespaces, + ); + + await core.pairing.sendRequest( + topic, + MethodConstants.WC_SESSION_UPDATE, + updateRequest.toJson(), + ); + } + + @override + Future extendSession({ + required String topic, + }) async { + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + await _isValidSessionTopic(topic); + + await core.pairing.sendRequest( + topic, + MethodConstants.WC_SESSION_EXTEND, + {}, + ); + + await _setSessionExpiry( + topic, + ReownCoreUtils.calculateExpiry( + ReownConstants.SEVEN_DAYS, + ), + ); + } + + /// Maps a request using chainId:method to its handler + final Map _methodHandlers = {}; + + @override + void registerRequestHandler({ + required String chainId, + required String method, + dynamic Function(String, dynamic)? handler, + }) { + _methodHandlers[_getRegisterKey(chainId, method)] = handler; + } + + @override + Future request({ + required String topic, + required String chainId, + required SessionRequestParams request, + }) async { + _checkInitialized(); + await _isValidRequest( + topic, + chainId, + request, + ); + + final session = sessions.get(topic); + final isLinkMode = _isLinkModeRequest(session); + if (!isLinkMode) { + _confirmOnlineStateOrThrow(); + } + + final sessionRequest = WcSessionRequestRequest( + chainId: chainId, + request: request, + ); + + return await core.pairing.sendRequest( + topic, + MethodConstants.WC_SESSION_REQUEST, + sessionRequest.toJson(), + appLink: _getAppLinkIfEnabled(session?.peer.metadata), + ); + } + + bool _isLinkModeRequest(SessionData? session) { + final appLink = (session?.peer.metadata.redirect?.universal ?? ''); + final supportedApps = core.getLinkModeSupportedApps(); + return appLink.isNotEmpty && supportedApps.contains(appLink); + } + + @override + Future> requestReadContract({ + required DeployedContract deployedContract, + required String functionName, + required String rpcUrl, + EthereumAddress? sender, + List parameters = const [], + }) async { + try { + final results = await Web3Client(rpcUrl, http.Client()).call( + sender: sender, + contract: deployedContract, + function: deployedContract.function(functionName), + params: parameters, + ); + + return results; + } catch (e) { + rethrow; + } + } + + @override + Future requestWriteContract({ + required String topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + required Transaction transaction, + List parameters = const [], + String? method, + }) async { + if (transaction.from == null) { + throw Exception('Transaction must include `from` value'); + } + + final trx = Transaction.callContract( + contract: deployedContract, + function: deployedContract.function(functionName), + from: transaction.from!, + value: transaction.value, + maxGas: transaction.maxGas, + gasPrice: transaction.gasPrice, + nonce: transaction.nonce, + maxFeePerGas: transaction.maxFeePerGas, + maxPriorityFeePerGas: transaction.maxPriorityFeePerGas, + parameters: parameters, + ); + + return await request( + topic: topic, + chainId: chainId, + request: SessionRequestParams( + method: method ?? MethodsConstants.ethSendTransaction, + params: [trx.toJson()], + ), + ); + } + + @override + Future respondSessionRequest({ + required String topic, + required JsonRpcResponse response, + }) async { + _checkInitialized(); + await _isValidResponse(topic, response); + + final session = sessions.get(topic); + final isLinkModeSession = session?.transportType.isLinkMode ?? false; + if (!isLinkModeSession) { + _confirmOnlineStateOrThrow(); + } + + final appLink = _getAppLinkIfEnabled(session?.peer.metadata); + + if (response.result != null) { + await core.pairing.sendResult( + response.id, + topic, + MethodConstants.WC_SESSION_REQUEST, + response.result, + appLink: appLink, + ); + } else { + await core.pairing.sendError( + response.id, + topic, + MethodConstants.WC_SESSION_REQUEST, + response.error!, + appLink: appLink, + ); + } + + await _deletePendingRequest(response.id); + } + + /// Maps a request using chainId:event to its handler + final Map _eventHandlers = {}; + + @override + void registerEventHandler({ + required String chainId, + required String event, + dynamic Function(String, dynamic)? handler, + }) { + _checkInitialized(); + _eventHandlers[_getRegisterKey(chainId, event)] = handler; + } + + @override + Future emitSessionEvent({ + required String topic, + required String chainId, + required SessionEventParams event, + }) async { + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + await _isValidEmit( + topic, + event, + chainId, + ); + + final eventRequest = WcSessionEventRequest( + chainId: chainId, + event: event, + ); + + await core.pairing.sendRequest( + topic, + MethodConstants.WC_SESSION_EVENT, + eventRequest.toJson(), + ); + } + + @override + Future ping({ + required String topic, + }) async { + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + await _isValidPing(topic); + + if (sessions.has(topic)) { + bool _ = await core.pairing.sendRequest( + topic, + MethodConstants.WC_SESSION_PING, + {}, + ); + } else if (core.pairing.getStore().has(topic)) { + await core.pairing.ping(topic: topic); + } + } + + @override + Future disconnectSession({ + required String topic, + required ReownSignError reason, + }) async { + _checkInitialized(); + _confirmOnlineStateOrThrow(); + + try { + await _isValidDisconnect(topic); + + if (sessions.has(topic)) { + // Send the request to delete the session, we don't care if it fails + try { + final deleteRequest = WcSessionDeleteRequest( + code: reason.code, + message: reason.message, + data: reason.data, + ); + core.pairing.sendRequest( + topic, + MethodConstants.WC_SESSION_DELETE, + deleteRequest.toJson(), + ); + } catch (_) {} + + await _deleteSession(topic); + } else { + await core.pairing.disconnect(topic: topic); + } + } on ReownSignError catch (error, s) { + core.logger.e( + '[$runtimeType] disconnectSession()', + error: error, + stackTrace: s, + ); + } + } + + @override + SessionData? find({ + required Map requiredNamespaces, + }) { + _checkInitialized(); + final compatible = sessions.getAll().where((element) { + return SignApiValidatorUtils.isSessionCompatible( + session: element, + requiredNamespaces: requiredNamespaces, + ); + }); + + return compatible.isNotEmpty ? compatible.first : null; + } + + @override + Map getActiveSessions() { + _checkInitialized(); + + Map activeSessions = {}; + sessions.getAll().forEach((session) { + activeSessions[session.topic] = session; + }); + + return activeSessions; + } + + @override + Map getSessionsForPairing({ + required String pairingTopic, + }) { + _checkInitialized(); + + Map pairingSessions = {}; + sessions + .getAll() + .where((session) => session.pairingTopic == pairingTopic) + .forEach((session) { + pairingSessions[session.topic] = session; + }); + + return pairingSessions; + } + + @override + Map getPendingSessionProposals() { + _checkInitialized(); + + Map pendingProposals = {}; + proposals.getAll().forEach((proposal) { + pendingProposals[proposal.id.toString()] = proposal; + }); + + return pendingProposals; + } + + @override + Map getPendingSessionRequests() { + _checkInitialized(); + + Map requests = {}; + pendingRequests.getAll().forEach((r) { + requests[r.id.toString()] = r; + }); + + return requests; + } + + @override + IPairingStore get pairings => core.pairing.getStore(); + + final Set _eventEmitters = {}; + final Set _accounts = {}; + + @override + void registerEventEmitter({ + required String chainId, + required String event, + }) { + final bool isChainId = NamespaceUtils.isValidChainId(chainId); + if (!isChainId) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: + 'registerEventEmitter, chain $chainId should conform to "CAIP-2" format', + ).toSignError(); + } + final String value = _getRegisterKey(chainId, event); + SignApiValidatorUtils.isValidAccounts( + accounts: [value], + context: 'registerEventEmitter', + ); + _eventEmitters.add(value); + } + + @override + void registerAccount({ + required String chainId, + required String accountAddress, + }) { + final bool isChainId = NamespaceUtils.isValidChainId(chainId); + if (!isChainId) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: + 'registerAccount, chain $chainId should conform to "CAIP-2" format', + ).toSignError(); + } + final String value = _getRegisterKey(chainId, accountAddress); + SignApiValidatorUtils.isValidAccounts( + accounts: [value], + context: 'registerAccount', + ); + _accounts.add(value); + } + + /// ---- PRIVATE HELPERS ---- //// + + Future _resubscribeAll() async { + // If the relay is not connected, stop here + if (!core.relayClient.isConnected) { + return; + } + + // Subscribe to all the sessions + for (final SessionData session in sessions.getAll()) { + // print('Session: subscribing to ${session.topic}'); + await core.relayClient.subscribe(topic: session.topic); + } + } + + void _checkInitialized() { + if (!_initialized) { + throw Errors.getInternalError(Errors.NOT_INITIALIZED).toSignError(); + } + } + + void _confirmOnlineStateOrThrow() { + core.confirmOnlineStateOrThrow(); + } + + String _getRegisterKey(String chainId, String value) { + return '$chainId:$value'; + } + + Future _deleteSession( + String topic, { + bool expirerHasDeleted = false, + }) async { + // print('deleting session: $topic, expirerHasDeleted: $expirerHasDeleted'); + final SessionData? session = sessions.get(topic); + if (session == null) { + return; + } + await core.relayClient.unsubscribe(topic: topic); + + await sessions.delete(topic); + await core.crypto.deleteKeyPair(session.self.publicKey); + await core.crypto.deleteSymKey(topic); + if (expirerHasDeleted) { + await core.expirer.delete(topic); + } + + onSessionDelete.broadcast( + SessionDelete( + topic, + ), + ); + } + + Future _deleteProposal( + int id, { + bool expirerHasDeleted = false, + }) async { + await proposals.delete(id.toString()); + if (expirerHasDeleted) { + await core.expirer.delete(id.toString()); + } + } + + Future _deletePendingRequest( + int id, { + bool expirerHasDeleted = false, + }) async { + await pendingRequests.delete(id.toString()); + if (expirerHasDeleted) { + await core.expirer.delete(id.toString()); + } + } + + Future _setSessionExpiry(String topic, int expiry) async { + if (sessions.has(topic)) { + await sessions.update( + topic, + expiry: expiry, + ); + } + await core.expirer.set(topic, expiry); + } + + Future _setProposal(int id, ProposalData proposal) async { + await proposals.set(id.toString(), proposal); + core.expirer.set(id.toString(), proposal.expiry); + } + + Future _setPendingRequest(int id, SessionRequest request) async { + await pendingRequests.set( + id.toString(), + request, + ); + core.expirer.set( + id.toString(), + ReownCoreUtils.calculateExpiry( + ReownConstants.FIVE_MINUTES, + ), + ); + } + + Future _cleanup() async { + final List sessionTopics = []; + final List proposalIds = []; + + for (final SessionData session in sessions.getAll()) { + if (ReownCoreUtils.isExpired(session.expiry)) { + sessionTopics.add(session.topic); + } + } + for (final ProposalData proposal in proposals.getAll()) { + if (ReownCoreUtils.isExpired(proposal.expiry)) { + proposalIds.add(proposal.id); + } + } + + sessionTopics.map((topic) async { + // print('deleting expired session $topic'); + await _deleteSession(topic); + }); + proposalIds.map((id) async => await _deleteProposal(id)); + } + + /// ---- Relay Events ---- /// + + void _registerRelayClientFunctions() { + core.pairing.register( + method: MethodConstants.WC_SESSION_PROPOSE, + function: _onSessionProposeRequest, + type: ProtocolType.sign, + ); + core.pairing.register( + method: MethodConstants.WC_SESSION_SETTLE, + function: _onSessionSettleRequest, + type: ProtocolType.sign, + ); + core.pairing.register( + method: MethodConstants.WC_SESSION_UPDATE, + function: _onSessionUpdateRequest, + type: ProtocolType.sign, + ); + core.pairing.register( + method: MethodConstants.WC_SESSION_EXTEND, + function: _onSessionExtendRequest, + type: ProtocolType.sign, + ); + core.pairing.register( + method: MethodConstants.WC_SESSION_PING, + function: _onSessionPingRequest, + type: ProtocolType.sign, + ); + core.pairing.register( + method: MethodConstants.WC_SESSION_DELETE, + function: _onSessionDeleteRequest, + type: ProtocolType.sign, + ); + core.pairing.register( + method: MethodConstants.WC_SESSION_REQUEST, + function: _onSessionRequest, + type: ProtocolType.sign, + ); + core.pairing.register( + method: MethodConstants.WC_SESSION_EVENT, + function: _onSessionEventRequest, + type: ProtocolType.sign, + ); + core.pairing.register( + method: MethodConstants.WC_SESSION_AUTHENTICATE, + function: _onSessionAuthenticateRequest, + type: ProtocolType.sign, + ); + } + + bool _shouldIgnoreSessionPropose(String topic) { + final PairingInfo? pairingInfo = core.pairing.getPairing(topic: topic); + final implementSessionAuth = onSessionAuthRequest.subscriberCount > 0; + final method = MethodConstants.WC_SESSION_AUTHENTICATE; + final containsMethod = (pairingInfo?.methods ?? []).contains(method); + + return implementSessionAuth && containsMethod; + } + + Future _onSessionProposeRequest( + String topic, + JsonRpcRequest payload, [ + _, + ]) async { + if (_shouldIgnoreSessionPropose(topic)) { + core.logger.t( + 'Session Propose ignored. Session Authenticate will be used instead', + ); + return; + } + try { + core.logger.t( + '_onSessionProposeRequest, topic: $topic, payload: $payload', + ); + final proposeRequest = WcSessionProposeRequest.fromJson(payload.params); + await _isValidConnect( + requiredNamespaces: proposeRequest.requiredNamespaces, + optionalNamespaces: proposeRequest.optionalNamespaces, + sessionProperties: proposeRequest.sessionProperties, + pairingTopic: topic, + relays: proposeRequest.relays, + ); + + // If there are accounts and event emitters, then handle the Namespace generate automatically + Map? namespaces; + if (_accounts.isNotEmpty || _eventEmitters.isNotEmpty) { + namespaces = NamespaceUtils.constructNamespaces( + availableAccounts: _accounts, + availableMethods: _methodHandlers.keys.toSet(), + availableEvents: _eventEmitters, + requiredNamespaces: proposeRequest.requiredNamespaces, + optionalNamespaces: proposeRequest.optionalNamespaces, + ); + + // Check that the namespaces are conforming + try { + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: proposeRequest.requiredNamespaces, + namespaces: namespaces, + context: 'onSessionProposeRequest', + ); + } on ReownSignError catch (err) { + // If they aren't, send an error + core.logger.t( + '_onSessionProposeRequest ReownSignError: $err', + ); + final rpcOpts = MethodConstants.RPC_OPTS[payload.method]; + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError(code: err.code, message: err.message), + rpcOptions: rpcOpts?['autoReject'], + ); + + // Broadcast that a session proposal error has occurred + onSessionProposalError.broadcast( + SessionProposalErrorEvent( + payload.id, + proposeRequest.requiredNamespaces, + namespaces, + err, + ), + ); + return; + } + } + + final expiry = ReownCoreUtils.calculateExpiry( + ReownConstants.FIVE_MINUTES, + ); + final ProposalData proposal = ProposalData( + id: payload.id, + expiry: expiry, + relays: proposeRequest.relays, + proposer: proposeRequest.proposer, + requiredNamespaces: proposeRequest.requiredNamespaces, + optionalNamespaces: proposeRequest.optionalNamespaces ?? {}, + sessionProperties: proposeRequest.sessionProperties, + pairingTopic: topic, + generatedNamespaces: namespaces, + ); + + await _setProposal(payload.id, proposal); + + final verifyContext = await _getVerifyContext( + payload, + proposal.proposer.metadata, + TransportType.relay, + ); + + onSessionProposal.broadcast( + SessionProposalEvent( + payload.id, + proposal, + verifyContext, + ), + ); + } on ReownSignError catch (err) { + core.logger.e('_onSessionProposeRequest Error: $err'); + final rpcOpts = MethodConstants.RPC_OPTS[payload.method]; + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError(code: err.code, message: err.message), + rpcOptions: rpcOpts?['autoReject'], + ); + } + } + + Future _onSessionSettleRequest( + String topic, + JsonRpcRequest payload, [ + _, + ]) async { + // print('wc session settle'); + final request = WcSessionSettleRequest.fromJson(payload.params); + try { + await _isValidSessionSettleRequest(request.namespaces, request.expiry); + + final SessionProposalCompleter sProposalCompleter = + pendingProposals.removeLast(); + // print(sProposalCompleter); + + // Create the session + final SessionData session = SessionData( + topic: topic, + pairingTopic: sProposalCompleter.pairingTopic, + relay: request.relay, + expiry: request.expiry, + acknowledged: true, + controller: request.controller.publicKey, + namespaces: request.namespaces, + sessionProperties: request.sessionProperties, + self: ConnectionMetadata( + publicKey: sProposalCompleter.selfPublicKey, + metadata: metadata, + ), + peer: request.controller, + transportType: TransportType.relay, + ); + + // Update all the things: session, expiry, metadata, pairing + sessions.set(topic, session); + _setSessionExpiry(topic, session.expiry); + await core.pairing.updateMetadata( + topic: sProposalCompleter.pairingTopic, + metadata: session.peer.metadata, + ); + final pairing = core.pairing.getPairing(topic: topic); + if (pairing != null && !pairing.active) { + await core.pairing.activate(topic: topic); + } + + // Send the session back to the completer + sProposalCompleter.completer.complete(session); + + // Send back a success! + // print('responding to session settle: acknolwedged'); + await core.pairing.sendResult( + payload.id, + topic, + MethodConstants.WC_SESSION_SETTLE, + true, + ); + onSessionConnect.broadcast( + SessionConnect(session), + ); + } on ReownSignError catch (err) { + core.logger.e('_onSessionSettleRequest Error: $err'); + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.message, + ), + ); + } + } + + Future _onSessionUpdateRequest( + String topic, + JsonRpcRequest payload, [ + _, + ]) async { + try { + // print(payload.params); + final request = WcSessionUpdateRequest.fromJson(payload.params); + await _isValidUpdate(topic, request.namespaces); + await sessions.update( + topic, + namespaces: request.namespaces, + ); + await core.pairing.sendResult( + payload.id, + topic, + MethodConstants.WC_SESSION_UPDATE, + true, + ); + onSessionUpdate.broadcast( + SessionUpdate( + payload.id, + topic, + request.namespaces, + ), + ); + } on ReownSignError catch (err) { + core.logger.e('_onSessionUpdateRequest Error: $err'); + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.message, + ), + ); + } + } + + Future _onSessionExtendRequest( + String topic, + JsonRpcRequest payload, [ + _, + ]) async { + try { + final _ = WcSessionExtendRequest.fromJson(payload.params); + await _isValidSessionTopic(topic); + await _setSessionExpiry( + topic, + ReownCoreUtils.calculateExpiry( + ReownConstants.SEVEN_DAYS, + ), + ); + await core.pairing.sendResult( + payload.id, + topic, + MethodConstants.WC_SESSION_EXTEND, + true, + ); + onSessionExtend.broadcast( + SessionExtend( + payload.id, + topic, + ), + ); + } on ReownSignError catch (err) { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.message, + ), + ); + } + } + + Future _onSessionPingRequest( + String topic, + JsonRpcRequest payload, [ + _, + ]) async { + try { + final _ = WcSessionPingRequest.fromJson(payload.params); + await _isValidPing(topic); + await core.pairing.sendResult( + payload.id, + topic, + MethodConstants.WC_SESSION_PING, + true, + ); + onSessionPing.broadcast( + SessionPing( + payload.id, + topic, + ), + ); + } on ReownSignError catch (err) { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.message, + ), + ); + } + } + + Future _onSessionDeleteRequest( + String topic, + JsonRpcRequest payload, [ + _, + ]) async { + try { + final _ = WcSessionDeleteRequest.fromJson(payload.params); + await _isValidDisconnect(topic); + await core.pairing.sendResult( + payload.id, + topic, + MethodConstants.WC_SESSION_DELETE, + true, + ); + await _deleteSession(topic); + } on ReownSignError catch (err) { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.message, + ), + ); + } + } + + /// Called when a session request (method) is received by the wallet + /// Will attempt to find a handler for the request, if it doesn't, + /// it will throw an error. + Future _onSessionRequest( + String topic, + JsonRpcRequest payload, [ + TransportType transportType = TransportType.relay, + ]) async { + try { + final request = WcSessionRequestRequest.fromJson(payload.params); + await _isValidRequest( + topic, + request.chainId, + request.request, + ); + + final session = sessions.get(topic)!; + final verifyContext = await _getVerifyContext( + payload, + session.peer.metadata, + transportType, + ); + + final sessionRequest = SessionRequest( + id: payload.id, + topic: topic, + method: request.request.method, + chainId: request.chainId, + params: request.request.params, + verifyContext: verifyContext, + transportType: transportType, + ); + + // print('payload id: ${payload.id}'); + await _setPendingRequest( + payload.id, + sessionRequest, + ); + + final appLink = (session.peer.metadata.redirect?.universal ?? ''); + if (session.transportType.isLinkMode && appLink.isNotEmpty) { + // save app as supported for link mode + core.addLinkModeSupportedApp(appLink); + } + + final methodKey = _getRegisterKey( + request.chainId, + request.request.method, + ); + final handler = _methodHandlers[methodKey]; + // If a method handler has been set using registerRequestHandler we use it to process the request + if (handler != null) { + try { + await handler(topic, request.request.params); + } on ReownSignError catch (e) { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.fromJson( + e.toJson(), + ), + ); + await _deletePendingRequest(payload.id); + } on ReownSignErrorSilent catch (_) { + // Do nothing on silent error + await _deletePendingRequest(payload.id); + } catch (err) { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.toString(), + ), + ); + await _deletePendingRequest(payload.id); + } + } else { + // Otherwise we send onSessionRequest event + onSessionRequest.broadcast( + SessionRequestEvent.fromSessionRequest( + sessionRequest, + ), + ); + } + } on ReownSignError catch (err) { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.message, + ), + ); + } + } + + /// Called when a session event is received by the wallet + Future _onSessionEventRequest( + String topic, + JsonRpcRequest payload, [ + _, + ]) async { + try { + final request = WcSessionEventRequest.fromJson(payload.params); + final SessionEventParams event = request.event; + await _isValidEmit( + topic, + event, + request.chainId, + ); + + final String eventKey = _getRegisterKey( + request.chainId, + request.event.name, + ); + if (_eventHandlers.containsKey(eventKey)) { + final handler = _methodHandlers[eventKey]; + if (handler != null) { + final handler = _eventHandlers[eventKey]!; + try { + await handler( + topic, + event.data, + ); + } catch (err) { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.toString(), + ), + ); + } + } + + await core.pairing.sendResult( + payload.id, + topic, + MethodConstants.WC_SESSION_REQUEST, + true, + ); + + onSessionEvent.broadcast( + SessionEvent( + payload.id, + topic, + event.name, + request.chainId, + event.data, + ), + ); + } else { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.methodNotFound( + 'No handler found for chainId:event -> $eventKey', + ), + ); + } + } on ReownSignError catch (err) { + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams( + err.message, + ), + ); + } + } + + /// ---- Event Registers ---- /// + + void _registerInternalEvents() { + core.relayClient.onRelayClientConnect.subscribe(_onRelayConnect); + core.expirer.onExpire.subscribe(_onExpired); + core.pairing.onPairingDelete.subscribe(_onPairingDelete); + core.pairing.onPairingExpire.subscribe(_onPairingDelete); + core.heartbeat.onPulse.subscribe(_heartbeatSubscription); + } + + Future _onRelayConnect(EventArgs? args) async { + // print('Session: relay connected'); + await _resubscribeAll(); + } + + Future _onPairingDelete(PairingEvent? event) async { + // Delete all the sessions associated with the pairing + if (event == null) { + return; + } + + // Delete the proposals + final List proposalsToDelete = proposals + .getAll() + .where((proposal) => proposal.pairingTopic == event.topic) + .toList(); + + for (final proposal in proposalsToDelete) { + await _deleteProposal( + proposal.id, + ); + } + + // Delete the sessions + final List sessionsToDelete = sessions + .getAll() + .where((session) => session.pairingTopic == event.topic) + .toList(); + + for (final session in sessionsToDelete) { + await _deleteSession( + session.topic, + ); + } + } + + Future _onExpired(ExpirationEvent? event) async { + if (event == null) { + return; + } + + if (sessions.has(event.target)) { + await _deleteSession( + event.target, + expirerHasDeleted: true, + ); + onSessionExpire.broadcast( + SessionExpire( + event.target, + ), + ); + } else if (proposals.has(event.target)) { + ProposalData proposal = proposals.get(event.target)!; + await _deleteProposal( + int.parse(event.target), + expirerHasDeleted: true, + ); + onProposalExpire.broadcast( + SessionProposalEvent( + int.parse(event.target), + proposal, + ), + ); + } else if (pendingRequests.has(event.target)) { + await _deletePendingRequest( + int.parse(event.target), + expirerHasDeleted: true, + ); + return; + } + } + + void _heartbeatSubscription(EventArgs? args) async { + await checkAndExpire(); + } + + /// ---- Validation Helpers ---- /// + + Future _isValidSessionTopic(String topic) async { + if (!sessions.has(topic)) { + throw Errors.getInternalError( + Errors.NO_MATCHING_KEY, + context: "session topic doesn't exist: $topic", + ).toSignError(); + } + + if (await core.expirer.checkAndExpire(topic)) { + throw Errors.getInternalError( + Errors.EXPIRED, + context: 'session topic: $topic', + ).toSignError(); + } + + return true; + } + + Future _isValidSessionOrPairingTopic(String topic) async { + if (sessions.has(topic)) { + await _isValidSessionTopic(topic); + } else if (core.pairing.getStore().has(topic)) { + await core.pairing.isValidPairingTopic(topic: topic); + } else { + throw Errors.getInternalError( + Errors.NO_MATCHING_KEY, + context: "session or pairing topic doesn't exist: $topic", + ).toSignError(); + } + + return true; + } + + Future _isValidProposalId(int id) async { + if (!proposals.has(id.toString())) { + throw Errors.getInternalError( + Errors.NO_MATCHING_KEY, + context: "proposal id doesn't exist: $id", + ).toSignError(); + } + + if (await core.expirer.checkAndExpire(id.toString())) { + throw Errors.getInternalError( + Errors.EXPIRED, + context: 'proposal id: $id', + ).toSignError(); + } + + return true; + } + + Future _isValidPendingRequest(int id) async { + if (!pendingRequests.has(id.toString())) { + throw Errors.getInternalError( + Errors.NO_MATCHING_KEY, + context: "proposal id doesn't exist: $id", + ).toSignError(); + } + + if (await core.expirer.checkAndExpire(id.toString())) { + throw Errors.getInternalError( + Errors.EXPIRED, + context: 'pending request id: $id', + ).toSignError(); + } + + return true; + } + + /// ---- Validations ---- /// + + Future _isValidConnect({ + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + String? pairingTopic, + List? relays, + }) async { + // No need to validate sessionProperties. Strict typing enforces Strings are valid + // No need to see if the relays are a valid array and whatnot. Strict typing enforces that. + if (pairingTopic != null) { + try { + await core.pairing.isValidPairingTopic( + topic: pairingTopic, + ); + } on ReownCoreError catch (e) { + throw e.toSignError(); + } + } + + if (requiredNamespaces != null) { + SignApiValidatorUtils.isValidRequiredNamespaces( + requiredNamespaces: requiredNamespaces, + context: 'connect() check requiredNamespaces.', + ); + } + + if (optionalNamespaces != null) { + SignApiValidatorUtils.isValidRequiredNamespaces( + requiredNamespaces: optionalNamespaces, + context: 'connect() check optionalNamespaces.', + ); + } + + return true; + } + + Future _isValidApprove({ + required int id, + required Map namespaces, + Map? sessionProperties, + String? relayProtocol, + }) async { + // No need to validate sessionProperties. Strict typing enforces Strings are valid + await _isValidProposalId(id); + final ProposalData proposal = proposals.get(id.toString())!; + + // Validate the namespaces + SignApiValidatorUtils.isValidNamespaces( + namespaces: namespaces, + context: 'approve()', + ); + + // Validate the required and optional namespaces + SignApiValidatorUtils.isValidRequiredNamespaces( + requiredNamespaces: proposal.requiredNamespaces, + context: 'approve() check requiredNamespaces.', + ); + SignApiValidatorUtils.isValidRequiredNamespaces( + requiredNamespaces: proposal.optionalNamespaces, + context: 'approve() check optionalNamespaces.', + ); + + // Make sure the provided namespaces conforms with the required + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: proposal.requiredNamespaces, + namespaces: namespaces, + context: 'approve()', + ); + + return true; + } + + Future _isValidReject(int id, ReownSignError reason) async { + // No need to validate reason. Strict typing enforces ErrorResponse is valid + await _isValidProposalId(id); + return true; + } + + Future _isValidSessionSettleRequest( + Map namespaces, + int expiry, + ) async { + SignApiValidatorUtils.isValidNamespaces( + namespaces: namespaces, + context: 'onSessionSettleRequest()', + ); + + if (ReownCoreUtils.isExpired(expiry)) { + throw Errors.getInternalError( + Errors.EXPIRED, + context: 'onSessionSettleRequest()', + ).toSignError(); + } + + return true; + } + + Future _isValidUpdate( + String topic, + Map namespaces, + ) async { + await _isValidSessionTopic(topic); + SignApiValidatorUtils.isValidNamespaces( + namespaces: namespaces, + context: 'update()', + ); + final SessionData session = sessions.get(topic)!; + + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: session.requiredNamespaces ?? {}, + namespaces: namespaces, + context: 'update()', + ); + + return true; + } + + Future _isValidRequest( + String topic, + String chainId, + SessionRequestParams request, + ) async { + await _isValidSessionTopic(topic); + final SessionData session = sessions.get(topic)!; + SignApiValidatorUtils.isValidNamespacesChainId( + namespaces: session.namespaces, + chainId: chainId, + ); + SignApiValidatorUtils.isValidNamespacesRequest( + namespaces: session.namespaces, + chainId: chainId, + method: request.method, + ); + + return true; + } + + Future _isValidResponse( + String topic, + JsonRpcResponse response, + ) async { + await _isValidSessionTopic(topic); + + if (response.result == null && response.error == null) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'JSON-RPC response and error cannot both be null', + ).toSignError(); + } + + await _isValidPendingRequest(response.id); + + return true; + } + + Future _isValidPing( + String topic, + ) async { + await _isValidSessionOrPairingTopic(topic); + + return true; + } + + Future _isValidEmit( + String topic, + SessionEventParams event, + String chainId, + ) async { + await _isValidSessionTopic(topic); + final SessionData session = sessions.get(topic)!; + SignApiValidatorUtils.isValidNamespacesChainId( + namespaces: session.namespaces, + chainId: chainId, + ); + SignApiValidatorUtils.isValidNamespacesEvent( + namespaces: session.namespaces, + chainId: chainId, + eventName: event.name, + ); + + return true; + } + + Future _isValidDisconnect(String topic) async { + await _isValidSessionOrPairingTopic(topic); + + return true; + } + + Future _getVerifyContext( + JsonRpcRequest payload, + PairingMetadata proposerMetada, + TransportType transportType, + ) async { + if (transportType.isLinkMode) { + return await _getVerifyContextLinkMode(payload, proposerMetada); + } + + final defaultVerifyUrl = ReownConstants.VERIFY_SERVER; + final verifyUrl = proposerMetada.verifyUrl ?? defaultVerifyUrl; + + final metadataUri = Uri.tryParse(proposerMetada.url); + + try { + final jsonStringify = jsonEncode(payload.toJson()); + final hash = core.crypto.getUtils().hashMessage(jsonStringify); + final attestation = await core.verify.resolve(attestationId: hash); + + final validation = core.verify.getValidation( + attestation, + metadataUri, + ); + + final origin = attestation?.origin; + return VerifyContext( + origin: (origin ?? metadataUri?.origin) ?? proposerMetada.url, + verifyUrl: verifyUrl, + validation: validation, + isScam: validation == Validation.SCAM, + ); + } catch (e, s) { + if (e is! AttestationNotFound) { + core.logger.e('[$runtimeType] verify error', error: e, stackTrace: s); + } + return VerifyContext( + origin: metadataUri?.origin ?? proposerMetada.url, + verifyUrl: verifyUrl, + validation: Validation.UNKNOWN, + ); + } + } + + Future _getVerifyContextLinkMode( + JsonRpcRequest payload, + PairingMetadata requesterMetadata, + ) async { + final universalLink = requesterMetadata.redirect?.universal ?? ''; + final domainUrl = requesterMetadata.url; + + if (universalLink.isEmpty || domainUrl.isEmpty) { + return VerifyContext( + origin: requesterMetadata.url, + verifyUrl: ReownConstants.VERIFY_SERVER, + validation: Validation.INVALID, + ); + } + + final universalHost = Uri.parse(universalLink).host; + final domainHost = Uri.parse(domainUrl).host; + + final matches = + universalHost == domainHost || universalHost.endsWith('.$domainHost'); + if (matches) { + return VerifyContext( + origin: domainHost, + verifyUrl: ReownConstants.VERIFY_SERVER, + validation: Validation.VALID, + ); + } + + return VerifyContext( + origin: domainHost, + verifyUrl: ReownConstants.VERIFY_SERVER, + validation: Validation.INVALID, + ); + } + + // NEW 1-CA METHOD (Should this be private?) + + @override + Future validateSignedCacao({ + required Cacao cacao, + required String projectId, + }) async { + final CacaoSignature signature = cacao.s; + final CacaoPayload payload = cacao.p; + + final reconstructed = formatAuthMessage( + iss: payload.iss, + cacaoPayload: CacaoRequestPayload.fromCacaoPayload(payload), + ); + + final walletAddress = AddressUtils.getDidAddress(payload.iss); + final chainId = AddressUtils.getDidChainId(payload.iss); + + final isValid = await AuthSignature.verifySignature( + walletAddress.toEIP55(), + reconstructed, + signature, + chainId, + projectId, + ); + + return isValid; + } + + // FORMER AUTH ENGINE PROPERTY + @override + String formatAuthMessage({ + required String iss, + required CacaoRequestPayload cacaoPayload, + }) { + final header = + '${cacaoPayload.domain} wants you to sign in with your Ethereum account:'; + final walletAddress = AddressUtils.getDidAddress(iss); + + if (cacaoPayload.aud.isEmpty) { + throw ReownSignError(code: -1, message: 'aud is required'); + } + + String statement = cacaoPayload.statement ?? ''; + final uri = 'URI: ${cacaoPayload.aud}'; + final version = 'Version: ${cacaoPayload.version}'; + final chainId = 'Chain ID: ${AddressUtils.getDidChainId(iss)}'; + final nonce = 'Nonce: ${cacaoPayload.nonce}'; + final issuedAt = 'Issued At: ${cacaoPayload.iat}'; + final expirationTime = (cacaoPayload.exp != null) + ? 'Expiration Time: ${cacaoPayload.exp}' + : null; + final notBefore = + (cacaoPayload.nbf != null) ? 'Not Before: ${cacaoPayload.nbf}' : null; + final requestId = (cacaoPayload.requestId != null) + ? 'Request ID: ${cacaoPayload.requestId}' + : null; + final resources = cacaoPayload.resources != null && + cacaoPayload.resources!.isNotEmpty + ? 'Resources:\n${cacaoPayload.resources!.map((resource) => '- $resource').join('\n')}' + : null; + final recap = ReCapsUtils.getRecapFromResources( + resources: cacaoPayload.resources, + ); + if (recap != null) { + final decoded = ReCapsUtils.decodeRecap(recap); + statement = ReCapsUtils.formatStatementFromRecap( + statement: statement, + recap: decoded, + ); + } + + final message = [ + header, + walletAddress.toEIP55(), + '', + statement, + '', + uri, + version, + chainId, + nonce, + issuedAt, + expirationTime, + notBefore, + requestId, + resources, + ].where((element) => element != null).join('\n'); + + return message; + } + + @override + Map getPendingSessionAuthRequests() { + Map pendingSessionAuthRequests = {}; + sessionAuthRequests.getAll().forEach((key) { + pendingSessionAuthRequests[key.id] = key; + }); + return pendingSessionAuthRequests; + } + + bool _isLinkModeAuthenticate(String? walletLink) { + final selfLinkMode = metadata.redirect?.linkMode == true; + final selfLink = (metadata.redirect?.universal ?? ''); + final walletUniversalLink = (walletLink ?? ''); + return selfLinkMode && + selfLink.isNotEmpty && + walletUniversalLink.isNotEmpty && + core.getLinkModeSupportedApps().contains(walletLink); + } + + @override + Future authenticate({ + required SessionAuthRequestParams params, + String? walletUniversalLink, + String? pairingTopic, + List>? methods = const [ + [MethodConstants.WC_SESSION_AUTHENTICATE] + ], + }) async { + _checkInitialized(); + AuthApiValidators.isValidAuthenticate(params); + + final isLinkMode = _isLinkModeAuthenticate(walletUniversalLink); + final transportType = + isLinkMode ? TransportType.linkMode : TransportType.relay; + if (!transportType.isLinkMode) { + _confirmOnlineStateOrThrow(); + } + + final chains = params.chains; + final resources = params.resources ?? []; + final requestMethods = params.methods ?? []; + + String? pTopic = pairingTopic; + Uri? connectionUri; + + if (pTopic == null) { + final CreateResponse pairing = await core.pairing.create( + methods: methods, + ); + pTopic = pairing.topic; + connectionUri = pairing.uri; + } else { + core.pairing.isValidPairingTopic(topic: pTopic); + } + + final publicKey = await core.crypto.generateKeyPair(); + final responseTopic = core.crypto.getUtils().hashKey(publicKey); + + await Future.wait([ + authKeys.set( + StringConstants.OCAUTH_CLIENT_PUBLIC_KEY_NAME, + AuthPublicKey(publicKey: publicKey), + ), + pairingTopics.set(responseTopic, pTopic), + ]); + + if (requestMethods.isNotEmpty) { + final namespace = NamespaceUtils.getNamespaceFromChain(chains.first); + String recap = ReCapsUtils.createEncodedRecap( + namespace, + 'request', + requestMethods, + ); + final existingRecap = ReCapsUtils.getRecapFromResources( + resources: resources, + ); + if (existingRecap != null) { + // per Recaps spec, recap must occupy the last position in the resources array + // using .removeLast() to remove the element given we already checked it's a recap and will replace it + recap = ReCapsUtils.mergeEncodedRecaps(recap, resources.removeLast()); + } + resources.add(recap); + } + + // Subscribe to the responseTopic because we expect the response to use this topic + await core.relayClient.subscribe(topic: responseTopic); + + final id = JsonRpcUtils.payloadId(); + final fallbackId = JsonRpcUtils.payloadId(); + + // Ensure the expiry is greater than the minimum required for the request - currently 1h + final method = MethodConstants.WC_SESSION_AUTHENTICATE; + final opts = MethodConstants.RPC_OPTS[method]!['req']!; + final ttl = max((params.expiry ?? 0), opts.ttl); + + final currentDateTime = DateTime.now(); + final expiryTimestamp = currentDateTime.add(Duration(seconds: ttl)); + + final request = WcSessionAuthRequestParams( + authPayload: SessionAuthPayload.fromRequestParams(params).copyWith( + resources: resources, + ), + requester: ConnectionMetadata( + publicKey: publicKey, + metadata: metadata, + ), + expiryTimestamp: expiryTimestamp.millisecondsSinceEpoch, + ); + + // Set the one time use receiver public key for decoding the Type 1 envelope + await core.pairing.setReceiverPublicKey( + topic: responseTopic, + publicKey: publicKey, + expiry: ttl, + ); + + Completer completer = Completer(); + + // ----- build fallback session proposal request ----- // + + final fallbackMethod = MethodConstants.WC_SESSION_PROPOSE; + final fallbackOpts = MethodConstants.RPC_OPTS[fallbackMethod]!['req']!; + final fallbackExpiryTimestamp = DateTime.now().add( + Duration(seconds: fallbackOpts.ttl), + ); + final proposalData = ProposalData( + id: fallbackId, + requiredNamespaces: {}, + optionalNamespaces: { + 'eip155': RequiredNamespace( + chains: chains, + methods: {'personal_sign', ...requestMethods}.toList(), + events: EventsConstants.requiredEvents, + ), + }, + relays: [Relay(ReownConstants.RELAYER_DEFAULT_PROTOCOL)], + expiry: fallbackExpiryTimestamp.millisecondsSinceEpoch, + proposer: ConnectionMetadata( + publicKey: publicKey, + metadata: metadata, + ), + pairingTopic: pTopic, + ); + final proposeRequest = WcSessionProposeRequest( + relays: proposalData.relays, + requiredNamespaces: proposalData.requiredNamespaces, + optionalNamespaces: proposalData.optionalNamespaces, + proposer: proposalData.proposer, + ); + await _setProposal(proposalData.id, proposalData); + + Completer completerFallback = Completer(); + + pendingProposals.add( + SessionProposalCompleter( + id: fallbackId, + selfPublicKey: proposalData.proposer.publicKey, + pairingTopic: proposalData.pairingTopic, + requiredNamespaces: proposalData.requiredNamespaces, + optionalNamespaces: proposalData.optionalNamespaces, + completer: completerFallback, + ), + ); + + // ------------------------------------------------------- // + late final Uri linkModeUri; + if (isLinkMode) { + final payload = JsonRpcUtils.formatJsonRpcRequest( + method, + request.toJson(), + id: id, + ); + final message = await core.crypto.encode( + pTopic, + payload, + options: EncodeOptions(type: EncodeOptions.TYPE_2), + ); + linkModeUri = ReownCoreUtils.getLinkModeURL( + walletUniversalLink!, + pTopic, + message!, + ).toLinkMode; + } else { + // Send Session Proposal request (Only when on Relay mode) + _connectResponseHandler( + pTopic, + proposeRequest, + fallbackId, + ); + } + + // Send One-Click Auth request (When on Relay and LinkMode) + _sessionAuthResponseHandler( + id: id, + pairingTopic: pTopic, + responseTopic: responseTopic, + walletUniversalLink: walletUniversalLink, + isLinkMode: isLinkMode, + request: request, + ttl: ttl, + completer: completer, + ); + + return SessionAuthRequestResponse( + id: id, + pairingTopic: pTopic, + // linkModeUri is sent in the response so the host app can trigger it + uri: isLinkMode ? linkModeUri : connectionUri, + completer: completer, + ); + } + + Future _sessionAuthResponseHandler({ + required int id, + required String pairingTopic, + required String responseTopic, + required String? walletUniversalLink, + required bool isLinkMode, + required int ttl, + required WcSessionAuthRequestParams request, + required Completer completer, + }) async { + // + late WcSessionAuthRequestResult result; + try { + final Map response = await core.pairing.sendRequest( + pairingTopic, + MethodConstants.WC_SESSION_AUTHENTICATE, + request.toJson(), + id: id, + ttl: ttl, + appLink: isLinkMode ? walletUniversalLink : null, + // We don't want to open the appLink in this case as it will be opened by the host app + openUrl: false, + ); + result = WcSessionAuthRequestResult.fromJson(response); + } catch (error) { + final response = SessionAuthResponse( + id: id, + topic: responseTopic, + jsonRpcError: (error is JsonRpcError) ? error : null, + error: (error is! JsonRpcError) + ? ReownSignError(code: -1, message: '$error') + : null, + ); + onSessionAuthResponse.broadcast(response); + completer.complete(response); + return; + } + + await core.pairing.activate(topic: pairingTopic); + + final List cacaos = result.cacaos; + final ConnectionMetadata responder = result.responder; + + final approvedMethods = {}; + final approvedAccounts = {}; + + try { + for (final Cacao cacao in cacaos) { + final isValid = await validateSignedCacao( + cacao: cacao, + projectId: core.projectId, + ); + if (!isValid) { + throw Errors.getSdkError( + Errors.SIGNATURE_VERIFICATION_FAILED, + context: 'Invalid signature', + ).toSignError(); + } + + // This is used on Auth request, would it be needed on 1-CA? + // await completeRequests.set( + // id.toString(), + // StoredCacao.fromCacao( + // id: id, + // pairingTopic: pairingTopic, + // cacao: cacao, + // ), + // ); + + final CacaoPayload payload = cacao.p; + final chainId = AddressUtils.getDidChainId(payload.iss); + final approvedChains = ['eip155:$chainId']; + + final recap = ReCapsUtils.getRecapFromResources( + resources: payload.resources, + ); + if (recap != null) { + final methodsfromRecap = ReCapsUtils.getMethodsFromRecap(recap); + final chainsFromRecap = ReCapsUtils.getChainsFromRecap(recap); + approvedMethods.addAll(methodsfromRecap); + approvedChains.addAll(chainsFromRecap); + } + + final parsedAddress = AddressUtils.getDidAddress(payload.iss); + for (var chain in approvedChains.toSet()) { + approvedAccounts.add('$chain:${parsedAddress.toEIP55()}'); + } + } + } on ReownSignError catch (e) { + final resp = SessionAuthResponse( + id: id, + topic: responseTopic, + error: ReownSignError( + code: e.code, + message: e.message, + ), + ); + onSessionAuthResponse.broadcast(resp); + completer.complete(resp); + // disconnectSession(topic: topic, reason: reason) // TODO + return; + } + + final sessionTopic = await core.crypto.generateSharedKey( + request.requester.publicKey, + responder.publicKey, + ); + + SessionData? session; + if (approvedMethods.isNotEmpty) { + session = SessionData( + topic: sessionTopic, + acknowledged: true, + self: ConnectionMetadata( + publicKey: request.requester.publicKey, + metadata: metadata, + ), + peer: responder, + controller: request.requester.publicKey, + expiry: ReownCoreUtils.calculateExpiry( + ReownConstants.SEVEN_DAYS, + ), + relay: Relay(ReownConstants.RELAYER_DEFAULT_PROTOCOL), + pairingTopic: pairingTopic, + namespaces: NamespaceUtils.buildNamespacesFromAuth( + accounts: approvedAccounts, + methods: approvedMethods, + ), + authentication: cacaos, + transportType: + isLinkMode ? TransportType.linkMode : TransportType.relay, + ); + + await core.relayClient.subscribe(topic: sessionTopic); + await sessions.set(sessionTopic, session); + await core.pairing.updateMetadata( + topic: pairingTopic, + metadata: responder.metadata, + ); + + session = sessions.get(sessionTopic); + } + + await _addLinkModeSupportedApp( + responder, + sessionTopic, + walletUniversalLink, + ); + + final resp = SessionAuthResponse( + id: id, + topic: responseTopic, + auths: cacaos, + session: session, + ); + onSessionAuthResponse.broadcast(resp); + completer.complete(resp); + } + + Future _addLinkModeSupportedApp( + ConnectionMetadata responder, + String sessionTopic, + String? walletUniversalLink, + ) async { + final selfLinkMode = metadata.redirect?.linkMode == true; + final responderLinkMode = responder.metadata.redirect?.linkMode == true; + final walletLink = (walletUniversalLink ?? ''); + final matchesLink = walletLink == responder.metadata.redirect?.universal; + if (selfLinkMode && responderLinkMode) { + if (walletLink.isNotEmpty && matchesLink) { + // save wallet link in array of apps that support linkMode + await core.addLinkModeSupportedApp(walletUniversalLink!); + core.logger.i( + '[$runtimeType] session update $sessionTopic to linkMode', + ); + await sessions.update( + sessionTopic, + transportType: TransportType.linkMode, + ); + } + } + } + + @override + Future approveSessionAuthenticate({ + required int id, + List? auths, + }) async { + _checkInitialized(); + + final pendingSessionAuthRequests = getPendingSessionAuthRequests(); + + if (!pendingSessionAuthRequests.containsKey(id)) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'approveSessionAuthenticate() ' + 'Could not find pending auth request with id $id', + ).toSignError(); + } + + AuthApiValidators.isValidRespondAuthenticate( + id: id, + pendingRequests: pendingSessionAuthRequests, + auths: auths, + ); + + final pendingRequest = pendingSessionAuthRequests[id]!; + if (!pendingRequest.transportType.isLinkMode) { + _confirmOnlineStateOrThrow(); + } + + final receiverPublicKey = pendingRequest.requester.publicKey; + final senderPublicKey = await core.crypto.generateKeyPair(); + final responseTopic = core.crypto.getUtils().hashKey(receiverPublicKey); + + final encodeOpts = EncodeOptions( + type: EncodeOptions.TYPE_1, + receiverPublicKey: receiverPublicKey, + senderPublicKey: senderPublicKey, + ); + + final approvedMethods = {}; + final approvedAccounts = {}; + for (final Cacao cacao in auths!) { + final isValid = await validateSignedCacao( + cacao: cacao, + projectId: core.projectId, + ); + if (!isValid) { + final error = Errors.getSdkError( + Errors.SIGNATURE_VERIFICATION_FAILED, + context: 'Signature verification failed', + ).toSignError(); + await core.pairing.sendError( + id, + responseTopic, + MethodConstants.WC_SESSION_AUTHENTICATE, + JsonRpcError(code: error.code, message: error.message), + encodeOptions: encodeOpts, + ); + throw error; + } + + final CacaoPayload payload = cacao.p; + final chainId = AddressUtils.getDidChainId(payload.iss); + final approvedChains = ['eip155:$chainId']; + + final recap = ReCapsUtils.getRecapFromResources( + resources: payload.resources, + ); + if (recap != null) { + final methodsfromRecap = ReCapsUtils.getMethodsFromRecap(recap); + final chainsFromRecap = ReCapsUtils.getChainsFromRecap(recap); + approvedMethods.addAll(methodsfromRecap); + approvedChains.addAll(chainsFromRecap); + } + + final parsedAddress = AddressUtils.getDidAddress(payload.iss); + for (var chain in approvedChains.toSet()) { + approvedAccounts.add('$chain:${parsedAddress.toEIP55()}'); + } + } + + final sessionTopic = await core.crypto.generateSharedKey( + senderPublicKey, + receiverPublicKey, + ); + + SessionData? session; + if (approvedMethods.isNotEmpty) { + session = SessionData( + topic: sessionTopic, + acknowledged: true, + self: ConnectionMetadata( + publicKey: senderPublicKey, + metadata: metadata, + ), + peer: pendingRequest.requester, + controller: receiverPublicKey, + expiry: ReownCoreUtils.calculateExpiry( + ReownConstants.SEVEN_DAYS, + ), + relay: Relay(ReownConstants.RELAYER_DEFAULT_PROTOCOL), + pairingTopic: pendingRequest.pairingTopic, + namespaces: NamespaceUtils.buildNamespacesFromAuth( + accounts: approvedAccounts, + methods: approvedMethods, + ), + authentication: auths, + transportType: pendingRequest.transportType, + ); + + await core.relayClient.subscribe(topic: sessionTopic); + await sessions.set(sessionTopic, session); + await core.pairing.updateMetadata( + topic: pendingRequest.pairingTopic, + metadata: pendingRequest.requester.metadata, + ); + } + + await sessionAuthRequests.delete(id.toString()); + await core.pairing.activate(topic: pendingRequest.pairingTopic); + + final result = WcSessionAuthRequestResult( + cacaos: auths, + responder: ConnectionMetadata( + publicKey: senderPublicKey, + metadata: metadata, + ), + ); + await core.pairing.sendResult( + id, + responseTopic, + MethodConstants.WC_SESSION_AUTHENTICATE, + result.toJson(), + encodeOptions: encodeOpts, + appLink: _getAppLinkIfEnabled(pendingRequest.requester.metadata), + ); + + return ApproveResponse( + topic: sessionTopic, + session: session, + ); + } + + @override + Future rejectSessionAuthenticate({ + required int id, + required ReownSignError reason, + }) async { + _checkInitialized(); + + final pendingSessionAuthRequests = getPendingSessionAuthRequests(); + + if (!pendingSessionAuthRequests.containsKey(id)) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'rejectSessionAuthenticate() ' + 'Could not find pending auth request with id $id', + ).toSignError(); + } + + final pendingRequest = pendingSessionAuthRequests[id]!; + if (!pendingRequest.transportType.isLinkMode) { + _confirmOnlineStateOrThrow(); + } + + final receiverPublicKey = pendingRequest.requester.publicKey; + final senderPublicKey = await core.crypto.generateKeyPair(); + final responseTopic = core.crypto.getUtils().hashKey(receiverPublicKey); + + final encodeOpts = EncodeOptions( + type: EncodeOptions.TYPE_1, + receiverPublicKey: receiverPublicKey, + senderPublicKey: senderPublicKey, + ); + + final method = MethodConstants.WC_SESSION_AUTHENTICATE; + final rpcOpts = MethodConstants.RPC_OPTS[method]; + await core.pairing.sendError( + id, + responseTopic, + method, + JsonRpcError(code: reason.code, message: reason.message), + encodeOptions: encodeOpts, + rpcOptions: rpcOpts?['reject'], + appLink: _getAppLinkIfEnabled(pendingRequest.requester.metadata), + ); + + await sessionAuthRequests.delete(id.toString()); + await _deleteProposal(id); + } + + void _onSessionAuthenticateRequest( + String topic, + JsonRpcRequest payload, [ + TransportType transportType = TransportType.relay, + ]) async { + core.logger.t( + '_onSessionAuthenticateRequest, topic: $topic, payload: $payload', + ); + + if (transportType.isLinkMode && !sessions.has(topic)) { + final pairingInfo = PairingInfo( + topic: topic, + expiry: ReownCoreUtils.calculateExpiry( + ReownConstants.SEVEN_DAYS, + ), + relay: Relay(ReownConstants.RELAYER_DEFAULT_PROTOCOL), + // Will be activated during approveSessionAuthenticate() + // or deleted during rejectSessionAuthenticate() + active: false, + ); + + await pairings.set(topic, pairingInfo); + core.logger.t( + '[$runtimeType] _onSessionAuthenticateRequest pairingInfo $pairingInfo', + ); + } + + final sessionAuthRequest = WcSessionAuthRequestParams.fromJson( + payload.params, + ); + try { + final cacaoPayload = CacaoRequestPayload.fromSessionAuthPayload( + sessionAuthRequest.authPayload, + ); + + final verifyContext = await _getVerifyContext( + payload, + sessionAuthRequest.requester.metadata, + transportType, + ); + + sessionAuthRequests.set( + payload.id.toString(), + PendingSessionAuthRequest( + id: payload.id, + pairingTopic: topic, + requester: sessionAuthRequest.requester, + authPayload: cacaoPayload, + expiryTimestamp: sessionAuthRequest.expiryTimestamp, + verifyContext: verifyContext, + transportType: transportType, + ), + ); + + final appLink = + (sessionAuthRequest.requester.metadata.redirect?.universal ?? ''); + if (transportType.isLinkMode && appLink.isNotEmpty) { + // save app as supported for link mode + core.addLinkModeSupportedApp(appLink); + } + + onSessionAuthRequest.broadcast( + SessionAuthRequest( + id: payload.id, + topic: topic, + requester: sessionAuthRequest.requester, + authPayload: sessionAuthRequest.authPayload, + verifyContext: verifyContext, + transportType: transportType, + ), + ); + } on ReownSignError catch (err) { + final receiverPublicKey = sessionAuthRequest.requester.publicKey; + final senderPublicKey = await core.crypto.generateKeyPair(); + + final encodeOpts = EncodeOptions( + type: EncodeOptions.TYPE_1, + receiverPublicKey: receiverPublicKey, + senderPublicKey: senderPublicKey, + ); + final rpcOpts = MethodConstants.RPC_OPTS[payload.method]; + await core.pairing.sendError( + payload.id, + topic, + payload.method, + JsonRpcError.invalidParams(err.message), + encodeOptions: encodeOpts, + rpcOptions: rpcOpts?['autoReject'], + ); + } + } + + @override + Future dispatchEnvelope(String url) async { + final topic = ReownCoreUtils.getSearchParamFromURL(url, 'topic'); + final envelope = ReownCoreUtils.getSearchParamFromURL(url, 'wc_ev'); + + if (envelope.isEmpty) { + throw ReownSignError(code: 0, message: 'Envelope not found'); + } + if (topic.isEmpty) { + throw ReownSignError(code: 0, message: 'Topic not found'); + } + + final session = sessions.get(topic); + if (session != null) { + core.logger.i('[$runtimeType] sessions.update $topic to linkMode'); + await sessions.update( + session.topic, + transportType: TransportType.linkMode, + ); + } + + core.pairing.dispatchEnvelope( + topic: topic, + envelope: envelope, + ); + } + + bool _isLinkModeEnabled(PairingMetadata? peerMetadata) { + if (peerMetadata == null) return false; + + final selfLink = (metadata.redirect?.universal ?? ''); + final selfLinkMode = metadata.redirect?.linkMode == true; + final peerLink = (peerMetadata.redirect?.universal ?? ''); + final peerLinkMode = peerMetadata.redirect?.linkMode == true; + return selfLinkMode && + selfLink.isNotEmpty && + peerLinkMode && + peerLink.isNotEmpty && + core.getLinkModeSupportedApps().contains(peerLink); + } + + String? _getAppLinkIfEnabled(PairingMetadata? peerMetadata) { + final isLinkMode = _isLinkModeEnabled(peerMetadata); + return isLinkMode ? peerMetadata?.redirect?.universal : null; + } + + @override + Future redirectToDapp({ + required String topic, + required Redirect? redirect, + }) { + return _callRedirect(topic, redirect); + } + + @override + Future redirectToWallet({ + required String topic, + required Redirect? redirect, + }) { + return _callRedirect(topic, redirect); + } + + Future _callRedirect(String topic, Redirect? redirect) async { + final hasSession = topic.isNotEmpty && sessions.has(topic); + if (hasSession) { + final session = sessions.get(topic)!; + final isLinkMode = session.transportType.isLinkMode; + final isEnabled = _isLinkModeEnabled(session.peer.metadata); + if (isLinkMode && isEnabled) { + // linkMode redirection is already handled in the requests + return false; + } + } + + final scheme = redirect?.native; + try { + if (scheme == null) { + throw ReownSignError( + code: 0, + message: 'scheme `$scheme` is invalid', + ); + } + final success = await ReownCoreUtils.openURL(scheme); + if (!success) { + throw ReownSignError( + code: 0, + message: 'Can not open $scheme', + ); + } + return true; + } catch (e) { + rethrow; + } + } +} diff --git a/packages/reown_sign/lib/store/i_sessions.dart b/packages/reown_sign/lib/store/i_sessions.dart new file mode 100644 index 0000000..1e58e19 --- /dev/null +++ b/packages/reown_sign/lib/store/i_sessions.dart @@ -0,0 +1,12 @@ +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_sign/models/session_models.dart'; + +abstract class ISessions extends IGenericStore { + Future update( + String topic, { + int? expiry, + Map? namespaces, + TransportType? transportType, + }); +} diff --git a/packages/reown_sign/lib/store/sessions.dart b/packages/reown_sign/lib/store/sessions.dart new file mode 100644 index 0000000..b0ea399 --- /dev/null +++ b/packages/reown_sign/lib/store/sessions.dart @@ -0,0 +1,41 @@ +import 'package:reown_core/relay_client/relay_client_models.dart'; +import 'package:reown_core/store/generic_store.dart'; +// +import 'package:reown_sign/store/i_sessions.dart'; +import 'package:reown_sign/models/session_models.dart'; + +class Sessions extends GenericStore implements ISessions { + Sessions({ + required super.storage, + required super.context, + required super.version, + required super.fromJson, + }); + + @override + Future update( + String topic, { + int? expiry, + Map? namespaces, + TransportType? transportType, + }) async { + checkInitialized(); + + SessionData? info = get(topic); + if (info == null) { + return; + } + + if (expiry != null) { + info = info.copyWith(expiry: expiry); + } + if (namespaces != null) { + info = info.copyWith(namespaces: namespaces); + } + if (transportType != null) { + info = info.copyWith(transportType: transportType); + } + + await set(topic, info); + } +} diff --git a/packages/reown_sign/lib/utils/address_utils.dart b/packages/reown_sign/lib/utils/address_utils.dart new file mode 100644 index 0000000..e43a21c --- /dev/null +++ b/packages/reown_sign/lib/utils/address_utils.dart @@ -0,0 +1,22 @@ +// import 'package:web3dart/web3dart.dart'; + +class AddressUtils { + static String getDidAddress(String iss) { + return iss.split(':').last; + } + + static String getDidChainId(String iss) { + return iss.split(':')[3]; + } + + static String getNamespaceDidChainId(String iss) { + return iss.substring(iss.indexOf(RegExp(r':')) + 1); + } +} + +extension AddressUtilsExtension on String { + String toEIP55() { + return this; + // return EthereumAddress.fromHex(this).hexEip55; + } +} diff --git a/packages/reown_sign/lib/utils/auth_api_validators.dart b/packages/reown_sign/lib/utils/auth_api_validators.dart new file mode 100644 index 0000000..7e56e2f --- /dev/null +++ b/packages/reown_sign/lib/utils/auth_api_validators.dart @@ -0,0 +1,148 @@ +import 'package:reown_core/reown_core.dart'; +// import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/reown_sign.dart'; +import 'package:reown_sign/utils/constants.dart'; + +class AuthApiValidators { + static bool isValidRequestExpiry(int expiry) { + return StringConstants.AUTH_REQUEST_EXPIRY_MIN <= expiry && + expiry <= StringConstants.AUTH_REQUEST_EXPIRY_MAX; + } + + // static bool isValidRequest(AuthRequestParams params) { + // if (!NamespaceUtils.isValidUrl(params.aud)) { + // throw Errors.getInternalError( + // Errors.MISSING_OR_INVALID, + // context: + // 'requestAuth() invalid aud: ${params.aud}. Must be a valid url.', + // ); + // } + + // if (params.nonce.isEmpty) { + // throw Errors.getInternalError( + // Errors.MISSING_OR_INVALID, + // context: 'requestAuth() nonce must be nonempty.', + // ); + // } + + // // params.type == null || params.type == CacaoHeader.EIP4361 + // if (params.type != null && params.type != CacaoHeader.EIP4361) { + // throw Errors.getInternalError( + // Errors.MISSING_OR_INVALID, + // context: 'requestAuth() type must null or ${CacaoHeader.EIP4361}.', + // ); + // } + + // final expiry = params.expiry; + // if (expiry != null && !isValidRequestExpiry(expiry)) { + // throw Errors.getInternalError( + // Errors.MISSING_OR_INVALID, + // context: 'requestAuth() expiry: $expiry. ' + // 'Expiry must be a number (in seconds) between ${StringConstants.AUTH_REQUEST_EXPIRY_MIN} and ${StringConstants.AUTH_REQUEST_EXPIRY_MAX}', + // ); + // } + + // return true; + // } + + // static bool isValidRespond({ + // required int id, + // required Map pendingRequests, + // CacaoSignature? signature, + // ReownSignError? error, + // }) { + // if (!pendingRequests.containsKey(id)) { + // throw Errors.getInternalError( + // Errors.MISSING_OR_INVALID, + // context: 'respondAuth() invalid id: $id. No pending request found.', + // ); + // } + + // if (signature == null && error == null) { + // throw Errors.getInternalError( + // Errors.MISSING_OR_INVALID, + // context: + // 'respondAuth() invalid response. Must contain either signature or error.', + // ); + // } + + // return true; + // } + + static bool isValidAuthenticate(SessionAuthRequestParams params) { + if (params.chains.isEmpty) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'authenticate() invalid chains: Must not be emtpy.', + ).toSignError(); + } + + if (!NamespaceUtils.isValidUrl(params.uri)) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: + 'authenticate() invalid uri: ${params.uri}. Must be a valid url.', + ).toSignError(); + } + + if (params.nonce.isEmpty) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'authenticate() nonce must be nonempty.', + ).toSignError(); + } + + if (params.type != null && params.type!.t != CacaoHeader.EIP4361) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'authenticate() type must null or ${CacaoHeader.EIP4361}.', + ).toSignError(); + } + + final uniqueNamespaces = params.chains.map((chain) { + return NamespaceUtils.getNamespaceFromChain(chain); + }).toSet(); + if (uniqueNamespaces.length > 1) { + throw Errors.getInternalError( + Errors.NON_CONFORMING_NAMESPACES, + context: + 'authenticate() Multi-namespace requests are not supported. Please request single namespace only.', + ).toSignError(); + } + + final namespace = NamespaceUtils.getNamespaceFromChain(params.chains.first); + if (namespace != 'eip155') { + throw Errors.getInternalError( + Errors.NON_CONFORMING_NAMESPACES, + context: + 'authenticate() Only eip155 namespace is supported for authenticated sessions. Please use .connect() for non-eip155 chains.', + ).toSignError(); + } + + return true; + } + + static bool isValidRespondAuthenticate({ + required int id, + required Map pendingRequests, + List? auths, + }) { + if (!pendingRequests.containsKey(id)) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: + 'approveSessionAuthenticate() Could not find pending auth request with id $id', + ).toSignError(); + } + + if (auths == null || auths.isEmpty) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: + 'approveSessionAuthenticate() invalid response. Must contain Cacao signatures.', + ).toSignError(); + } + + return true; + } +} diff --git a/packages/reown_sign/lib/utils/auth_signature.dart b/packages/reown_sign/lib/utils/auth_signature.dart new file mode 100644 index 0000000..97b237e --- /dev/null +++ b/packages/reown_sign/lib/utils/auth_signature.dart @@ -0,0 +1,307 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:convert/convert.dart'; +import 'package:http/http.dart' as http; + +import 'package:pointycastle/digests/keccak.dart'; +import 'package:reown_core/pairing/utils/json_rpc_utils.dart'; + +import 'package:reown_sign/models/cacao_models.dart'; +import 'package:reown_sign/models/session_auth_models.dart'; +import 'package:reown_sign/models/basic_models.dart'; +import 'package:reown_sign/utils/address_utils.dart'; +import 'package:reown_sign/utils/constants.dart'; +import 'package:reown_sign/utils/recaps_utils.dart'; +import 'package:web3dart/crypto.dart' as crypto; + +class AuthSignature { + static final KeccakDigest keccakDigest = KeccakDigest(256); + static Uint8List keccak256(Uint8List input) { + keccakDigest.reset(); + return keccakDigest.process(input); + } + + static Uint8List hashMessage(String message) { + return keccak256( + Uint8List.fromList( + utf8.encode( + [ + '\x19Ethereum Signed Message:\n', + message.length.toString(), + message, + ].join(), + ), + ), + ); + } + + static int getNormalizedV(int v) { + if (v == 0 || v == 27) { + return 27; + } + if (v == 1 || v == 28) { + return 28; + } + return v & 1 == 1 ? 27 : 28; + } + + static bool isValidEip191Signature( + String address, + String message, + String sig, + ) { + // Get the sig bytes + // print(sig); + final sigBytes = Uint8List.fromList( + hex.decode(sig.substring(2)), + ); + + // If the sig bytes aren't 65 bytes long, throw an error + if (sigBytes.length != 65) { + throw Exception('Invalid signature length'); + } + + // Get the r and s values from the sig bytes + final r = BigInt.parse( + hex.encode(sigBytes.sublist(0, 32)), + radix: 16, + ); + final s = BigInt.parse( + hex.encode(sigBytes.sublist(32, 64)), + radix: 16, + ); + // print(sigBytes[64]); + final v = getNormalizedV(sigBytes[64]); + // print(r); + // print(s); + // print(v); + + // // Recover the public key from the signature + // Uint8List? publicKeyBytes = AuthSecp256k1.recoverPublicKeyFromSignature( + // v - 27, + // r, + // s, + // hashMessage(message), + // ); + + // // If the public key is null, return false + // if (publicKeyBytes == null) { + // print('Could not derive publicKey'); + // return false; + // } + + // Convert the public key to an address + final publicKeyBytes = crypto.ecRecover( + hashMessage(message), + crypto.MsgSignature(r, s, v), + ); + // print(hex.encode(publicKeyBytes)); + final hashedPubKeyBytes = keccak256(publicKeyBytes); + final addressBytes = hashedPubKeyBytes.sublist(12, 32); + final recoveredAddress = '0x${hex.encode(addressBytes)}'; + + // final String recoveredAddress = EthSigUtil.recoverSignature( + // signature: sig, + // message: hashMessage(message), + // // Uint8List.fromList( + // // ascii.encode(message), + // // ), + // ); + + // print(recoveredAddress.toLowerCase()); + // print(address.toLowerCase()); + + return recoveredAddress.toLowerCase() == address.toLowerCase(); + } + + static Future isValidEip1271Signature( + String address, + String reconstructedMessage, + String cacaoSignature, + String chainId, + String projectId, + ) async { + try { + const String eip1271MagicValue = '0x1626ba7e'; + const String dynamicTypeOffset = + '0000000000000000000000000000000000000000000000000000000000000040'; + const String dynamicTypeLength = + '0000000000000000000000000000000000000000000000000000000000000041'; + final String nonPrefixedSignature = cacaoSignature.substring(2); + final String nonPrefixedHashedMessage = + hex.encode(hashMessage(reconstructedMessage)).substring(2); + + final String data = eip1271MagicValue + + nonPrefixedHashedMessage + + dynamicTypeOffset + + dynamicTypeLength + + nonPrefixedSignature; + + final Uri url = Uri.parse( + '${StringConstants.AUTH_DEFAULT_URL}/?chainId=$chainId&projectId=$projectId', + ); + final Map body = JsonRpcUtils.formatJsonRpcRequest( + 'eth_call', + { + 'to': address, + 'data': data, + }, + ); + + final http.Response response = await http.post( + url, + body: body, + ); + + // print(response.body); + // final jsonBody = jsonDecode(response.body); + final String recoveredValue = + response.body.substring(0, eip1271MagicValue.length); + return recoveredValue.toLowerCase() == eip1271MagicValue.toLowerCase(); + } catch (e) { + return false; + } + } + + // verifies CACAO signature + // Used by the wallet after formatting the message + static Future verifySignature( + String address, + String reconstructedMessage, + CacaoSignature cacaoSignature, + String chainId, + String projectId, + ) async { + if (cacaoSignature.t == 'eip191') { + return isValidEip191Signature( + address, + reconstructedMessage, + cacaoSignature.s, + ); + } else if (cacaoSignature.t == 'eip1271') { + return await isValidEip1271Signature( + address, + reconstructedMessage, + cacaoSignature.s, + chainId, + projectId, + ); + } else { + throw Exception( + 'verifySignature failed: Attempted to verify CacaoSignature with unknown type: ${cacaoSignature.t}', + ); + } + } + + static Cacao buildAuthObject({ + required CacaoRequestPayload requestPayload, + required CacaoSignature signature, + required String iss, + }) { + if (!iss.contains('did:pkh:')) { + iss = 'did:pkh:$iss'; + } + final address = AddressUtils.getDidAddress(iss); + iss = iss.replaceAll(address, address.toEIP55()); + return Cacao( + h: const CacaoHeader(t: CacaoHeader.CAIP122), + p: CacaoPayload.fromRequestPayload( + issuer: iss, + payload: requestPayload, + ), + s: signature, + ); + } + + static SessionAuthPayload populateAuthPayload({ + required SessionAuthPayload authPayload, + required List chains, + required List methods, + }) { + final statement = authPayload.statement ?? ''; + + if (chains.isEmpty) return authPayload; + + final requested = authPayload.chains; + final supported = chains; + + final approvedChains = + supported.where((value) => requested.contains(value)).toList(); + if (approvedChains.isEmpty) { + throw ReownSignError(code: -1, message: 'No supported chains'); + } + + final requestedRecaps = ReCapsUtils.getDecodedRecapFromResources( + resources: authPayload.resources, + ); + if (requestedRecaps == null) return authPayload; + + ReCapsUtils.isValidRecap(requestedRecaps); + + final resource = ReCapsUtils.getRecapResource( + recap: requestedRecaps, + resource: 'eip155', + ); + List updatedResources = authPayload.resources ?? []; + + if (resource.isNotEmpty) { + final actions = ReCapsUtils.getReCapActions(abilities: resource); + final approvedActions = + actions.where((value) => methods.contains(value)).toList(); + if (approvedActions.isEmpty) { + throw ReownSignError( + code: -1, + message: 'Supported methods don\'t satisfy the requested: $actions, ' + 'supported: $methods', + ); + } + final formattedActions = ReCapsUtils.assignAbilityToActions( + 'request', + approvedActions, + limits: {'chains': approvedChains}, + ); + final updatedRecap = ReCapsUtils.addResourceToRecap( + recap: requestedRecaps, + resource: 'eip155', + actions: formattedActions, + ); + // remove recap from resources as we will add the updated one + updatedResources = List.from((authPayload.resources ?? [])) + ..removeLast(); + updatedResources.add(ReCapsUtils.encodeRecap(updatedRecap)); + } + // + return SessionAuthPayload.fromJson(authPayload.toJson()).copyWith( + statement: ReCapsUtils.buildRecapStatement( + statement, + ReCapsUtils.getRecapFromResources(resources: updatedResources), + ), + chains: approvedChains, + resources: updatedResources.isNotEmpty ? updatedResources : null, + ); + } + + static String getAddressFromMessage(String message) { + try { + final regexp = RegExp('0x[a-fA-F0-9]{40}'); + final matches = regexp.allMatches(message); + for (final Match m in matches) { + return m[0]!; + } + return ''; + } catch (_) {} + return ''; + } + + static String getChainIdFromMessage(String message) { + try { + const pattern = 'Chain ID: '; + final regexp = RegExp('$pattern(?\\d+)'); + final matches = regexp.allMatches(message); + for (final Match m in matches) { + return m[0]!.toString().replaceAll(pattern, ''); + } + } catch (_) {} + return ''; + } +} diff --git a/packages/reown_sign/lib/utils/auth_utils.dart b/packages/reown_sign/lib/utils/auth_utils.dart new file mode 100644 index 0000000..7f4e801 --- /dev/null +++ b/packages/reown_sign/lib/utils/auth_utils.dart @@ -0,0 +1,5 @@ +class AuthUtils { + static String generateNonce() { + return DateTime.now().millisecondsSinceEpoch.toString(); + } +} diff --git a/packages/reown_sign/lib/utils/constants.dart b/packages/reown_sign/lib/utils/constants.dart new file mode 100644 index 0000000..e9a2f96 --- /dev/null +++ b/packages/reown_sign/lib/utils/constants.dart @@ -0,0 +1,19 @@ +import 'package:reown_core/reown_core.dart'; + +class StringConstants { + static const AUTH_REQUEST_EXPIRY_MIN = ReownConstants.FIVE_MINUTES; + static const AUTH_REQUEST_EXPIRY_MAX = ReownConstants.SEVEN_DAYS; + + static const AUTH_DEFAULT_URL = 'https://rpc.walletconnect.org/v1'; + + static const AUTH_PROTOCOL = 'wc'; + static const AUTH_VERSION = 1.5; + static const AUTH_CONTEXT = 'auth'; + static const AUTH_STORAGE_PREFIX = + '$AUTH_PROTOCOL@$AUTH_VERSION:$AUTH_CONTEXT:'; + + static const AUTH_CLIENT_PUBLIC_KEY_NAME = 'PUB_KEY'; + + static const OCAUTH_CLIENT_PUBLIC_KEY_NAME = + '$AUTH_STORAGE_PREFIX:$AUTH_CLIENT_PUBLIC_KEY_NAME'; +} diff --git a/packages/reown_sign/lib/utils/extensions.dart b/packages/reown_sign/lib/utils/extensions.dart new file mode 100644 index 0000000..f2e2db6 --- /dev/null +++ b/packages/reown_sign/lib/utils/extensions.dart @@ -0,0 +1,95 @@ +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:web3dart/crypto.dart' as crypto; +import 'package:web3dart/web3dart.dart'; + +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/models/basic_models.dart'; + +extension ReownSignErrorExtension on ReownCoreError { + ReownSignError toSignError() => ReownSignError( + code: code, + message: message, + data: data, + ); +} + +extension UriExtension on String { + Uri get toLinkMode => Uri.parse(Uri.decodeFull(this)); +} + +extension TransactionExtension on Transaction { + Map toJson() { + return { + if (from != null) 'from': from!.hexEip55, + if (to != null) 'to': to!.hexEip55, + if (maxGas != null) 'gas': '0x${maxGas!.toRadixString(16)}', + if (gasPrice != null) + 'gasPrice': '0x${gasPrice!.getInWei.toRadixString(16)}', + if (value != null) 'value': '0x${value!.getInWei.toRadixString(16)}', + if (data != null) 'data': crypto.bytesToHex(data!), + if (nonce != null) 'nonce': nonce, + if (maxFeePerGas != null) + 'maxFeePerGas': '0x${maxFeePerGas!.getInWei.toRadixString(16)}', + if (maxPriorityFeePerGas != null) + 'maxPriorityFeePerGas': + '0x${maxPriorityFeePerGas!.getInWei.toRadixString(16)}', + }; + } +} + +extension TransactionExtension2 on Map { + Transaction toTransaction() { + return Transaction( + from: EthereumAddress.fromHex(this['from']), + to: EthereumAddress.fromHex(this['to']), + value: (this['value'] as String?)?.toEthereAmount(), + gasPrice: (this['gasPrice'] as String?)?.toEthereAmount(), + maxFeePerGas: (this['maxFeePerGas'] as String?)?.toEthereAmount(), + maxPriorityFeePerGas: + (this['maxPriorityFeePerGas'] as String?)?.toEthereAmount(), + maxGas: (this['maxGas'] as String?)?.toIntFromHex(), + nonce: (this['nonce'] as String?)?.toIntFromHex(), + data: _parseTransactionData(this['data']), + ); + } +} + +Uint8List? _parseTransactionData(dynamic data) { + if (data != null && data != '0x') { + if (data.startsWith('0x')) { + return Uint8List.fromList(hex.decode(data.substring(2))); + } + return Uint8List.fromList(hex.decode(data)); + } + return null; +} + +extension EtheraAmountExtension on String? { + EtherAmount? toEthereAmount() { + if (this != null) { + final hexValue = this!.replaceFirst('0x', ''); + return EtherAmount.fromBigInt( + EtherUnit.wei, + BigInt.parse(hexValue, radix: 16), + ); + } + return null; + } + + int? toIntFromHex() { + if (this != null) { + final hexValue = this!.replaceFirst('0x', ''); + return int.parse(hexValue, radix: 16); + } + return null; + } + + int? toInt() { + if (this != null) { + return int.tryParse(this!); + } + return null; + } +} diff --git a/packages/reown_sign/lib/utils/namespace_utils.dart b/packages/reown_sign/lib/utils/namespace_utils.dart new file mode 100644 index 0000000..6b83832 --- /dev/null +++ b/packages/reown_sign/lib/utils/namespace_utils.dart @@ -0,0 +1,442 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +class NamespaceUtils { + /// Checks if the string is a chain + static bool isValidChainId(String value) { + if (value.contains(':')) { + List split = value.split(':'); + return split.length == 2; + } + return false; + } + + /// Checks if the string is an account + static bool isValidAccount(String value) { + if (value.contains(':')) { + List split = value.split(':'); + if (split.length == 3) { + String chainId = '${split[0]}:${split[1]}'; + return split.length >= 2 && isValidChainId(chainId); + } + } + return false; + } + + static bool isValidUrl(String value) { + try { + Uri.parse(value); + return true; + } catch (e) { + return false; + } + } + + static String getAccount(String namespaceAccount) { + if (isValidAccount(namespaceAccount)) { + return namespaceAccount.split(':')[2]; + } + return namespaceAccount; + } + + static String getChainFromAccount(String account) { + if (isValidAccount(account)) { + List parts = account.split(':'); + String namespace = parts[0]; + String reference = parts[1]; + return '$namespace:$reference'; + } + return account; + } + + /// Gets all unique chains from the provided list of accounts + /// This function assumes that all accounts are valid + static List getChainsFromAccounts(List accounts) { + Set chains = {}; + for (var account in accounts) { + chains.add( + getChainFromAccount( + account, + ), + ); + } + + return chains.toList(); + } + + /// Gets the namespace string id from the chainId + /// If the chain id is not valid, then it returns the chain id + static String getNamespaceFromChain(String chainId) { + if (isValidChainId(chainId)) { + return chainId.split(':')[0]; + } + return chainId; + } + + /// Gets all unique namespaces from the provided list of accounts + /// This function assumes that all accounts are valid + static Map getNamespacesFromAccounts( + List accounts, + ) { + Map namespaces = {}; + for (var account in accounts) { + final ns = account.split(':')[0]; + final cid = account.split(':')[1]; + if (namespaces[ns] == null) { + namespaces[ns] = const Namespace( + accounts: [], + methods: [], + events: [], + ); + } + namespaces[ns] = namespaces[ns]!.copyWith( + accounts: [ + ...namespaces[ns]!.accounts, + account, + ], + chains: [ + ...(namespaces[ns]?.chains ?? []), + '$ns:$cid', + ], + ); + } + + return namespaces; + } + + /// Gets the chains from the namespace. + /// If the namespace is a chain, then it returns the chain. + /// Otherwise it gets the chains from the accounts in the namespace. + static List getChainIdsFromNamespace({ + required String nsOrChainId, + required Namespace namespace, + }) { + if (isValidChainId(nsOrChainId)) { + return [nsOrChainId]; + } + + return getChainsFromAccounts(namespace.accounts); + } + + /// Gets the chainIds from the namespace. + static List getChainIdsFromNamespaces({ + required Map namespaces, + }) { + Set chainIds = {}; + + namespaces.forEach((String ns, Namespace namespace) { + chainIds.addAll( + getChainIdsFromNamespace( + nsOrChainId: ns, + namespace: namespace, + ), + ); + }); + + return chainIds.toList(); + } + + /// Gets the methods from a namespace map for the given chain + static List getNamespacesMethodsForChainId({ + required String chainId, + required Map namespaces, + }) { + List methods = []; + namespaces.forEach((String nsOrChain, Namespace namespace) { + if (nsOrChain == chainId) { + methods.addAll(namespace.methods); + } else { + List chains = getChainsFromAccounts(namespace.accounts); + if (chains.contains(chainId)) { + methods.addAll(namespace.methods); + } + } + }); + + return methods; + } + + /// Gets the optional methods from a namespace map for the given chain + static List getOptionalMethodsForChainId({ + required String chainId, + required Map optionalNamespaces, + }) { + List methods = []; + optionalNamespaces.forEach((String nsOrChain, RequiredNamespace rns) { + if (nsOrChain == chainId) { + methods.addAll(rns.methods); + } else { + if ((rns.chains ?? []).contains('$nsOrChain:$chainId')) { + methods.addAll(rns.methods); + } + } + }); + + return methods; + } + + /// Gets the methods from a namespace map for the given chain id + static List getNamespacesEventsForChain({ + required String chainId, + required Map namespaces, + }) { + List events = []; + namespaces.forEach((String nsOrChain, Namespace namespace) { + if (nsOrChain == chainId) { + events.addAll(namespace.events); + } else { + List chains = getChainsFromAccounts(namespace.accounts); + if (chains.contains(chainId)) { + events.addAll(namespace.events); + } + } + }); + + return events; + } + + /// If the namespace is a chain, add it to the list and return it + /// Otherwise, get the chains from the required namespaces + static List getChainsFromRequiredNamespace({ + required String nsOrChainId, + required RequiredNamespace requiredNamespace, + }) { + List chains = []; + if (isValidChainId(nsOrChainId)) { + chains.add(nsOrChainId); + } else if (requiredNamespace.chains != null) { + // We are assuming that the namespace is a chain + // Validate the requiredNamespace before it is sent here + chains.addAll(requiredNamespace.chains!); + } + + return chains; + } + + /// Gets the chains from the required namespaces + /// If keys value is provided, it will only get the chains for the provided keys + static List getChainIdsFromRequiredNamespaces({ + required Map requiredNamespaces, + }) { + Set chainIds = {}; + + // Loop through the required namespaces + requiredNamespaces.forEach((String ns, RequiredNamespace value) { + chainIds.addAll( + getChainsFromRequiredNamespace( + nsOrChainId: ns, + requiredNamespace: value, + ), + ); + }); + + return chainIds.toList(); + } + + /// Using the availabe accounts, methods, and events, construct the namespaces + /// If optional namespaces are provided, then they will be added to the namespaces as well + static Map constructNamespaces({ + required Set availableAccounts, + required Set availableMethods, + required Set availableEvents, + required Map requiredNamespaces, + Map? optionalNamespaces, + }) { + final Map namespaces = _constructNamespaces( + availableAccounts: availableAccounts, + availableMethods: availableMethods, + availableEvents: availableEvents, + namespacesMap: requiredNamespaces, + ); + final Map optionals = _constructNamespaces( + availableAccounts: availableAccounts, + availableMethods: availableMethods, + availableEvents: availableEvents, + namespacesMap: optionalNamespaces ?? {}, + ); + + // Loop through the optional keys and if they exist in the namespaces, then merge them and delete them from optional + List keys = optionals.keys.toList(); + for (int i = 0; i < keys.length; i++) { + String key = keys[i]; + if (namespaces.containsKey(key)) { + namespaces[key] = Namespace( + accounts: namespaces[key]! + .accounts + .toSet() + .union(optionals[key]!.accounts.toSet()) + .toList(), + methods: namespaces[key]! + .methods + .toSet() + .union(optionals[key]!.methods.toSet()) + .toList(), + events: namespaces[key]! + .events + .toSet() + .union(optionals[key]!.events.toSet()) + .toList(), + ); + optionals.remove(key); + } + } + + return { + ...namespaces, + ...optionals, + }; + } + + static Map buildNamespacesFromAuth({ + required Set methods, + required Set accounts, + }) { + final parsedAccounts = accounts.map( + (account) => account.replaceAll('did:pkh:', ''), + ); + + final namespaces = getNamespacesFromAccounts(parsedAccounts.toList()); + + final entries = namespaces.entries.map((e) { + return MapEntry( + e.key, + Namespace.fromJson(e.value.toJson()).copyWith( + methods: methods.toList(), + events: EventsConstants.allEvents, + ), + ); + }); + + return Map.fromEntries(entries); + } + + /// Gets the matching items from the available items using the chainId + /// This function assumes that each element in the available items is in the format of chainId:itemId + static Set _getMatching({ + required String namespaceOrChainId, + required Set available, + Set? requested, + bool takeLast = true, + }) { + Set matching = {}; + // Loop through the available items, and if it starts with the chainId, + // and is in the requested items, add it to the matching items + for (var item in available) { + if (item.startsWith('$namespaceOrChainId:')) { + matching.add(takeLast ? item.split(':').last : item); + } + } + + if (requested != null) { + matching = matching.intersection(requested); + } + + return matching; + } + + static Map _constructNamespaces({ + required Set availableAccounts, + required Set availableMethods, + required Set availableEvents, + required Map namespacesMap, + }) { + Map namespaces = {}; + + // Loop through the required namespaces + for (final String namespaceOrChainId in namespacesMap.keys) { + // If the key is a chain, add all of the chain specific events, keys, and methods first + final List accounts = []; + final List events = []; + final List methods = []; + final namespace = namespacesMap[namespaceOrChainId]!; + final chains = namespace.chains ?? []; + if (NamespaceUtils.isValidChainId(namespaceOrChainId) || chains.isEmpty) { + // Add the chain specific availableAccounts + accounts.addAll( + _getMatching( + namespaceOrChainId: namespaceOrChainId, + available: availableAccounts, + takeLast: false, + ), + ); + // Add the chain specific events + events.addAll( + _getMatching( + namespaceOrChainId: namespaceOrChainId, + available: availableEvents, + requested: namespace.events.toSet(), + ), + ); + // Add the chain specific methods + methods.addAll( + _getMatching( + namespaceOrChainId: namespaceOrChainId, + available: availableMethods, + requested: namespace.methods.toSet(), + ), + ); + } else { + // Loop through all of the chains + for (final String chainId in chains) { + // Add the chain specific availableAccounts + accounts.addAll( + _getMatching( + namespaceOrChainId: chainId, + available: availableAccounts, + ).map((e) => '$chainId:$e'), + ); + // Add the chain specific events + events.addAll( + _getMatching( + namespaceOrChainId: chainId, + available: availableEvents, + requested: namespace.events.toSet(), + ), + ); + // Add the chain specific methods + methods.addAll( + _getMatching( + namespaceOrChainId: chainId, + available: availableMethods, + requested: namespace.methods.toSet(), + ), + ); + } + } + + // Add the namespace to the list + namespaces[namespaceOrChainId] = Namespace( + accounts: accounts.toSet().toList(), + events: events.toSet().toList(), + methods: methods.toSet().toList(), + ); + } + + return namespaces; + } + + /// To be used by Wallet to regenerate already generatedNamespaces but adding `chains` parameter + /// + /// Example usage on onSessionProposal(SessionProposalEvent? event) + /// + /// await _web3Wallet!.approveSession( + /// id: event.id, + /// namespaces: NamespaceUtils.regenerateNamespacesWithChains( + /// event.params.generatedNamespaces!, + /// ), + /// sessionProperties: event.params.sessionProperties, + /// ); + + static Map regenerateNamespacesWithChains( + Map generatedNamespaces, + ) { + Map regeneratedNamespaces = {}; + for (var key in generatedNamespaces.keys) { + final namespace = generatedNamespaces[key]!.copyWith( + chains: getChainsFromAccounts(generatedNamespaces[key]!.accounts), + ); + regeneratedNamespaces[key] = namespace; + } + return regeneratedNamespaces; + } +} diff --git a/packages/reown_sign/lib/utils/recaps_utils.dart b/packages/reown_sign/lib/utils/recaps_utils.dart new file mode 100644 index 0000000..265b84f --- /dev/null +++ b/packages/reown_sign/lib/utils/recaps_utils.dart @@ -0,0 +1,349 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/utils/extensions.dart'; + +class ReCapsUtils { + // + static String? getRecapFromResources({List? resources}) { + final resourcesList = resources ?? []; + if (resourcesList.isEmpty) return null; + // per spec, recap is always the last resource + final recap = resourcesList.last; + return isRecap(recap) ? recap : null; + } + + static bool isRecap(String resource) { + return resource.contains('urn:recap:'); + } + + static List getMethodsFromRecap(String recap) { + final decodedRecap = decodeRecap(recap); + if (!isValidRecap(decodedRecap)) return []; + + try { + // methods are only available for eip155 as per the current implementation + final resource = decodedRecap['att']?['eip155'] as Map?; + if (resource == null) return []; + + return resource.keys.map((k) => k.split('/').last).toList(); + } catch (e) { + return []; + } + } + + static List getChainsFromRecap(String recap) { + final decodedRecap = decodeRecap(recap); + if (!isValidRecap(decodedRecap)) return []; + + final List recapChains = []; + try { + final att = + decodedRecap['att'] as Map? ?? {}; + + for (var resources in att.values) { + final resourcesMap = resources as Map; + final resourcesValues = resourcesMap.values.first as List; + for (var value in resourcesValues) { + final chainValues = value as Map; + final chains = chainValues['chains'] as List; + recapChains.addAll(chains); + } + } + return recapChains.map((e) => e.toString()).toSet().toList(); + } catch (e) { + return []; + } + } + + static Map decodeRecap(String recap) { + // Add the padding that was removed during encoding + String paddedRecap = recap.replaceAll('urn:recap:', ''); + final padding = paddedRecap.length % 4; + if (padding > 0) { + paddedRecap += '=' * (4 - padding); + } + + final decoded = utf8.decode(base64.decode(paddedRecap)); + final decodedRecap = jsonDecode(decoded) as Map; + isValidRecap(decodedRecap); + return decodedRecap; + } + + static bool isValidRecap(Map recap) { + final att = recap['att'] as Map?; + if (att == null) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'Invalid ReCap. No `att` property found', + ).toSignError(); + } + // + final resources = att.keys; + if (resources.isEmpty) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'Invalid ReCap. No resources found in `att` property', + ).toSignError(); + } + // + for (var resource in resources) { + final abilities = att[resource]; + if (abilities is! Map) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'Invalid ReCap. Resource must be an object: $resource', + ).toSignError(); + } + final resourceAbilities = (abilities as Map).keys; + if (resourceAbilities.isEmpty) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'Invalid ReCap. Resource object is empty: $resource', + ).toSignError(); + } + // + for (var ability in resourceAbilities) { + final limits = abilities[ability]; + if (limits is! List) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'Invalid ReCap. Ability limits $ability must be an array ' + 'of objects, found: $limits', + ).toSignError(); + } + if ((limits).isEmpty) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: 'Invalid ReCap. Value of $ability is empty array, must be ' + 'an array with objects', + ).toSignError(); + } + // + for (var limit in limits) { + if (limit is! Map) { + throw Errors.getInternalError( + Errors.MISSING_OR_INVALID, + context: + 'Invalid ReCap. Ability limits ($ability) must be an array ' + 'of objects, found: $limit', + ).toSignError(); + } + } + } + } + + return true; + } + + static String createEncodedRecap( + String namespace, + String ability, + List methods, + ) { + final recap = createRecap(namespace, ability, methods); + return encodeRecap(recap); + } + + static String encodeRecap(Map recap) { + isValidRecap(recap); + final jsonRecap = jsonEncode(recap); + final bytes = utf8.encode(jsonRecap).toList(); + // remove the padding from the base64 string as per recap spec + return 'urn:recap:${base64.encode(bytes).replaceAll('=', '')}'; + } + + static Map createRecap( + String namespace, + String ability, + List methods, { + Map limits = const {}, + }) { + try { + final sortedMethods = List.from(methods) + ..sort((a, b) => a.compareTo(b)); + + Map abilities = {}; + for (var method in sortedMethods) { + abilities['$ability/$method'] = [ + ...(abilities['$ability/$method'] ?? []), + limits, + ]; + } + + return { + 'att': { + namespace: Map.fromEntries(abilities.entries), + } + }; + } catch (e) { + rethrow; + } + } + + static String mergeEncodedRecaps(String recap1, String recap2) { + final decoded1 = decodeRecap(recap1); + final decoded2 = decodeRecap(recap2); + final merged = mergeRecaps(decoded1, decoded2); + return encodeRecap(merged); + } + + static Map mergeRecaps( + Map recap1, + Map recap2, + ) { + isValidRecap(recap1); + isValidRecap(recap2); + final att1 = recap1['att'] as Map; + final att2 = recap2['att'] as Map; + final keys = [...att1.keys, ...att2.keys]..sort( + (a, b) => a.compareTo(b), + ); + final mergedRecap = {'att': {}}; + + for (var key in keys) { + final actions1 = att1[key] as Map? ?? {}; + final actions1Keys = actions1.keys; + final actions2 = att2[key] as Map? ?? {}; + final actions2Keys = actions2.keys; + final actions = [...actions1Keys, ...actions2Keys]..sort( + (a, b) => a.compareTo(b), + ); + + for (var action in actions) { + mergedRecap['att']![key] = { + ...mergedRecap['att']?[key], + [action]: recap1['att'][key]?[action] || recap2['att'][key]?[action], + }; + } + } + + return mergedRecap; + } + + static Map? getDecodedRecapFromResources({ + List? resources, + }) { + final resource = getRecapFromResources(resources: resources); + if (resource == null) return null; + if (!isRecap(resource)) return null; + return decodeRecap(resource); + } + + static String formatStatementFromRecap({ + String statement = '', + Map recap = const {}, + }) { + isValidRecap(recap); + // + final baseStatement = + 'I further authorize the stated URI to perform the following actions on my behalf: '; + if (statement.contains(baseStatement)) return statement; + // + final List statementForRecap = []; + int currentCounter = 0; + final att = recap['att'] as Map; + final resources = att.keys; + for (var resource in resources) { + final abilities = att[resource]; + final resourceAbilities = (abilities as Map).keys; + final actions = resourceAbilities.map((ability) { + return { + 'ability': ability.split('/')[0], + 'action': ability.split('/')[1], + }; + }).toList(); + actions.sort((a, b) => a['action']!.compareTo(b['action']!)); + // + final uniqueAbilities = {}; + for (var actionMap in actions) { + final ability = actionMap['ability']!; + final action = actionMap['action']!; + if (uniqueAbilities[ability] == null) { + uniqueAbilities[ability] = []; + } + uniqueAbilities[ability].add(action); + } + // + final abilitiesStatements = uniqueAbilities.keys.map((ability) { + currentCounter++; + final abilities = (uniqueAbilities[ability] as List).join('\', \''); + return '($currentCounter) \'$ability\': \'$abilities\' for \'$resource\'.'; + }).toList(); + + statementForRecap.add( + abilitiesStatements.join(', ').replaceAll('.,', '.'), + ); + } + // + final recapStatemet = statementForRecap.join(' '); + final recapStatement = '$baseStatement$recapStatemet'; + // add a space if there is a statement + return '${statement.isNotEmpty ? "$statement " : ""}$recapStatement'; + } + + static List getRecapResource({ + required Map recap, + required String resource, + }) { + try { + final att = recap['att'] as Map?; + final abilities = att?[resource] as Map?; + if (abilities != null) { + return abilities.keys.toList(); + } + } catch (e) { + debugPrint(e.toString()); + } + return []; + } + + static List getReCapActions({required List abilities}) { + try { + return abilities.map((ability) => ability.split('/')[1]).toList(); + } catch (e) { + debugPrint(e.toString()); + } + return []; + } + + static Map assignAbilityToActions( + String ability, + List actions, { + Map limits = const {}, + }) { + final sortedActions = List.from(actions) + ..sort((a, b) => a.compareTo(b)); + + Map abilities = {}; + for (var method in sortedActions) { + abilities['$ability/$method'] = [ + ...(abilities['$ability/$method'] ?? []), + limits, + ]; + } + + return Map.fromEntries(abilities.entries); + } + + static Map addResourceToRecap({ + required Map recap, + required String resource, + required Map actions, + }) { + // + final sortedRecap = Map.from(recap); + sortedRecap['att']![resource] = actions; + sortedRecap.keys.toList().sort((a, b) => a.compareTo(b)); + isValidRecap(sortedRecap); + return sortedRecap; + } + + static String buildRecapStatement(String statement, String? recap) { + if ((recap ?? '').isEmpty) return statement; + final decoded = decodeRecap(recap!); + isValidRecap(decoded); + return formatStatementFromRecap(statement: statement, recap: decoded); + } +} diff --git a/packages/reown_sign/lib/utils/secp256k1/LICENSE b/packages/reown_sign/lib/utils/secp256k1/LICENSE new file mode 100644 index 0000000..5c8f2ec --- /dev/null +++ b/packages/reown_sign/lib/utils/secp256k1/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2021 Wakumo Vietnam + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/reown_sign/lib/utils/secp256k1/auth_secp256k1.dart b/packages/reown_sign/lib/utils/secp256k1/auth_secp256k1.dart new file mode 100644 index 0000000..2047524 --- /dev/null +++ b/packages/reown_sign/lib/utils/secp256k1/auth_secp256k1.dart @@ -0,0 +1,115 @@ +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:pointycastle/ecc/api.dart'; +import 'package:pointycastle/ecc/curves/secp256k1.dart'; + +enum Endian { + be, +} + +class AuthSecp256k1 { + static final ECDomainParameters _params = ECCurve_secp256k1(); + static final BigInt _byteMask = BigInt.from(0xff); + + static BigInt decodeBigInt(List bytes) { + BigInt result = BigInt.from(0); + for (int i = 0; i < bytes.length; i++) { + result += BigInt.from(bytes[bytes.length - i - 1]) << (8 * i); + } + return result; + } + + static Uint8List encodeBigInt( + BigInt input, { + Endian endian = Endian.be, + int length = 0, + }) { + int byteLength = (input.bitLength + 7) >> 3; + int reqLength = length > 0 ? length : max(1, byteLength); + assert(byteLength <= reqLength, 'byte array longer than desired length'); + assert(reqLength > 0, 'Requested array length <= 0'); + + var res = Uint8List(reqLength); + res.fillRange(0, reqLength - byteLength, 0); + + var q = input; + if (endian == Endian.be) { + for (int i = 0; i < byteLength; i++) { + res[reqLength - i - 1] = (q & _byteMask).toInt(); + q = q >> 8; + } + return res; + } + + return Uint8List(0); + } + + static ECPoint _decompressKey(BigInt xBN, bool yBit, ECCurve c) { + List x9IntegerToBytes(BigInt s, int qLength) { + //https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/asn1/x9/X9IntegerConverter.java#L45 + String hexString = s.toRadixString(16); + if (hexString.length % 2 == 1) { + hexString = '0$hexString'; + } + final bytes = hex.decode(hexString); + + if (qLength < bytes.length) { + return bytes.sublist(0, bytes.length - qLength); + } else if (qLength > bytes.length) { + final tmp = List.filled(qLength, 0); + + final offset = qLength - bytes.length; + for (var i = 0; i < bytes.length; i++) { + tmp[i + offset] = bytes[i]; + } + + return tmp; + } + + return bytes; + } + + final compEnc = x9IntegerToBytes(xBN, 1 + ((c.fieldSize + 7) ~/ 8)); + compEnc[0] = yBit ? 0x03 : 0x02; + return c.decodePoint(compEnc)!; + } + + static Uint8List? recoverPublicKeyFromSignature( + int recId, + BigInt r, + BigInt s, + Uint8List message, + ) { + final n = _params.n; + final i = BigInt.from(recId ~/ 2); + final x = r + (i * n); + + //Parameter q of curve + final prime = BigInt.parse( + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', + radix: 16); + if (x.compareTo(prime) >= 0) return null; + + final R = _decompressKey(x, (recId & 1) == 1, _params.curve); + final ECPoint? ecPoint = R * n; + if (ecPoint == null || !ecPoint.isInfinity) return null; + + // print(bytesToHex(message)); + // final e = BigInt.parse(bytesToHex(message).substring(1)); + final e = decodeBigInt(message.toList()); + + final eInv = (BigInt.zero - e) % n; + final rInv = r.modInverse(n); + final srInv = (rInv * s) % n; + final eInvrInv = (rInv * eInv) % n; + + final preQ = (_params.G * eInvrInv); + if (preQ == null) return null; + final q = preQ + (R * srInv); + + final bytes = q?.getEncoded(false); + return bytes?.sublist(1); + } +} diff --git a/packages/reown_sign/lib/utils/sign_api_validator_utils.dart b/packages/reown_sign/lib/utils/sign_api_validator_utils.dart new file mode 100644 index 0000000..5211e93 --- /dev/null +++ b/packages/reown_sign/lib/utils/sign_api_validator_utils.dart @@ -0,0 +1,325 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/models/proposal_models.dart'; +import 'package:reown_sign/models/session_models.dart'; +import 'package:reown_sign/utils/extensions.dart'; +import 'package:reown_sign/utils/namespace_utils.dart'; + +class SignApiValidatorUtils { + static bool isContainedIn({ + required List container, + required List contained, + }) { + List matches = contained + .where( + (x) => container.contains(x), + ) + .toList(); + return matches.length == contained.length; + } + + /// This function will throw an error if + /// 1. the nsOrChainId parameter is a chainId, and the chains param exists + /// 2. The list of chains contains an invalid chainId + static bool isValidChains({ + required String nsOrChainId, + List? chains, + required String context, + }) { + // If the key is a valid chain Id, then the chains should be empty + final bool isChainId = NamespaceUtils.isValidChainId(nsOrChainId); + if (isChainId) { + if (chains != null && chains.isNotEmpty) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: '$context, namespace is a chainId, but chains is not empty', + ).toSignError(); + } + } else { + for (String c in chains!) { + if (!NamespaceUtils.isValidChainId(c)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: + '$context, chain $c should conform to "namespace:chainId" format', + ).toSignError(); + } + } + } + + return true; + } + + /// Validates the data of the Required Namespaces, ensuring it conforms to CAIP-25 + static bool isValidRequiredNamespaces({ + required Map requiredNamespaces, + required String context, + }) { + requiredNamespaces.forEach((key, namespace) { + isValidChains( + nsOrChainId: key, + chains: namespace.chains, + context: '$context requiredNamespace', + ); + }); + + return true; + } + + /// Loops through each account, and validates it + /// Context is used to provide more information in the error message + static bool isValidAccounts({ + required List accounts, + required String context, + }) { + for (String account in accounts) { + if (!NamespaceUtils.isValidAccount(account)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_ACCOUNTS, + context: + '$context, account $account should conform to "namespace:chainId:address" format', + ).toSignError(); + } + } + + return true; + } + + /// Validates the data of the Namespaces, ensuring it conforms to CAIP-25 + static bool isValidNamespaces({ + required Map namespaces, + required String context, + }) { + namespaces.forEach((key, namespace) { + isValidAccounts( + accounts: namespace.accounts, + context: '$context namespace', + ); + }); + + return true; + } + + /// Validates the provided chainId, then ensures that the chainId is contained + /// in the namespaces + static bool isValidNamespacesChainId({ + required Map namespaces, + required String chainId, + }) { + // Validate the chainId + if (!NamespaceUtils.isValidChainId(chainId)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: 'chain $chainId should conform to "CAIP-2" format', + ).toSignError(); + } + + // Validate the namespaces + isValidNamespaces( + namespaces: namespaces, + context: 'isValidNamespacesChainId', + ); + + // Get the chains from the namespaces and + List chains = NamespaceUtils.getChainIdsFromNamespaces( + namespaces: namespaces, + ); + + if (!chains.contains(chainId)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: 'The chain $chainId is not supported', + ).toSignError(); + } + + return true; + } + + /// Validates the provided chainId, then gets the methods for that chainId + /// and ensures that the method request is contained in the methods + static bool isValidNamespacesRequest({ + required Map namespaces, + required String chainId, + required String method, + }) { + // Validate the chainId + if (!NamespaceUtils.isValidChainId(chainId)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: 'chain $chainId should conform to "CAIP-2" format', + ).toSignError(); + } + + // Validate the namespaces + isValidNamespaces( + namespaces: namespaces, + context: 'isValidNamespacesRequest', + ); + + List methods = NamespaceUtils.getNamespacesMethodsForChainId( + namespaces: namespaces, + chainId: chainId, + ); + + if (!methods.contains(method)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_METHODS, + context: 'The method $method is not supported', + ).toSignError(); + } + + return true; + } + + /// Validates the provided chainId, then gets the events for that chainId + /// and ensures that the event request is contained in the events + static bool isValidNamespacesEvent({ + required Map namespaces, + required String chainId, + required String eventName, + }) { + // Validate the chainId + if (!NamespaceUtils.isValidChainId(chainId)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: 'chain $chainId should conform to "CAIP-2" format', + ).toSignError(); + } + + // Validate the namespaces + isValidNamespaces( + namespaces: namespaces, + context: 'isValidNamespacesEvent', + ); + + List events = NamespaceUtils.getNamespacesEventsForChain( + namespaces: namespaces, + chainId: chainId, + ); + + if (!events.contains(eventName)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_EVENTS, + context: 'The event $eventName is not supported', + ).toSignError(); + } + + return true; + } + + /// Makes sure that the chains, methods and events of the required namespaces + /// are contained in the namespaces + static bool isConformingNamespaces({ + required Map requiredNamespaces, + required Map namespaces, + required String context, + }) { + List requiredNamespaceKeys = requiredNamespaces.keys.toList(); + List namespaceKeys = namespaces.keys.toList(); + + // If the namespaces doesn't have the correct keys, we can fail automatically + if (!isContainedIn( + container: namespaceKeys, contained: requiredNamespaceKeys)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_NAMESPACE_KEY, + context: "$context namespaces keys don't satisfy requiredNamespaces", + ).toSignError(); + } else { + for (var key in requiredNamespaceKeys) { + List requiredNamespaceChains = + NamespaceUtils.getChainsFromRequiredNamespace( + nsOrChainId: key, + requiredNamespace: requiredNamespaces[key]!, + ); + List namespaceChains = NamespaceUtils.getChainIdsFromNamespace( + nsOrChainId: key, + namespace: namespaces[key]!, + ); + + // Check the chains, methods and events for overlaps. + // If any of them don't have it, we fail. + final bool chainsOverlap = isContainedIn( + container: namespaceChains, + contained: requiredNamespaceChains, + ); + final bool methodsOverlap = isContainedIn( + container: namespaces[key]!.methods, + contained: requiredNamespaces[key]!.methods, + ); + final bool eventsOverlap = isContainedIn( + container: namespaces[key]!.events, + contained: requiredNamespaces[key]!.events, + ); + + if (!chainsOverlap) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: + "$context namespaces chains don't satisfy requiredNamespaces chains for $key. Requested: $requiredNamespaceChains, Supported: $namespaceChains", + ).toSignError(); + } else if (!methodsOverlap) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_METHODS, + context: + "$context namespaces methods don't satisfy requiredNamespaces methods for $key. Requested: ${requiredNamespaces[key]!.methods}, Supported: ${namespaces[key]!.methods}", + ).toSignError(); + } else if (!eventsOverlap) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_EVENTS, + context: + "$context namespaces events don't satisfy requiredNamespaces events for $key. Requested: ${requiredNamespaces[key]!.events}, Supported: ${namespaces[key]!.events}", + ).toSignError(); + } + } + } + + return true; + } + + static bool isSessionCompatible({ + required SessionData session, + required Map requiredNamespaces, + }) { + List sessionKeys = session.namespaces.keys.toList(); + List paramsKeys = requiredNamespaces.keys.toList(); + bool compatible = true; + + if (!isContainedIn(container: sessionKeys, contained: paramsKeys)) { + return false; + } + + for (var key in sessionKeys) { + Namespace namespace = session.namespaces[key]!; + RequiredNamespace requiredNamespace = requiredNamespaces[key]!; + List requiredNamespaceChains = + NamespaceUtils.getChainsFromRequiredNamespace( + nsOrChainId: key, + requiredNamespace: requiredNamespace, + ); + List namespaceChains = NamespaceUtils.getChainIdsFromNamespace( + nsOrChainId: key, + namespace: namespace, + ); + + // Check the chains, methods and events for overlaps. + // If any of them don't have it, we fail. + final bool chainsOverlap = isContainedIn( + container: namespaceChains, + contained: requiredNamespaceChains, + ); + final bool methodsOverlap = isContainedIn( + container: namespace.methods, + contained: requiredNamespaces[key]!.methods, + ); + final bool eventsOverlap = isContainedIn( + container: namespace.events, + contained: requiredNamespaces[key]!.events, + ); + + if (!chainsOverlap || !methodsOverlap || !eventsOverlap) { + compatible = false; + } + } + + return compatible; + } +} diff --git a/packages/reown_sign/lib/version.dart b/packages/reown_sign/lib/version.dart new file mode 100644 index 0000000..526cd53 --- /dev/null +++ b/packages/reown_sign/lib/version.dart @@ -0,0 +1,2 @@ +// Generated code. Do not modify. +const packageVersion = '1.0.0'; diff --git a/packages/reown_sign/pubspec.yaml b/packages/reown_sign/pubspec.yaml new file mode 100644 index 0000000..62045f2 --- /dev/null +++ b/packages/reown_sign/pubspec.yaml @@ -0,0 +1,41 @@ +name: reown_sign +description: "The communications protocol for web3" +version: 1.0.0 +homepage: https://github.com/reown-com/reown_flutter +repository: https://github.com/reown-com/reown_flutter + +environment: + sdk: ">=2.19.0 <4.0.0" + +dependencies: + convert: ^3.0.1 + event: ^2.1.2 + flutter: + sdk: flutter + freezed_annotation: ^2.2.0 + http: ^1.2.0 + logger: ^2.2.0 + pointycastle: ^3.9.1 + reown_core: + path: ../reown_core + web3dart: ^2.7.3 + +dev_dependencies: + build_runner: ^2.4.7 + build_version: ^2.1.1 + dependency_validator: ^3.2.2 + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + freezed: ^2.4.5 + json_serializable: ^6.7.0 + mockito: ^5.4.3 + package_info_plus: ^7.0.0 + +platforms: + android: + ios: + web: + macos: + linux: + windows: diff --git a/packages/reown_sign/test/auth/signature_test.dart b/packages/reown_sign/test/auth/signature_test.dart new file mode 100644 index 0000000..08f358c --- /dev/null +++ b/packages/reown_sign/test/auth/signature_test.dart @@ -0,0 +1,144 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import '../shared/engine_constants.dart'; +import '../shared/signature_constants.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('AuthSignature', () { + test('Test EIP-191 Personal Message Hash', () { + final messages = [ + 'Hello World', + utf8.decode([0x42, 0x43]), + '0x4243', + ]; + final hashes = [ + '0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2', + '0x0d3abc18ec299cf9b42ba439ac6f7e3e6ec9f5c048943704e30fc2d9c7981438', + '0x6d91b221f765224b256762dcba32d62209cf78e9bebb0a1b758ca26c76db3af4', + ]; + + for (int i = 0; i < messages.length; i++) { + final hash = AuthSignature.hashMessage(messages[i]); + expect( + '0x${hex.encode(hash)}', + hashes[i], + ); + } + }); + + test('isValidEip191Signature', () async { + // print('Actual Sig: ${EthSigUtil.signPersonalMessage( + // message: Uint8List.fromList(TEST_MESSAGE_EIP191.codeUnits), + // privateKey: TEST_PRIVATE_KEY_EIP191, + // )}'); + + const cacaoSig = CacaoSignature( + t: CacaoSignature.EIP191, + s: TEST_SIG_EIP191, + ); + + final bool = await AuthSignature.verifySignature( + TEST_ADDRESS_EIP191, + TEST_MESSAGE_EIP191, + cacaoSig, + TEST_ETHEREUM_CHAIN, + TEST_PROJECT_ID, + ); + + // print(bool); + expect(bool, true); + + const cacaoSig2 = CacaoSignature( + t: CacaoSignature.EIP191, + s: TEST_SIGNATURE_FAIL, + ); + + final bool2 = await AuthSignature.verifySignature( + TEST_ADDRESS_EIP191, + TEST_MESSAGE_EIP191, + cacaoSig2, + TEST_ETHEREUM_CHAIN, + TEST_PROJECT_ID, + ); + + // print(bool); + expect(bool2, false); + }); + + test('getAddressFromMessage', () { + final address = AuthSignature.getAddressFromMessage( + TEST_MESSAGE_EIP1271, + ); + expect(address, TEST_ADDRESS_EIP1271); + + final address2 = AuthSignature.getAddressFromMessage( + TEST_FORMATTED_MESSAGE, + ); + expect(address2, '0x06C6A22feB5f8CcEDA0db0D593e6F26A3611d5fa'); + + final address3 = AuthSignature.getAddressFromMessage( + TEST_MESSAGE_EIP1271_2, + ); + expect(address3, '0x59e2f66C0E96803206B6486cDb39029abAE834c0'); + }); + + test('getChainIdFromMessage', () { + final chainId = AuthSignature.getChainIdFromMessage( + TEST_MESSAGE_EIP1271, + ); + expect(chainId, '1'); + + final chainId2 = AuthSignature.getChainIdFromMessage( + TEST_FORMATTED_MESSAGE, + ); + expect(chainId2, '1'); + + final chainId3 = AuthSignature.getChainIdFromMessage( + TEST_MESSAGE_EIP1271_2, + ); + expect(chainId3, '465321'); + }); + + // TODO: Fix this test, can't call http requests from within the test + // test('isValidEip1271Signature', () async { + // final cacaoSig = CacaoSignature( + // t: CacaoSignature.EIP1271, + // s: TEST_SIG_EIP1271, + // ); + + // final bool = await AuthSignature.verifySignature( + // TEST_ADDRESS_EIP1271, + // TEST_MESSAGE_EIP1271, + // cacaoSig, + // TEST_ETHEREUM_CHAIN, + // TEST_PROJECT_ID, + // ); + + // // print(bool); + // expect(bool, true); + + // final cacaoSig2 = CacaoSignature( + // t: CacaoSignature.EIP1271, + // s: TEST_SIGNATURE_FAIL, + // ); + + // final bool2 = await AuthSignature.verifySignature( + // TEST_ADDRESS_EIP1271, + // TEST_MESSAGE_EIP1271, + // cacaoSig2, + // TEST_ETHEREUM_CHAIN, + // TEST_PROJECT_ID, + // ); + + // // print(bool); + // expect(bool2, false); + // }); + }); +} diff --git a/packages/reown_sign/test/auth/validation_test.dart b/packages/reown_sign/test/auth/validation_test.dart new file mode 100644 index 0000000..9be1fc6 --- /dev/null +++ b/packages/reown_sign/test/auth/validation_test.dart @@ -0,0 +1,124 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_sign/reown_sign.dart'; +import 'package:reown_sign/utils/constants.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('AuthValidators', () { + test('isValidRequestExpiry', () { + final List expiries = [ + StringConstants.AUTH_REQUEST_EXPIRY_MIN - 1, + StringConstants.AUTH_REQUEST_EXPIRY_MIN, + StringConstants.AUTH_REQUEST_EXPIRY_MIN + 1, + StringConstants.AUTH_REQUEST_EXPIRY_MAX - 1, + StringConstants.AUTH_REQUEST_EXPIRY_MAX, + StringConstants.AUTH_REQUEST_EXPIRY_MAX + 1, + ]; + final List expiryResults = [ + false, + true, + true, + true, + true, + false, + ]; + + // Loop through the expiries and expect the results + for (var i = 0; i < expiries.length; i++) { + final expiry = expiries[i]; + final result = expiryResults[i]; + + expect(AuthApiValidators.isValidRequestExpiry(expiry), result); + } + }); + + // test('isValidRequest', () { + // expect( + // AuthApiValidators.isValidRequest(testAuthRequestParamsValid), + // true, + // ); + // expect( + // () => AuthApiValidators.isValidRequest(testAuthRequestParamsInvalidAud), + // throwsA( + // isA().having( + // (e) => e.message, + // 'message', + // 'Missing or invalid. requestAuth() invalid aud: ${testAuthRequestParamsInvalidAud.aud}. Must be a valid url.', + // ), + // ), + // ); + // expect( + // () => AuthApiValidators.isValidRequest( + // testAuthRequestParamsInvalidNonce, + // ), + // throwsA( + // isA().having( + // (e) => e.message, + // 'message', + // 'Missing or invalid. requestAuth() nonce must be nonempty.', + // ), + // ), + // ); + // expect( + // () => + // AuthApiValidators.isValidRequest(testAuthRequestParamsInvalidType), + // throwsA( + // isA().having( + // (e) => e.message, + // 'message', + // 'Missing or invalid. requestAuth() type must null or ${CacaoHeader.EIP4361}.', + // ), + // ), + // ); + // expect( + // () => AuthApiValidators.isValidRequest( + // testAuthRequestParamsInvalidExpiry), + // throwsA( + // isA().having( + // (e) => e.message, + // 'message', + // 'Missing or invalid. requestAuth() expiry: ${testAuthRequestParamsInvalidExpiry.expiry}. Expiry must be a number (in seconds) between ${StringConstants.AUTH_REQUEST_EXPIRY_MIN} and ${StringConstants.AUTH_REQUEST_EXPIRY_MAX}', + // ), + // ), + // ); + // }); + + // test('isValidRespond', () { + // expect( + // AuthApiValidators.isValidRespond( + // id: TEST_PENDING_REQUEST_ID, + // pendingRequests: testPendingRequests, + // signature: const CacaoSignature(t: '', s: ''), + // ), + // true, + // ); + // expect( + // () => AuthApiValidators.isValidRespond( + // id: TEST_PENDING_REQUEST_ID_INVALID, + // pendingRequests: testPendingRequests, + // ), + // throwsA( + // isA().having( + // (e) => e.message, + // 'message', + // 'Missing or invalid. respondAuth() invalid id: $TEST_PENDING_REQUEST_ID_INVALID. No pending request found.', + // ), + // ), + // ); + // expect( + // () => AuthApiValidators.isValidRespond( + // id: TEST_PENDING_REQUEST_ID, + // pendingRequests: testPendingRequests, + // ), + // throwsA( + // isA().having( + // (e) => e.message, + // 'message', + // 'Missing or invalid. respondAuth() invalid response. Must contain either signature or error.', + // ), + // ), + // ); + // }); + }); +} diff --git a/packages/reown_sign/test/reown_sign_test.dart b/packages/reown_sign/test/reown_sign_test.dart new file mode 100644 index 0000000..5ae463f --- /dev/null +++ b/packages/reown_sign/test/reown_sign_test.dart @@ -0,0 +1,148 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_values.dart'; +import 'tests/sign_common.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(); + + signEngineTests( + context: 'SignEngine', + clientACreator: (PairingMetadata metadata) async { + final core = ReownCore( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + memoryStore: true, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ); + IReownSign engineA = ReownSign( + core: core, + metadata: metadata, + proposals: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PROPOSALS, + version: StoreVersions.VERSION_PROPOSALS, + fromJson: (dynamic value) { + return ProposalData.fromJson(value); + }, + ), + sessions: Sessions( + storage: core.storage, + context: StoreVersions.CONTEXT_SESSIONS, + version: StoreVersions.VERSION_SESSIONS, + fromJson: (dynamic value) { + return SessionData.fromJson(value); + }, + ), + pendingRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PENDING_REQUESTS, + version: StoreVersions.VERSION_PENDING_REQUESTS, + fromJson: (dynamic value) { + return SessionRequest.fromJson(value); + }, + ), + authKeys: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_KEYS, + version: StoreVersions.VERSION_AUTH_KEYS, + fromJson: (dynamic value) { + return AuthPublicKey.fromJson(value); + }, + ), + pairingTopics: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PAIRING_TOPICS, + version: StoreVersions.VERSION_PAIRING_TOPICS, + fromJson: (dynamic value) { + return value; + }, + ), + sessionAuthRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_REQUESTS, + version: StoreVersions.VERSION_AUTH_REQUESTS, + fromJson: (dynamic value) { + return PendingSessionAuthRequest.fromJson(value); + }, + ), + ); + await core.start(); + await engineA.init(); + + return engineA; + }, + clientBCreator: (PairingMetadata metadata) async { + final core = ReownCore( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + memoryStore: true, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ); + IReownSign engineB = ReownSign( + core: core, + metadata: metadata, + proposals: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PROPOSALS, + version: StoreVersions.VERSION_PROPOSALS, + fromJson: (dynamic value) { + return ProposalData.fromJson(value); + }, + ), + sessions: Sessions( + storage: core.storage, + context: StoreVersions.CONTEXT_SESSIONS, + version: StoreVersions.VERSION_SESSIONS, + fromJson: (dynamic value) { + return SessionData.fromJson(value); + }, + ), + pendingRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PENDING_REQUESTS, + version: StoreVersions.VERSION_PENDING_REQUESTS, + fromJson: (dynamic value) { + return SessionRequest.fromJson(value); + }, + ), + authKeys: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_KEYS, + version: StoreVersions.VERSION_AUTH_KEYS, + fromJson: (dynamic value) { + return AuthPublicKey.fromJson(value); + }, + ), + pairingTopics: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PAIRING_TOPICS, + version: StoreVersions.VERSION_PAIRING_TOPICS, + fromJson: (dynamic value) { + return value; + }, + ), + sessionAuthRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_REQUESTS, + version: StoreVersions.VERSION_AUTH_REQUESTS, + fromJson: (dynamic value) { + return PendingSessionAuthRequest.fromJson(value); + }, + ), + ); + await core.start(); + await engineB.init(); + + return engineB; + }, + ); +} diff --git a/packages/reown_sign/test/shared/engine_constants.dart b/packages/reown_sign/test/shared/engine_constants.dart new file mode 100644 index 0000000..eb4398e --- /dev/null +++ b/packages/reown_sign/test/shared/engine_constants.dart @@ -0,0 +1,97 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import 'signature_constants.dart'; + +const TEST_AUD = 'http://localhost:3000/login'; +const TEST_AUD_INVALID = '<>:http://localhost:3000/login'; +const TEST_DOMAIN = 'localhost:3000'; + +const TEST_PUBLIC_KEY_A = '0x123'; +const TEST_PUBLIC_KEY_B = '0xxyz'; + +const TEST_METADATA_REQUESTER = PairingMetadata( + name: 'client (requester)', + description: 'Test Client as Requester', + url: 'www.reown.com', + icons: [], +); +const TEST_METADATA_RESPONDER = PairingMetadata( + name: 'peer (responder)', + description: 'Test Client as Peer/Responder', + url: 'www.reown.com', + icons: [], +); +const TEST_CONNECTION_METADATA_REQUESTER = ConnectionMetadata( + publicKey: TEST_PUBLIC_KEY_A, + metadata: TEST_METADATA_REQUESTER, +); + +// final defaultRequestParams = AuthRequestParams( +// chainId: TEST_ETHEREUM_CHAIN, +// domain: 'localhost:3000', +// aud: TEST_AUD, +// resources: ['test'], +// ); + +// final testAuthRequestParamsValid = AuthRequestParams( +// chainId: TEST_ETHEREUM_CHAIN, +// domain: TEST_DOMAIN, +// aud: TEST_AUD, +// ); +// final testAuthRequestParamsInvalidAud = AuthRequestParams( +// chainId: TEST_ETHEREUM_CHAIN, +// domain: TEST_DOMAIN, +// aud: TEST_AUD_INVALID, +// ); +// final testAuthRequestParamsInvalidNonce = AuthRequestParams( +// chainId: TEST_ETHEREUM_CHAIN, +// domain: TEST_DOMAIN, +// nonce: '', +// aud: TEST_AUD, +// ); +// final testAuthRequestParamsInvalidType = AuthRequestParams( +// chainId: TEST_ETHEREUM_CHAIN, +// domain: TEST_DOMAIN, +// aud: TEST_AUD, +// type: 'abc', +// ); +// final testAuthRequestParamsInvalidExpiry = AuthRequestParams( +// chainId: TEST_ETHEREUM_CHAIN, +// domain: TEST_DOMAIN, +// aud: TEST_AUD, +// expiry: 0, +// ); + +const testCacaoRequestPayload = CacaoRequestPayload( + domain: TEST_DOMAIN, + aud: TEST_AUD, + version: '1', + nonce: '100', + iat: '2022-10-10T23:03:35.700Z', +); +final CacaoPayload testCacaoPayload = CacaoPayload.fromRequestPayload( + issuer: TEST_ISSUER_EIP191, + payload: testCacaoRequestPayload, +); +const TEST_FORMATTED_MESSAGE = + '''localhost:3000 wants you to sign in with your Ethereum account: +0x06C6A22feB5f8CcEDA0db0D593e6F26A3611d5fa + + +URI: http://localhost:3000/login +Version: 1 +Chain ID: 1 +Nonce: 100 +Issued At: 2022-10-10T23:03:35.700Z'''; + +const TEST_PENDING_REQUEST_ID = 1; +const TEST_PENDING_REQUEST_ID_INVALID = -1; +// final testPendingRequests = { +// TEST_PENDING_REQUEST_ID: const PendingAuthRequest( +// id: TEST_PENDING_REQUEST_ID, +// pairingTopic: TEST_PAIRING_TOPIC, +// metadata: TEST_CONNECTION_METADATA_REQUESTER, +// cacaoPayload: testCacaoRequestPayload, +// ) +// }; diff --git a/packages/reown_sign/test/shared/shared_test_utils.dart b/packages/reown_sign/test/shared/shared_test_utils.dart new file mode 100644 index 0000000..829f83a --- /dev/null +++ b/packages/reown_sign/test/shared/shared_test_utils.dart @@ -0,0 +1,120 @@ +// ignore_for_file: no_leading_underscores_for_local_identifiers + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:reown_core/crypto/crypto.dart'; +import 'package:reown_core/crypto/crypto_utils.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/relay_client/i_message_tracker.dart'; +import 'package:reown_core/relay_client/message_tracker.dart'; +import 'package:reown_core/relay_client/websocket/http_client.dart'; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +import 'shared_test_utils.mocks.dart'; + +@GenerateMocks([ + CryptoUtils, + Crypto, + MessageTracker, + HttpWrapper, + ReownCore, + WebSocketHandler, +]) +class SharedTestUtils {} + +ICrypto getCrypto({ + IReownCore? core, + MockCryptoUtils? utils, +}) { + final IReownCore _core = core ?? ReownCore(projectId: '', memoryStore: true); + final ICrypto crypto = Crypto( + core: _core, + keyChain: GenericStore( + storage: _core.storage, + context: StoreVersions.CONTEXT_KEYCHAIN, + version: StoreVersions.VERSION_KEYCHAIN, + fromJson: (dynamic value) => value as String, + ), + utils: utils, + ); + _core.crypto = crypto; + return crypto; +} + +IMessageTracker getMessageTracker({ + IReownCore? core, +}) { + final IReownCore _core = core ?? ReownCore(projectId: '', memoryStore: true); + return MessageTracker( + storage: _core.storage, + context: StoreVersions.CONTEXT_MESSAGE_TRACKER, + version: StoreVersions.VERSION_MESSAGE_TRACKER, + fromJson: (dynamic value) => ReownCoreUtils.convertMapTo(value), + ); +} + +IGenericStore getTopicMap({ + IReownCore? core, +}) { + final IReownCore _core = core ?? ReownCore(projectId: '', memoryStore: true); + return GenericStore( + storage: _core.storage, + context: StoreVersions.CONTEXT_TOPIC_MAP, + version: StoreVersions.VERSION_TOPIC_MAP, + fromJson: (dynamic value) => value as String, + ); +} + +MockHttpWrapper getHttpWrapper() { + final MockHttpWrapper httpWrapper = MockHttpWrapper(); + when(httpWrapper.get(any)).thenAnswer((_) async => Response('', 200)); + // when(httpWrapper.post( + // url: anyNamed('url'), + // body: anyNamed('body'), + // )).thenAnswer((_) async => ''); + + return httpWrapper; +} + +mockPackageInfo() { + PackageInfo.setMockInitialValues( + appName: _mockInitialValues['appName'], + packageName: _mockInitialValues['packageName'], + version: _mockInitialValues['version'], + buildNumber: _mockInitialValues['buildNumber'], + buildSignature: _mockInitialValues['buildSignature'], + ); +} + +mockConnectivity([List values = const ['wifi']]) { + const channel = MethodChannel('dev.fluttercommunity.plus/connectivity'); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler( + channel.name, + (data) async { + final call = channel.codec.decodeMethodCall(data); + if (call.method == 'getAll') { + return channel.codec.encodeSuccessEnvelope(_mockInitialValues); + } + if (call.method == 'check') { + return channel.codec.encodeSuccessEnvelope(values); + } + return null; + }, + ); +} + +Map get _mockInitialValues => { + 'appName': 'ReownSignTest', + 'packageName': 'com.walletconnect.flutterdapp', + 'version': '1.0', + 'buildNumber': '2', + 'buildSignature': 'buildSignature', + }; diff --git a/packages/reown_sign/test/shared/shared_test_utils.mocks.dart b/packages/reown_sign/test/shared/shared_test_utils.mocks.dart new file mode 100644 index 0000000..0a44348 --- /dev/null +++ b/packages/reown_sign/test/shared/shared_test_utils.mocks.dart @@ -0,0 +1,1558 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in reown_sign/test/shared/shared_test_utils.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i23; +import 'dart:typed_data' as _i21; + +import 'package:event/event.dart' as _i8; +import 'package:http/http.dart' as _i9; +import 'package:logger/logger.dart' as _i19; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i22; +import 'package:reown_core/connectivity/i_connectivity.dart' as _i17; +import 'package:reown_core/core_impl.dart' as _i28; +import 'package:reown_core/crypto/crypto.dart' as _i24; +import 'package:reown_core/crypto/crypto_models.dart' as _i2; +import 'package:reown_core/crypto/crypto_utils.dart' as _i20; +import 'package:reown_core/crypto/i_crypto.dart' as _i10; +import 'package:reown_core/crypto/i_crypto_utils.dart' as _i5; +import 'package:reown_core/echo/i_echo.dart' as _i14; +import 'package:reown_core/heartbit/i_heartbeat.dart' as _i15; +import 'package:reown_core/i_core_impl.dart' as _i3; +import 'package:reown_core/pairing/i_expirer.dart' as _i12; +import 'package:reown_core/pairing/i_pairing.dart' as _i13; +import 'package:reown_core/relay_auth/i_relay_auth.dart' as _i6; +import 'package:reown_core/relay_client/i_relay_client.dart' as _i11; +import 'package:reown_core/relay_client/message_tracker.dart' as _i25; +import 'package:reown_core/relay_client/websocket/http_client.dart' as _i27; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart' + as _i29; +import 'package:reown_core/store/i_generic_store.dart' as _i4; +import 'package:reown_core/store/i_store.dart' as _i7; +import 'package:reown_core/store/link_mode_store.dart' as _i18; +import 'package:reown_core/store/store_models.dart' as _i26; +import 'package:reown_core/verify/i_verify.dart' as _i16; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeCryptoKeyPair_0 extends _i1.SmartFake implements _i2.CryptoKeyPair { + _FakeCryptoKeyPair_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEncodingParams_1 extends _i1.SmartFake + implements _i2.EncodingParams { + _FakeEncodingParams_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEncodingValidation_2 extends _i1.SmartFake + implements _i2.EncodingValidation { + _FakeEncodingValidation_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIReownCore_3 extends _i1.SmartFake implements _i3.IReownCore { + _FakeIReownCore_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIGenericStore_4 extends _i1.SmartFake + implements _i4.IGenericStore { + _FakeIGenericStore_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeICryptoUtils_5 extends _i1.SmartFake implements _i5.ICryptoUtils { + _FakeICryptoUtils_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIRelayAuth_6 extends _i1.SmartFake implements _i6.IRelayAuth { + _FakeIRelayAuth_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIStore_7 extends _i1.SmartFake implements _i7.IStore { + _FakeIStore_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEvent_8 extends _i1.SmartFake + implements _i8.Event { + _FakeEvent_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponse_9 extends _i1.SmartFake implements _i9.Response { + _FakeResponse_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeICrypto_10 extends _i1.SmartFake implements _i10.ICrypto { + _FakeICrypto_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIRelayClient_11 extends _i1.SmartFake implements _i11.IRelayClient { + _FakeIRelayClient_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIExpirer_12 extends _i1.SmartFake implements _i12.IExpirer { + _FakeIExpirer_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIPairing_13 extends _i1.SmartFake implements _i13.IPairing { + _FakeIPairing_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIEcho_14 extends _i1.SmartFake implements _i14.IEcho { + _FakeIEcho_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIHeartBeat_15 extends _i1.SmartFake implements _i15.IHeartBeat { + _FakeIHeartBeat_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIVerify_16 extends _i1.SmartFake implements _i16.IVerify { + _FakeIVerify_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIConnectivity_17 extends _i1.SmartFake + implements _i17.IConnectivity { + _FakeIConnectivity_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeILinkModeStore_18 extends _i1.SmartFake + implements _i18.ILinkModeStore { + _FakeILinkModeStore_18( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLogger_19 extends _i1.SmartFake implements _i19.Logger { + _FakeLogger_19( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [CryptoUtils]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCryptoUtils extends _i1.Mock implements _i20.CryptoUtils { + MockCryptoUtils() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.CryptoKeyPair generateKeyPair() => (super.noSuchMethod( + Invocation.method( + #generateKeyPair, + [], + ), + returnValue: _FakeCryptoKeyPair_0( + this, + Invocation.method( + #generateKeyPair, + [], + ), + ), + ) as _i2.CryptoKeyPair); + + @override + _i21.Uint8List randomBytes(int? length) => (super.noSuchMethod( + Invocation.method( + #randomBytes, + [length], + ), + returnValue: _i21.Uint8List(0), + ) as _i21.Uint8List); + + @override + String generateRandomBytes32() => (super.noSuchMethod( + Invocation.method( + #generateRandomBytes32, + [], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #generateRandomBytes32, + [], + ), + ), + ) as String); + + @override + _i23.Future deriveSymKey( + String? privKeyA, + String? pubKeyB, + ) => + (super.noSuchMethod( + Invocation.method( + #deriveSymKey, + [ + privKeyA, + pubKeyB, + ], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #deriveSymKey, + [ + privKeyA, + pubKeyB, + ], + ), + )), + ) as _i23.Future); + + @override + String hashKey(String? key) => (super.noSuchMethod( + Invocation.method( + #hashKey, + [key], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashKey, + [key], + ), + ), + ) as String); + + @override + String hashMessage(String? message) => (super.noSuchMethod( + Invocation.method( + #hashMessage, + [message], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashMessage, + [message], + ), + ), + ) as String); + + @override + _i23.Future encrypt( + String? message, + String? symKey, { + int? type, + String? iv, + String? senderPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #encrypt, + [ + message, + symKey, + ], + { + #type: type, + #iv: iv, + #senderPublicKey: senderPublicKey, + }, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #encrypt, + [ + message, + symKey, + ], + { + #type: type, + #iv: iv, + #senderPublicKey: senderPublicKey, + }, + ), + )), + ) as _i23.Future); + + @override + _i23.Future decrypt( + String? symKey, + String? encoded, + ) => + (super.noSuchMethod( + Invocation.method( + #decrypt, + [ + symKey, + encoded, + ], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #decrypt, + [ + symKey, + encoded, + ], + ), + )), + ) as _i23.Future); + + @override + String serialize( + int? type, + _i21.Uint8List? sealed, + _i21.Uint8List? iv, { + _i21.Uint8List? senderPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #serialize, + [ + type, + sealed, + iv, + ], + {#senderPublicKey: senderPublicKey}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #serialize, + [ + type, + sealed, + iv, + ], + {#senderPublicKey: senderPublicKey}, + ), + ), + ) as String); + + @override + _i2.EncodingParams deserialize(String? encoded) => (super.noSuchMethod( + Invocation.method( + #deserialize, + [encoded], + ), + returnValue: _FakeEncodingParams_1( + this, + Invocation.method( + #deserialize, + [encoded], + ), + ), + ) as _i2.EncodingParams); + + @override + _i2.EncodingValidation validateDecoding( + String? encoded, { + String? receiverPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #validateDecoding, + [encoded], + {#receiverPublicKey: receiverPublicKey}, + ), + returnValue: _FakeEncodingValidation_2( + this, + Invocation.method( + #validateDecoding, + [encoded], + {#receiverPublicKey: receiverPublicKey}, + ), + ), + ) as _i2.EncodingValidation); + + @override + _i2.EncodingValidation validateEncoding({ + int? type, + String? senderPublicKey, + String? receiverPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #validateEncoding, + [], + { + #type: type, + #senderPublicKey: senderPublicKey, + #receiverPublicKey: receiverPublicKey, + }, + ), + returnValue: _FakeEncodingValidation_2( + this, + Invocation.method( + #validateEncoding, + [], + { + #type: type, + #senderPublicKey: senderPublicKey, + #receiverPublicKey: receiverPublicKey, + }, + ), + ), + ) as _i2.EncodingValidation); + + @override + bool isTypeOneEnvelope(_i2.EncodingValidation? result) => (super.noSuchMethod( + Invocation.method( + #isTypeOneEnvelope, + [result], + ), + returnValue: false, + ) as bool); + + @override + bool isTypeTwoEnvelope(_i2.EncodingValidation? result) => (super.noSuchMethod( + Invocation.method( + #isTypeTwoEnvelope, + [result], + ), + returnValue: false, + ) as bool); + + @override + _i21.Uint8List encodeTypeByte(int? type) => (super.noSuchMethod( + Invocation.method( + #encodeTypeByte, + [type], + ), + returnValue: _i21.Uint8List(0), + ) as _i21.Uint8List); + + @override + int decodeTypeByte(_i21.Uint8List? byte) => (super.noSuchMethod( + Invocation.method( + #decodeTypeByte, + [byte], + ), + returnValue: 0, + ) as int); + + @override + String encodeTypeTwoEnvelope({required String? message}) => + (super.noSuchMethod( + Invocation.method( + #encodeTypeTwoEnvelope, + [], + {#message: message}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #encodeTypeTwoEnvelope, + [], + {#message: message}, + ), + ), + ) as String); + + @override + String decodeTypeTwoEnvelope({required String? message}) => + (super.noSuchMethod( + Invocation.method( + #decodeTypeTwoEnvelope, + [], + {#message: message}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #decodeTypeTwoEnvelope, + [], + {#message: message}, + ), + ), + ) as String); +} + +/// A class which mocks [Crypto]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCrypto extends _i1.Mock implements _i24.Crypto { + MockCrypto() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.IReownCore get core => (super.noSuchMethod( + Invocation.getter(#core), + returnValue: _FakeIReownCore_3( + this, + Invocation.getter(#core), + ), + ) as _i3.IReownCore); + + @override + _i4.IGenericStore get keyChain => (super.noSuchMethod( + Invocation.getter(#keyChain), + returnValue: _FakeIGenericStore_4( + this, + Invocation.getter(#keyChain), + ), + ) as _i4.IGenericStore); + + @override + set keyChain(_i4.IGenericStore? _keyChain) => super.noSuchMethod( + Invocation.setter( + #keyChain, + _keyChain, + ), + returnValueForMissingStub: null, + ); + + @override + _i5.ICryptoUtils get utils => (super.noSuchMethod( + Invocation.getter(#utils), + returnValue: _FakeICryptoUtils_5( + this, + Invocation.getter(#utils), + ), + ) as _i5.ICryptoUtils); + + @override + set utils(_i5.ICryptoUtils? _utils) => super.noSuchMethod( + Invocation.setter( + #utils, + _utils, + ), + returnValueForMissingStub: null, + ); + + @override + _i6.IRelayAuth get relayAuth => (super.noSuchMethod( + Invocation.getter(#relayAuth), + returnValue: _FakeIRelayAuth_6( + this, + Invocation.getter(#relayAuth), + ), + ) as _i6.IRelayAuth); + + @override + set relayAuth(_i6.IRelayAuth? _relayAuth) => super.noSuchMethod( + Invocation.setter( + #relayAuth, + _relayAuth, + ), + returnValueForMissingStub: null, + ); + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + _i23.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool hasKeys(String? tag) => (super.noSuchMethod( + Invocation.method( + #hasKeys, + [tag], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future getClientId() => (super.noSuchMethod( + Invocation.method( + #getClientId, + [], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #getClientId, + [], + ), + )), + ) as _i23.Future); + + @override + _i23.Future generateKeyPair() => (super.noSuchMethod( + Invocation.method( + #generateKeyPair, + [], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #generateKeyPair, + [], + ), + )), + ) as _i23.Future); + + @override + _i23.Future generateSharedKey( + String? selfPublicKey, + String? peerPublicKey, { + String? overrideTopic, + }) => + (super.noSuchMethod( + Invocation.method( + #generateSharedKey, + [ + selfPublicKey, + peerPublicKey, + ], + {#overrideTopic: overrideTopic}, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #generateSharedKey, + [ + selfPublicKey, + peerPublicKey, + ], + {#overrideTopic: overrideTopic}, + ), + )), + ) as _i23.Future); + + @override + _i23.Future setSymKey( + String? symKey, { + String? overrideTopic, + }) => + (super.noSuchMethod( + Invocation.method( + #setSymKey, + [symKey], + {#overrideTopic: overrideTopic}, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #setSymKey, + [symKey], + {#overrideTopic: overrideTopic}, + ), + )), + ) as _i23.Future); + + @override + _i23.Future deleteKeyPair(String? publicKey) => (super.noSuchMethod( + Invocation.method( + #deleteKeyPair, + [publicKey], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future deleteSymKey(String? topic) => (super.noSuchMethod( + Invocation.method( + #deleteSymKey, + [topic], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future encode( + String? topic, + Map? payload, { + _i2.EncodeOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #encode, + [ + topic, + payload, + ], + {#options: options}, + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future decode( + String? topic, + String? encoded, { + _i2.DecodeOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #decode, + [ + topic, + encoded, + ], + {#options: options}, + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future signJWT(String? aud) => (super.noSuchMethod( + Invocation.method( + #signJWT, + [aud], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #signJWT, + [aud], + ), + )), + ) as _i23.Future); + + @override + int getPayloadType(String? encoded) => (super.noSuchMethod( + Invocation.method( + #getPayloadType, + [encoded], + ), + returnValue: 0, + ) as int); + + @override + String? getPayloadSenderPublicKey(String? encoded) => + (super.noSuchMethod(Invocation.method( + #getPayloadSenderPublicKey, + [encoded], + )) as String?); + + @override + _i5.ICryptoUtils getUtils() => (super.noSuchMethod( + Invocation.method( + #getUtils, + [], + ), + returnValue: _FakeICryptoUtils_5( + this, + Invocation.method( + #getUtils, + [], + ), + ), + ) as _i5.ICryptoUtils); +} + +/// A class which mocks [MessageTracker]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMessageTracker extends _i1.Mock implements _i25.MessageTracker { + MockMessageTracker() { + _i1.throwOnMissingStub(this); + } + + @override + String get context => (super.noSuchMethod( + Invocation.getter(#context), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#context), + ), + ) as String); + + @override + String get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#version), + ), + ) as String); + + @override + _i7.IStore get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeIStore_7( + this, + Invocation.getter(#storage), + ), + ) as _i7.IStore); + + @override + _i8.Event<_i26.StoreCreateEvent>> get onCreate => + (super.noSuchMethod( + Invocation.getter(#onCreate), + returnValue: _FakeEvent_8<_i26.StoreCreateEvent>>( + this, + Invocation.getter(#onCreate), + ), + ) as _i8.Event<_i26.StoreCreateEvent>>); + + @override + _i8.Event<_i26.StoreUpdateEvent>> get onUpdate => + (super.noSuchMethod( + Invocation.getter(#onUpdate), + returnValue: _FakeEvent_8<_i26.StoreUpdateEvent>>( + this, + Invocation.getter(#onUpdate), + ), + ) as _i8.Event<_i26.StoreUpdateEvent>>); + + @override + _i8.Event<_i26.StoreDeleteEvent>> get onDelete => + (super.noSuchMethod( + Invocation.getter(#onDelete), + returnValue: _FakeEvent_8<_i26.StoreDeleteEvent>>( + this, + Invocation.getter(#onDelete), + ), + ) as _i8.Event<_i26.StoreDeleteEvent>>); + + @override + _i8.Event<_i26.StoreSyncEvent> get onSync => (super.noSuchMethod( + Invocation.getter(#onSync), + returnValue: _FakeEvent_8<_i26.StoreSyncEvent>( + this, + Invocation.getter(#onSync), + ), + ) as _i8.Event<_i26.StoreSyncEvent>); + + @override + Map> get data => (super.noSuchMethod( + Invocation.getter(#data), + returnValue: >{}, + ) as Map>); + + @override + set data(Map>? _data) => super.noSuchMethod( + Invocation.setter( + #data, + _data, + ), + returnValueForMissingStub: null, + ); + + @override + Map Function(dynamic) get fromJson => (super.noSuchMethod( + Invocation.getter(#fromJson), + returnValue: (dynamic __p0) => {}, + ) as Map Function(dynamic)); + + @override + String get storageKey => (super.noSuchMethod( + Invocation.getter(#storageKey), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#storageKey), + ), + ) as String); + + @override + String hashMessage(String? message) => (super.noSuchMethod( + Invocation.method( + #hashMessage, + [message], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashMessage, + [message], + ), + ), + ) as String); + + @override + _i23.Future recordMessageEvent( + String? topic, + String? message, + ) => + (super.noSuchMethod( + Invocation.method( + #recordMessageEvent, + [ + topic, + message, + ], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool messageIsRecorded( + String? topic, + String? message, + ) => + (super.noSuchMethod( + Invocation.method( + #messageIsRecorded, + [ + topic, + message, + ], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool has(String? key) => (super.noSuchMethod( + Invocation.method( + #has, + [key], + ), + returnValue: false, + ) as bool); + + @override + Map? get(String? key) => + (super.noSuchMethod(Invocation.method( + #get, + [key], + )) as Map?); + + @override + List> getAll() => (super.noSuchMethod( + Invocation.method( + #getAll, + [], + ), + returnValue: >[], + ) as List>); + + @override + _i23.Future set( + String? key, + Map? value, + ) => + (super.noSuchMethod( + Invocation.method( + #set, + [ + key, + value, + ], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future delete(String? key) => (super.noSuchMethod( + Invocation.method( + #delete, + [key], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future persist() => (super.noSuchMethod( + Invocation.method( + #persist, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future restore() => (super.noSuchMethod( + Invocation.method( + #restore, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + void checkInitialized() => super.noSuchMethod( + Invocation.method( + #checkInitialized, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [HttpWrapper]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHttpWrapper extends _i1.Mock implements _i27.HttpWrapper { + MockHttpWrapper() { + _i1.throwOnMissingStub(this); + } + + @override + _i23.Future<_i9.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i23.Future<_i9.Response>); + + @override + _i23.Future<_i9.Response> delete( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + {#headers: headers}, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #delete, + [url], + {#headers: headers}, + ), + )), + ) as _i23.Future<_i9.Response>); + + @override + _i23.Future<_i9.Response> post( + Uri? url, { + Map? headers, + Object? body, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + }, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + }, + ), + )), + ) as _i23.Future<_i9.Response>); +} + +/// A class which mocks [ReownCore]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReownCore extends _i1.Mock implements _i28.ReownCore { + MockReownCore() { + _i1.throwOnMissingStub(this); + } + + @override + String get projectId => (super.noSuchMethod( + Invocation.getter(#projectId), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#projectId), + ), + ) as String); + + @override + String get relayUrl => (super.noSuchMethod( + Invocation.getter(#relayUrl), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#relayUrl), + ), + ) as String); + + @override + set relayUrl(String? _relayUrl) => super.noSuchMethod( + Invocation.setter( + #relayUrl, + _relayUrl, + ), + returnValueForMissingStub: null, + ); + + @override + String get pushUrl => (super.noSuchMethod( + Invocation.getter(#pushUrl), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#pushUrl), + ), + ) as String); + + @override + set pushUrl(String? _pushUrl) => super.noSuchMethod( + Invocation.setter( + #pushUrl, + _pushUrl, + ), + returnValueForMissingStub: null, + ); + + @override + _i10.ICrypto get crypto => (super.noSuchMethod( + Invocation.getter(#crypto), + returnValue: _FakeICrypto_10( + this, + Invocation.getter(#crypto), + ), + ) as _i10.ICrypto); + + @override + set crypto(_i10.ICrypto? _crypto) => super.noSuchMethod( + Invocation.setter( + #crypto, + _crypto, + ), + returnValueForMissingStub: null, + ); + + @override + _i11.IRelayClient get relayClient => (super.noSuchMethod( + Invocation.getter(#relayClient), + returnValue: _FakeIRelayClient_11( + this, + Invocation.getter(#relayClient), + ), + ) as _i11.IRelayClient); + + @override + set relayClient(_i11.IRelayClient? _relayClient) => super.noSuchMethod( + Invocation.setter( + #relayClient, + _relayClient, + ), + returnValueForMissingStub: null, + ); + + @override + _i12.IExpirer get expirer => (super.noSuchMethod( + Invocation.getter(#expirer), + returnValue: _FakeIExpirer_12( + this, + Invocation.getter(#expirer), + ), + ) as _i12.IExpirer); + + @override + set expirer(_i12.IExpirer? _expirer) => super.noSuchMethod( + Invocation.setter( + #expirer, + _expirer, + ), + returnValueForMissingStub: null, + ); + + @override + _i13.IPairing get pairing => (super.noSuchMethod( + Invocation.getter(#pairing), + returnValue: _FakeIPairing_13( + this, + Invocation.getter(#pairing), + ), + ) as _i13.IPairing); + + @override + set pairing(_i13.IPairing? _pairing) => super.noSuchMethod( + Invocation.setter( + #pairing, + _pairing, + ), + returnValueForMissingStub: null, + ); + + @override + _i14.IEcho get echo => (super.noSuchMethod( + Invocation.getter(#echo), + returnValue: _FakeIEcho_14( + this, + Invocation.getter(#echo), + ), + ) as _i14.IEcho); + + @override + set echo(_i14.IEcho? _echo) => super.noSuchMethod( + Invocation.setter( + #echo, + _echo, + ), + returnValueForMissingStub: null, + ); + + @override + _i15.IHeartBeat get heartbeat => (super.noSuchMethod( + Invocation.getter(#heartbeat), + returnValue: _FakeIHeartBeat_15( + this, + Invocation.getter(#heartbeat), + ), + ) as _i15.IHeartBeat); + + @override + set heartbeat(_i15.IHeartBeat? _heartbeat) => super.noSuchMethod( + Invocation.setter( + #heartbeat, + _heartbeat, + ), + returnValueForMissingStub: null, + ); + + @override + _i16.IVerify get verify => (super.noSuchMethod( + Invocation.getter(#verify), + returnValue: _FakeIVerify_16( + this, + Invocation.getter(#verify), + ), + ) as _i16.IVerify); + + @override + set verify(_i16.IVerify? _verify) => super.noSuchMethod( + Invocation.setter( + #verify, + _verify, + ), + returnValueForMissingStub: null, + ); + + @override + _i17.IConnectivity get connectivity => (super.noSuchMethod( + Invocation.getter(#connectivity), + returnValue: _FakeIConnectivity_17( + this, + Invocation.getter(#connectivity), + ), + ) as _i17.IConnectivity); + + @override + set connectivity(_i17.IConnectivity? _connectivity) => super.noSuchMethod( + Invocation.setter( + #connectivity, + _connectivity, + ), + returnValueForMissingStub: null, + ); + + @override + _i18.ILinkModeStore get linkModeStore => (super.noSuchMethod( + Invocation.getter(#linkModeStore), + returnValue: _FakeILinkModeStore_18( + this, + Invocation.getter(#linkModeStore), + ), + ) as _i18.ILinkModeStore); + + @override + set linkModeStore(_i18.ILinkModeStore? _linkModeStore) => super.noSuchMethod( + Invocation.setter( + #linkModeStore, + _linkModeStore, + ), + returnValueForMissingStub: null, + ); + + @override + _i7.IStore> get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeIStore_7>( + this, + Invocation.getter(#storage), + ), + ) as _i7.IStore>); + + @override + set storage(_i7.IStore>? _storage) => super.noSuchMethod( + Invocation.setter( + #storage, + _storage, + ), + returnValueForMissingStub: null, + ); + + @override + String get protocol => (super.noSuchMethod( + Invocation.getter(#protocol), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#protocol), + ), + ) as String); + + @override + String get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#version), + ), + ) as String); + + @override + _i19.Logger get logger => (super.noSuchMethod( + Invocation.getter(#logger), + returnValue: _FakeLogger_19( + this, + Invocation.getter(#logger), + ), + ) as _i19.Logger); + + @override + void addLogListener(_i19.LogCallback? callback) => super.noSuchMethod( + Invocation.method( + #addLogListener, + [callback], + ), + returnValueForMissingStub: null, + ); + + @override + bool removeLogListener(_i19.LogCallback? callback) => (super.noSuchMethod( + Invocation.method( + #removeLogListener, + [callback], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future start() => (super.noSuchMethod( + Invocation.method( + #start, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future addLinkModeSupportedApp(String? universalLink) => + (super.noSuchMethod( + Invocation.method( + #addLinkModeSupportedApp, + [universalLink], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + List getLinkModeSupportedApps() => (super.noSuchMethod( + Invocation.method( + #getLinkModeSupportedApps, + [], + ), + returnValue: [], + ) as List); + + @override + void confirmOnlineStateOrThrow() => super.noSuchMethod( + Invocation.method( + #confirmOnlineStateOrThrow, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [WebSocketHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSocketHandler extends _i1.Mock implements _i29.WebSocketHandler { + MockWebSocketHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i23.Future get ready => (super.noSuchMethod( + Invocation.getter(#ready), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future setup({required String? url}) => (super.noSuchMethod( + Invocation.method( + #setup, + [], + {#url: url}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future connect() => (super.noSuchMethod( + Invocation.method( + #connect, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future close() => (super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); +} diff --git a/packages/reown_sign/test/shared/shared_test_values.dart b/packages/reown_sign/test/shared/shared_test_values.dart new file mode 100644 index 0000000..28b7bf7 --- /dev/null +++ b/packages/reown_sign/test/shared/shared_test_values.dart @@ -0,0 +1,236 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +const TEST_RELAY_URL = String.fromEnvironment( + 'RELAY_ENDPOINT', + defaultValue: 'wss://relay.walletconnect.org', +); +const TEST_PROJECT_ID = String.fromEnvironment( + 'PROJECT_ID', + defaultValue: 'cad4956f31a5e40a00b62865b030c6f8', +); + +const PROPOSER = PairingMetadata( + name: 'App A (Proposer, dapp)', + description: 'Description of Proposer App run by client A', + url: 'https://reown.org', + icons: ['https://avatars.githubusercontent.com/u/37784886'], +); +const RESPONDER = PairingMetadata( + name: 'App B (Responder, Wallet)', + description: 'Description of Proposer App run by client B', + url: 'https://reown.org', + icons: ['https://avatars.githubusercontent.com/u/37784886'], +); + +const TEST_PAIRING_TOPIC = ''; +const TEST_SESSION_TOPIC = ''; +const TEST_KEY_PAIRS = { + 'A': CryptoKeyPair( + '1fb63fca5c6ac731246f2f069d3bc2454345d5208254aa8ea7bffc6d110c8862', + 'ff7a7d5767c362b0a17ad92299ebdb7831dcbd9a56959c01368c7404543b3342', + ), + 'B': CryptoKeyPair( + '36bf507903537de91f5e573666eaa69b1fa313974f23b2b59645f20fea505854', + '590c2c627be7af08597091ff80dd41f7fa28acd10ef7191d7e830e116d3a186a', + ), +}; + +const TEST_SHARED_KEY = + '9c87e48e69b33a613907515bcd5b1b4cc10bbaf15167b19804b00f0a9217e607'; +const TEST_HASHED_KEY = + 'a492906ccc809a411bb53a84572b57329375378c6ad7566f3e1c688200123e77'; +const TEST_SYM_KEY = + '0653ca620c7b4990392e1c53c4a51c14a2840cd20f0f1524cf435b17b6fe988c'; + +const TEST_URI = + 'wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&relay-protocol=irn'; +const TEST_URI_V1 = + 'wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@1?key=abc&bridge=xyz'; + +const TEST_ETHEREUM_CHAIN = 'eip155:1'; + +final Set availableAccounts = { + 'namespace1:chain1:address1', + 'namespace1:chain1:address2', + 'namespace2:chain1:address3', + 'namespace2:chain1:address4', + 'namespace2:chain2:address5', + 'namespace4:chain1:address6', +}; + +final Set availableMethods = { + 'namespace1:chain1:method1', + 'namespace1:chain1:method2', + 'namespace2:chain1:method3', + 'namespace2:chain1:method4', + 'namespace2:chain2:method3', + 'namespace4:chain1:method5', +}; + +final Set availableEvents = { + 'namespace1:chain1:event1', + 'namespace1:chain1:event2', + 'namespace2:chain1:event3', + 'namespace2:chain1:event4', + 'namespace2:chain2:event3', + 'namespace4:chain1:event5', +}; + +final Map requiredNamespacesInAvailable = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1'], + events: ['event1'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesInAvailable2 = { + 'namespace1': const RequiredNamespace( + methods: ['method1'], + events: ['event1'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesMatchingAvailable1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1'], + methods: ['method3', 'method4'], + events: ['event3', 'event4'], + ), +}; + +final Map requiredNamespacesNonconformingAccounts1 = + { + 'namespace3': const RequiredNamespace( + chains: ['namespace3:chain1'], + methods: [], + events: [], + ), +}; + +final Map requiredNamespacesNonconformingMethods1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2', 'method3'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingMethods2 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2', 'method3'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3', 'method4'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingEvents1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2', 'event3'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingEvents2 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2', 'event3'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3', 'event4'], + ), +}; + +Map optionalNamespaces = { + 'namespace4:chain1': const RequiredNamespace( + methods: ['method5'], + events: ['event5', 'event2'], + ), +}; + +const sepolia = 'eip155:11155111'; + +final Set availableAccounts3 = { + '$sepolia:0x99999999999999999999999999', +}; + +final Set availableMethods3 = { + '$sepolia:eth_sendTransaction', + '$sepolia:personal_sign', + '$sepolia:eth_signTypedData', + '$sepolia:eth_signTypedData_v4', + '$sepolia:eth_sign', +}; + +final Set availableEvents3 = { + '$sepolia:chainChanged', + '$sepolia:accountsChanged', +}; + +final Map requiredNamespacesInAvailable3 = { + 'eip155': const RequiredNamespace( + chains: [sepolia], + methods: ['eth_sendTransaction', 'personal_sign'], + events: ['chainChanged', 'accountsChanged'], + ), +}; + +final Map optionalNamespacesInAvailable3 = { + 'eip155': const RequiredNamespace(chains: [ + 'eip155:1', + 'eip155:5', + sepolia, + 'eip155:137', + 'eip155:80001', + 'eip155:42220', + 'eip155:44787', + 'eip155:56', + 'eip155:43114', + 'eip155:42161', + 'eip155:421613', + 'eip155:10', + 'eip155:420', + 'eip155:8453' + ], methods: [ + 'eth_sendTransaction', + 'personal_sign', + 'eth_signTypedData', + 'eth_signTypedData_v4', + 'eth_sign' + ], events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ]), +}; diff --git a/packages/reown_sign/test/shared/signature_constants.dart b/packages/reown_sign/test/shared/signature_constants.dart new file mode 100644 index 0000000..0fe7513 --- /dev/null +++ b/packages/reown_sign/test/shared/signature_constants.dart @@ -0,0 +1,67 @@ +import 'package:reown_sign/reown_sign.dart'; + +import 'shared_test_values.dart'; + +const TEST_SIG_EIP1271 = + '0xc1505719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c'; +const TEST_ADDRESS_EIP1271 = '0x2faf83c542b68f1b4cdc0e770e8cb9f567b08f71'; +const TEST_MESSAGE_EIP1271 = + '''localhost wants you to sign in with your Ethereum account: +$TEST_ADDRESS_EIP1271 +URI: http://localhost:3000/ +Version: 1 +Chain ID: 1 +Nonce: 1665443015700 +Issued At: 2022-10-10T23:03:35.700Z +Expiration Time: 2022-10-11T23:03:35.700Z'''; + +const TEST_MESSAGE_EIP1271_2 = + '''reown.com wants you to sign in with your Ethereum account: +0x59e2f66C0E96803206B6486cDb39029abAE834c0 + +Welcome to AppKit for Flutter. + +URI: https://reown.com/login +Version: 1 +Chain ID: 465321 +Nonce: 1719392409504 +Issued At: 2024-06-26T11:00:41.043Z'''; + +const TEST_SIG_EIP191 = + '0x560a65deed4aaf332d9dbab82af897245c93139773b483072d5e59afdc5788d76e1dcbefaef36b11a52755bfd152241b4ea03d2cc08638818c5105cba9beb83d1c'; +const TEST_PRIVATE_KEY_EIP191 = + '5c0caa455d5354515baae31e01421db4763f21a25dfbffd32052deeb3076dbbb'; +const TEST_ADDRESS_EIP191 = '0x06C6A22feB5f8CcEDA0db0D593e6F26A3611d5fa'; +const TEST_MESSAGE_EIP191 = 'Hello World'; + +const TEST_SIGNATURE_FAIL = + '0xdead5719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c'; + +const TEST_ISSUER_EIP191 = 'did:pkh:$TEST_ETHEREUM_CHAIN:$TEST_ADDRESS_EIP191'; + +const TEST_CACAO_SIGNATURE = CacaoSignature( + t: CacaoSignature.EIP191, + s: TEST_SIG_EIP191, +); + +const TEST_VALID_EIP191_SIGNATURE = { + 'valid': true, + 'address': '0xf082c2c18b8293148fd3ad8b42e2348934f198b1', + 'cacao': { + 'h': {'t': 'eip4361'}, + 'p': { + 'iat': '2023-05-10T20:27:31.619Z', + 'iss': 'did:pkh:eip155:1:0xf082c2c18b8293148fd3ad8b42e2348934f198b1', + 'statement': 'Connect to Web3Modal Lab', + 'domain': 'lab.web3modal.com', + 'aud': 'https://lab.web3modal.com/AuthReact', + 'version': '1', + 'nonce': 'XpJ0thNvq9lNixmwN' + }, + 's': { + 't': 'eip191', + 's': + '0x80e709c190c879164a6db449696b6c1ba78b71a19e4f8814630fd16d6ebf61863a10b1a0a84f7aeb39bf449a676a5f2a03f5fad16d20eb121523759d387280c91c' + } + } +}; diff --git a/packages/reown_sign/test/sign_client_test.dart b/packages/reown_sign/test/sign_client_test.dart new file mode 100644 index 0000000..1d4666e --- /dev/null +++ b/packages/reown_sign/test/sign_client_test.dart @@ -0,0 +1,35 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_values.dart'; +import 'tests/sign_common.dart'; +import 'utils/sign_client_test_wrapper.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(); + + signEngineTests( + context: 'SignClient', + clientACreator: (PairingMetadata metadata) async => + await SignClientTestWrapper.createInstance( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + metadata: metadata, + memoryStore: true, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ), + clientBCreator: (PairingMetadata metadata) async => + await SignClientTestWrapper.createInstance( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + metadata: metadata, + memoryStore: true, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ), + ); +} diff --git a/packages/reown_sign/test/store_test.dart b/packages/reown_sign/test/store_test.dart new file mode 100644 index 0000000..8540294 --- /dev/null +++ b/packages/reown_sign/test/store_test.dart @@ -0,0 +1,83 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/i_store.dart'; +import 'package:reown_core/store/shared_prefs_store.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import 'shared/engine_constants.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('Store', () { + late IStore> store; + + setUp(() async { + store = SharedPrefsStores( + memoryStore: true, + ); + await store.init(); + }); + + group('special stores', () { + test('sessions', () async { + ISessions specialStore = Sessions( + storage: store, + context: 'messageTracker', + version: 'swag', + fromJson: (dynamic value) { + return SessionData.fromJson(value); + }, + ); + + await specialStore.init(); + + await specialStore.set( + '1', + SessionData( + topic: '1', + pairingTopic: '2', + relay: Relay('irn'), + expiry: -1, + acknowledged: false, + controller: 'controller', + namespaces: { + 'eth': const Namespace(accounts: [], methods: [], events: []), + }, + self: TEST_CONNECTION_METADATA_REQUESTER, + peer: TEST_CONNECTION_METADATA_REQUESTER, + ), + ); + + Completer updateComplete = Completer(); + Completer syncComplete = Completer(); + specialStore.onUpdate.subscribe((args) { + updateComplete.complete(); + }); + specialStore.onSync.subscribe((args) { + syncComplete.complete(); + }); + + expect( + specialStore.get('1')!.expiry, + -1, + ); + + await specialStore.update( + '1', + expiry: 2, + ); + + await updateComplete.future; + await syncComplete.future; + + expect( + specialStore.get('1')!.expiry, + 2, + ); + }); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_approve_session.dart b/packages/reown_sign/test/tests/sign_approve_session.dart new file mode 100644 index 0000000..5d5165f --- /dev/null +++ b/packages/reown_sign/test/tests/sign_approve_session.dart @@ -0,0 +1,156 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/models/basic_models.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/engine_constants.dart'; +import '../utils/sign_client_constants.dart'; + +void signApproveSession({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('approveSession', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + + await clientB.proposals.set( + TEST_PROPOSAL_VALID_ID.toString(), + TEST_PROPOSAL_VALID, + ); + await clientB.proposals.set( + TEST_PROPOSAL_EXPIRED_ID.toString(), + TEST_PROPOSAL_EXPIRED, + ); + await clientB.core.expirer.set( + TEST_PROPOSAL_EXPIRED_ID.toString(), + TEST_PROPOSAL_EXPIRED.expiry, + ); + await clientB.proposals.set( + TEST_PROPOSAL_INVALID_REQUIRED_NAMESPACES_ID.toString(), + TEST_PROPOSAL_INVALID_REQUIRED_NAMESPACES, + ); + await clientB.proposals.set( + TEST_PROPOSAL_INVALID_OPTIONAL_NAMESPACES_ID.toString(), + TEST_PROPOSAL_INVALID_OPTIONAL_NAMESPACES, + ); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('invalid proposal id', () async { + expect( + () async => await clientB.approveSession( + id: TEST_APPROVE_ID_INVALID, + namespaces: TEST_NAMESPACES, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. proposal id doesn\'t exist: $TEST_APPROVE_ID_INVALID', + ), + ), + ); + + int counter = 0; + Completer completer = Completer(); + clientB.core.expirer.onExpire.subscribe((args) { + counter++; + completer.complete(); + }); + int counterSession = 0; + Completer completer2 = Completer(); + clientB.onProposalExpire.subscribe((args) { + counterSession++; + completer2.complete(); + }); + expect( + () async => await clientB.approveSession( + id: TEST_PROPOSAL_EXPIRED_ID, + namespaces: TEST_NAMESPACES, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expired. proposal id: $TEST_PROPOSAL_EXPIRED_ID', + ), + ), + ); + + // await Future.delayed(Duration(milliseconds: 250)); + await completer.future; + await completer2.future; + + expect( + clientB.proposals.has( + TEST_PROPOSAL_EXPIRED_ID.toString(), + ), + false, + ); + expect(counter, 1); + expect(counterSession, 1); + clientB.core.expirer.onExpire.unsubscribeAll(); + clientB.onProposalExpire.unsubscribeAll(); + }); + + test('invalid namespaces', () async { + expect( + () async => await clientB.approveSession( + id: TEST_PROPOSAL_INVALID_REQUIRED_NAMESPACES_ID, + namespaces: TEST_NAMESPACES, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. approve() check requiredNamespaces. requiredNamespace, namespace is a chainId, but chains is not empty', + ), + ), + ); + expect( + () async => await clientB.approveSession( + id: TEST_PROPOSAL_INVALID_OPTIONAL_NAMESPACES_ID, + namespaces: TEST_NAMESPACES, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. approve() check optionalNamespaces. requiredNamespace, namespace is a chainId, but chains is not empty', + ), + ), + ); + expect( + () async => await clientB.approveSession( + id: TEST_PROPOSAL_VALID_ID, + namespaces: TEST_NAMESPACES_NONCONFORMING_KEY_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported namespace key. approve() namespaces keys don\'t satisfy requiredNamespaces', + ), + ), + ); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_approve_session_authenticate.dart b/packages/reown_sign/test/tests/sign_approve_session_authenticate.dart new file mode 100644 index 0000000..9dffad3 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_approve_session_authenticate.dart @@ -0,0 +1,71 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/engine_constants.dart'; +import '../shared/shared_test_values.dart'; +import '../utils/engine_constants.dart'; + +void signApproveSessionAuthenticate({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('approveSessionAuthenticate', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER.copyWith( + redirect: Redirect( + native: 'clientA://', + universal: 'https://lab.web3modal.com/dapp', + linkMode: true, + ), + )); + clientB = await clientBCreator(RESPONDER.copyWith( + redirect: Redirect( + native: 'clientB://', + universal: 'https://lab.web3modal.com/wallet', + linkMode: true, + ), + )); + await clientA.core.addLinkModeSupportedApp( + 'https://lab.web3modal.com/wallet', + ); + await clientB.core.addLinkModeSupportedApp( + 'https://lab.web3modal.com/dapp', + ); + clients.add(clientA); + clients.add(clientB); + + await clientB.sessionAuthRequests.set( + TEST_PROPOSAL_VALID_ID.toString(), + PendingSessionAuthRequest( + id: TEST_PROPOSAL_VALID_ID, + pairingTopic: TEST_PAIRING_TOPIC, + requester: TEST_CONNECTION_METADATA_REQUESTER.copyWith( + publicKey: TEST_KEY_PAIRS['A']!.publicKey, + ), + authPayload: CacaoRequestPayload.fromCacaoPayload(testCacaoPayload), + expiryTimestamp: 1000000000000, + verifyContext: VerifyContext( + origin: 'test.com', + validation: Validation.VALID, + verifyUrl: ReownConstants.VERIFY_SERVER, + ), + transportType: TransportType.linkMode, + ), + ); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_authenticate.dart b/packages/reown_sign/test/tests/sign_authenticate.dart new file mode 100644 index 0000000..a2b5836 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_authenticate.dart @@ -0,0 +1,102 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; + +void signAuthenticate({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('happy path', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER.copyWith( + redirect: Redirect( + native: 'clientA://', + universal: 'https://lab.web3modal.com/dapp', + linkMode: true, + ), + )); + clientB = await clientBCreator(RESPONDER.copyWith( + redirect: Redirect( + native: 'clientB://', + universal: 'https://lab.web3modal.com/wallet', + linkMode: true, + ), + )); + clients.add(clientA); + clients.add(clientB); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('Initializes', () async { + expect(clientA.core.pairing.getPairings().length, 0); + expect(clientB.core.pairing.getPairings().length, 0); + }); + + test('creates correct URI for LinkMode', () async { + await clientA.core.addLinkModeSupportedApp( + 'https://lab.web3modal.com/wallet', + ); + await clientB.core.addLinkModeSupportedApp( + 'https://lab.web3modal.com/dapp', + ); + + SessionAuthRequestResponse response = await clientA.authenticate( + params: SessionAuthRequestParams( + chains: ['eip155:1'], + domain: 'lab.web3modal.com', + nonce: 'XpJ0thNvq9lNixmwN', + uri: 'https://lab.web3modal.com/dapp', + statement: 'Connect to Web3Modal Lab', + methods: ['personal_sign'], + ), + walletUniversalLink: 'https://lab.web3modal.com/wallet', + ); + + expect(response.uri != null, true); + final envelope = ReownCoreUtils.getSearchParamFromURL( + '${response.uri}', + 'wc_ev', + ); + final message = Uri.decodeComponent(envelope); + + final topic = ReownCoreUtils.getSearchParamFromURL( + '${response.uri}', + 'topic', + ); + expect(message.isNotEmpty, true); + expect(topic.isNotEmpty, true); + expect(topic, response.pairingTopic); + + // Decode the message + String? payloadString = await clientA.core.crypto.decode( + topic, + message, + ); + expect(payloadString, isNotNull); + + Map data = jsonDecode(payloadString!); + + expect(data.containsKey('method'), true); + + final request = JsonRpcRequest.fromJson(data); + + expect(request.method, MethodConstants.WC_SESSION_AUTHENTICATE); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_client_helpers.dart b/packages/reown_sign/test/tests/sign_client_helpers.dart new file mode 100644 index 0000000..c238523 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_client_helpers.dart @@ -0,0 +1,397 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/sign_client_constants.dart'; + +class TestConnectMethodReturn { + PairingInfo pairing; + SessionData session; + int connectLatency; + int settleLatency; + + TestConnectMethodReturn( + this.pairing, + this.session, + this.connectLatency, + this.settleLatency, + ); +} + +class SignClientHelpers { + static Future testConnectPairApprove( + IReownSignDapp a, + IReownSignWallet b, { + Map? namespaces, + Map? requiredNamespaces, + Map>? accounts, + Map>? methods, + Map>? events, + List? relays, + String? pairingTopic, + int? qrCodeScanLatencyMs, + }) async { + final start = DateTime.now().millisecondsSinceEpoch; + final Map reqNamespaces = + requiredNamespaces ?? TEST_REQUIRED_NAMESPACES; + + Map workingNamespaces = namespaces ?? TEST_NAMESPACES; + + Map> workingAccounts = accounts ?? + { + TEST_ETHEREUM_CHAIN: [TEST_ETHEREUM_ADDRESS], + TEST_ARBITRUM_CHAIN: [TEST_ETHEREUM_ADDRESS], + TEST_AVALANCHE_CHAIN: [TEST_ETHEREUM_ADDRESS], + }; + + Map> workingMethods = methods ?? + { + TEST_ETHEREUM_CHAIN: TEST_METHODS_1, + TEST_ARBITRUM_CHAIN: TEST_METHODS_1, + TEST_AVALANCHE_CHAIN: TEST_METHODS_2, + }; + + Map> workingEvents = events ?? + { + TEST_ETHEREUM_CHAIN: [TEST_EVENT_1], + TEST_ARBITRUM_CHAIN: [TEST_EVENT_1], + TEST_AVALANCHE_CHAIN: [TEST_EVENT_2], + }; + + // Register the data: accounts, methods, events + for (final chainId in workingAccounts.keys) { + for (final account in workingAccounts[chainId]!) { + b.registerAccount( + chainId: chainId, + accountAddress: account, + ); + } + } + for (final chainId in workingMethods.keys) { + for (final method in workingMethods[chainId]!) { + b.registerRequestHandler(chainId: chainId, method: method); + } + } + for (final chainId in workingEvents.keys) { + for (final event in workingEvents[chainId]!) { + b.registerEventEmitter( + chainId: chainId, + event: event, + ); + } + } + + late SessionData sessionA; + SessionData? sessionB; + + // Listen for a proposal via connect to avoid race conditions + Completer sessionBCompleter = Completer(); + func(SessionProposalEvent? args) async { + // print('B Session Proposal'); + + expect( + args!.params.requiredNamespaces, + reqNamespaces, + ); + + expect(b.getPendingSessionProposals().length, 1); + + Completer completer = Completer(); + b.onSessionConnect.subscribe((args) { + expect(args != null, true); + completer.complete(); + }); + + workingNamespaces = args.params.generatedNamespaces ?? workingNamespaces; + + ApproveResponse response = await b.approveSession( + id: args.id, + namespaces: workingNamespaces, + ); + sessionB = response.session; + + if (!completer.isCompleted) { + await completer.future; + } + + b.onSessionConnect.unsubscribeAll(); + sessionBCompleter.complete(); + + // print('B Session assigned: $sessionB'); + // expect(b.core.expirer.has(args.params.id.toString()), true); + } + + b.onSessionProposal.subscribe(func); + + // Connect to client b from a, this will trigger the above event + // print('connecting'); + ConnectResponse connectResponse = await a.connect( + requiredNamespaces: reqNamespaces, + pairingTopic: pairingTopic, + relays: relays, + ); + connectResponse.session.future.then( + (value) => sessionA = value, + ); + Uri? uri = connectResponse.uri; + + // Track latency + final clientAConnectLatencyMs = + DateTime.now().millisecondsSinceEpoch - start; + + // Track pairings from "QR Scans" + PairingInfo? pairingA; + PairingInfo? pairingB; + + if (pairingTopic == null) { + // Simulate qr code scan latency if we want + if (uri == null) { + throw Exception('uri is missing'); + } + if (qrCodeScanLatencyMs != null) { + await Future.delayed( + Duration( + milliseconds: qrCodeScanLatencyMs, + ), + ); + } + + final uriParams = ReownCoreUtils.parseUri(connectResponse.uri!); + pairingA = a.pairings.get(uriParams.topic); + expect(pairingA != null, true); + expect(pairingA!.topic, uriParams.topic); + expect(pairingA.relay.protocol, uriParams.v2Data!.relay.protocol); + + // If we recieved no pairing topic, then we want to create one + // e.g. we pair from b to a using the uri created from the connect + // params (The QR code). + const pairTimeoutMs = 15000; + final timeout = Timer(const Duration(milliseconds: pairTimeoutMs), () { + throw Exception('Pair timed out after $pairTimeoutMs ms'); + }); + // print('pairing B -> A'); + pairingB = await b.pair(uri: uri); + timeout.cancel(); + expect(pairingA.topic, pairingB.topic); + expect(pairingA.relay.protocol, pairingB.relay.protocol); + } else { + pairingA = a.pairings.get(pairingTopic); + pairingB = b.pairings.get(pairingTopic); + } + + if (pairingA == null) { + throw Exception('expect pairing A to be defined'); + } + + // Assign session now that we have paired + if (!connectResponse.session.isCompleted) { + a.core.logger.i('signClientHelpers waiting connectResponseCompleter'); + // print('Waiting for connect response'); + await connectResponse.session.future; + } + + final settlePairingLatencyMs = DateTime.now().millisecondsSinceEpoch - + start - + (qrCodeScanLatencyMs ?? 0); + + if (!sessionBCompleter.isCompleted) { + a.core.logger.i('signClientHelpers waiting sessionBCompleter'); + await sessionBCompleter.future; + } + + // if (sessionA == null) throw Exception("expect session A to be defined"); + if (sessionB == null) throw Exception('expect session B to be defined'); + + expect(sessionA.topic, sessionB!.topic); + // relay + expect( + sessionA.relay.protocol, + TEST_RELAY_OPTIONS['protocol'], + ); + expect(sessionA.relay.protocol, sessionB!.relay.protocol); + // namespaces + expect(sessionA.namespaces, workingNamespaces); + expect(sessionA.namespaces, sessionB!.namespaces); + // expiry + expect((sessionA.expiry - sessionB!.expiry).abs() < 5, true); + // Check that there is an expiry + expect(a.core.expirer.has(sessionA.topic), true); + expect(b.core.expirer.has(sessionB!.topic), true); + // acknowledged + expect(sessionA.acknowledged, true); + expect(sessionB!.acknowledged, true); + // participants + expect(sessionA.self, sessionB!.peer); + expect(sessionA.peer, sessionB!.self); + // controller + + expect(sessionA.controller, sessionB!.controller); + expect(sessionA.controller, sessionA.peer.publicKey); + expect(sessionB!.controller, sessionB!.self.publicKey); + // metadata + expect(sessionA.self.metadata, sessionB!.peer.metadata); + expect(sessionB!.self.metadata, sessionA.peer.metadata); + + // if (pairingA == null) throw Exception("expect pairing A to be defined"); + if (pairingB == null) throw Exception('expect pairing B to be defined'); + + // update pairing state beforehand + pairingA = a.pairings.get(pairingA.topic); + pairingB = b.pairings.get(pairingB.topic); + + // topic + expect(pairingA!.topic, pairingB!.topic); + // relay + expect( + pairingA.relay.protocol, + TEST_RELAY_OPTIONS['protocol'], + ); + expect( + pairingB.relay.protocol, + TEST_RELAY_OPTIONS['protocol'], + ); + // active + expect(pairingA.active, true); + expect(pairingB.active, true); + // metadata + expect( + pairingA.peerMetadata, + sessionA.peer.metadata, + ); + expect( + pairingB.peerMetadata, + sessionB!.peer.metadata, + ); + + b.onSessionProposal.unsubscribe(func); + + return TestConnectMethodReturn( + pairingA, + sessionA, + clientAConnectLatencyMs, + settlePairingLatencyMs, + ); + } + + static Future testConnectPairReject( + IReownSignDapp a, + IReownSignWallet b, { + Map? namespaces, + Map? requiredNamespaces, + List? relays, + String? pairingTopic, + int? qrCodeScanLatencyMs, + }) async { + final start = DateTime.now().millisecondsSinceEpoch; + final Map reqNamespaces = + requiredNamespaces ?? TEST_REQUIRED_NAMESPACES; + + // Map workingNamespaces = + // namespaces != null ? namespaces : TEST_NAMESPACES; + + // SessionData? sessionA; + + // Listen for a proposal via connect to avoid race conditions + Completer sessionBCompleter = Completer(); + f(SessionProposalEvent? args) async { + expect( + args!.params.requiredNamespaces, + reqNamespaces, + ); + + // expect(b.getPendingSessionProposals().length, 1); + + await b.rejectSession( + id: args.id, + reason: ReownSignError.fromJson( + Errors.getSdkError(Errors.USER_REJECTED).toJson(), + ), + ); + sessionBCompleter.complete(); + + // print('B Session assigned: $sessionB'); + // expect(b.core.expirer.has(args.params.id.toString()), true); + } + + b.onSessionProposal.subscribe(f); + + // Connect to client b from a, this will trigger the above event + // print('connecting'); + ConnectResponse connectResponse = await a.connect( + requiredNamespaces: reqNamespaces, + pairingTopic: pairingTopic, + relays: relays, + ); + Uri? uri = connectResponse.uri; + + // Track latency + final _ = DateTime.now().millisecondsSinceEpoch - start; + + // Track pairings from "QR Scans" + PairingInfo? pairingA; + PairingInfo? pairingB; + + if (pairingTopic == null) { + // Simulate qr code scan latency if we want + if (uri == null) { + throw Exception('uri is missing'); + } + if (qrCodeScanLatencyMs != null) { + await Future.delayed( + Duration( + milliseconds: qrCodeScanLatencyMs, + ), + ); + } + + final uriParams = ReownCoreUtils.parseUri(connectResponse.uri!); + pairingA = a.pairings.get(uriParams.topic); + expect(pairingA != null, true); + expect(pairingA!.topic, uriParams.topic); + expect(pairingA.relay.protocol, uriParams.v2Data!.relay.protocol); + + // If we recieved no pairing topic, then we want to create one + // e.g. we pair from b to a using the uri created from the connect + // params (The QR code). + const pairTimeoutMs = 15000; + final timeout = Timer(const Duration(milliseconds: pairTimeoutMs), () { + throw Exception('Pair timed out after $pairTimeoutMs ms'); + }); + // print('pairing B -> A'); + pairingB = await b.pair(uri: uri); + timeout.cancel(); + expect(pairingA.topic, pairingB.topic); + expect(pairingA.relay.protocol, pairingB.relay.protocol); + } else { + pairingA = a.pairings.get(pairingTopic); + pairingB = b.pairings.get(pairingTopic); + } + + if (pairingA == null) { + throw Exception('expect pairing A to be defined'); + } + + // Assign session now that we have paired + // print('Waiting for connect response'); + try { + if (!connectResponse.session.isCompleted) { + // print('Waiting for connect response'); + await connectResponse.session.future; + } + await sessionBCompleter.future; + } catch (e) { + b.onSessionProposal.unsubscribe(f); + expect(e is JsonRpcError, true); + final e2 = e as JsonRpcError; + expect(e2.code, Errors.getSdkError(Errors.USER_REJECTED).code); + expect(e2.message, Errors.getSdkError(Errors.USER_REJECTED).message); + } + + // expect(true, false); + } +} diff --git a/packages/reown_sign/test/tests/sign_common.dart b/packages/reown_sign/test/tests/sign_common.dart new file mode 100644 index 0000000..ed28972 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_common.dart @@ -0,0 +1,210 @@ +@Timeout(Duration(seconds: 45)) + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/models/basic_models.dart'; + +import '../shared/shared_test_values.dart'; +import 'sign_approve_session.dart'; +import '../utils/sign_client_constants.dart'; +import 'sign_approve_session_authenticate.dart'; +import 'sign_authenticate.dart'; +import 'sign_connect.dart'; +import 'sign_disconnect.dart'; +import 'sign_emit_session_event.dart'; +import 'sign_expiration.dart'; +import 'sign_extend_session.dart'; +import 'sign_happy_path.dart'; +import 'sign_pair.dart'; +import 'sign_ping.dart'; +import 'sign_reject_session.dart'; +import 'sign_request_and_handler.dart'; +import 'sign_update_session.dart'; + +void signEngineTests({ + required String context, + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group(context, () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + signExpiration( + clientACreator: clientACreator, + ); + + signHappyPath( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signConnect( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signAuthenticate( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signPair( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signApproveSession( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signApproveSessionAuthenticate( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signRejectSession( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signUpdateSession( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signExtendSession( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signRequestAndHandler( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signEmitSessionEvent( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signPing( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + signDisconnect( + clientACreator: clientACreator, + clientBCreator: clientBCreator, + ); + + group('find', () { + test('works', () async { + await clientB.sessions.set( + TEST_SESSION_VALID_TOPIC, + testSessionValid, + ); + + final sessionData = clientB.find( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + ); + expect(sessionData != null, true); + expect(sessionData!.topic, TEST_SESSION_VALID_TOPIC); + + final sessionData2 = clientB.find( + requiredNamespaces: TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1, + ); + expect(sessionData2, null); + }); + }); + + group('pairings', () { + test('works', () async { + expect(clientA.pairings, clientA.core.pairing.getStore()); + expect(clientB.pairings, clientB.core.pairing.getStore()); + }); + }); + + group('registerEventEmitter', () { + test('fails properly', () { + expect( + () => clientB.registerEventEmitter( + chainId: TEST_CHAIN_INVALID_1, + event: TEST_EVENT_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. registerEventEmitter, chain $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId" format', + ), + ), + ); + + expect( + () => clientB.registerEventEmitter( + chainId: TEST_ETHEREUM_CHAIN, + event: TEST_ACCOUNT_INVALID_2, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported accounts. registerEventEmitter, account $TEST_ETHEREUM_CHAIN:$TEST_ACCOUNT_INVALID_2 should conform to "namespace:chainId:address" format', + ), + ), + ); + }); + }); + + group('registerAccounts', () { + test('fails properly', () { + expect( + () => clientB.registerAccount( + chainId: TEST_CHAIN_INVALID_1, + accountAddress: TEST_ETHEREUM_ACCOUNT, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. registerAccount, chain $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId" format', + ), + ), + ); + + expect( + () => clientB.registerAccount( + chainId: TEST_ETHEREUM_CHAIN, + accountAddress: TEST_ACCOUNT_INVALID_2, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported accounts. registerAccount, account $TEST_ETHEREUM_CHAIN:$TEST_ACCOUNT_INVALID_2 should conform to "namespace:chainId:address" format', + ), + ), + ); + }); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_connect.dart b/packages/reown_sign/test/tests/sign_connect.dart new file mode 100644 index 0000000..651a636 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_connect.dart @@ -0,0 +1,111 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/engine_constants.dart'; +import '../utils/sign_client_constants.dart'; + +void signConnect({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('happy path', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('Initializes', () async { + expect(clientA.core.pairing.getPairings().length, 0); + expect(clientB.core.pairing.getPairings().length, 0); + }); + + test('creates correct URI', () async { + ConnectResponse response = await clientA.connect( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + ); + + expect(response.uri != null, true); + URIParseResult parsed = ReownCoreUtils.parseUri(response.uri!); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v2); + expect(parsed.topic, response.pairingTopic); + expect(parsed.v2Data!.relay.protocol, 'irn'); + expect(parsed.v2Data!.methods.length, 2); + expect(parsed.v2Data!.methods[0], MethodConstants.WC_SESSION_PROPOSE); + expect(parsed.v2Data!.methods[1], MethodConstants.WC_SESSION_REQUEST); + + response = await clientA.connect( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + methods: [], + ); + + expect(response.uri != null, true); + parsed = ReownCoreUtils.parseUri(response.uri!); + expect(parsed.protocol, 'wc'); + expect(parsed.version, URIVersion.v2); + expect(parsed.v2Data!.relay.protocol, 'irn'); + expect(parsed.v2Data!.methods.length, 0); + }); + + test('invalid topic', () { + expect( + () async => await clientA.connect( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + pairingTopic: TEST_TOPIC_INVALID, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. pairing topic doesn\'t exist: abc', + ), + ), + ); + }); + + test('invalid required and optional namespaces', () { + expect( + () async => await clientA.connect( + requiredNamespaces: TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. connect() check requiredNamespaces. requiredNamespace, namespace is a chainId, but chains is not empty', + ), + ), + ); + expect( + () async => await clientA.connect( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + optionalNamespaces: TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. connect() check optionalNamespaces. requiredNamespace, namespace is a chainId, but chains is not empty', + ), + ), + ); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_disconnect.dart b/packages/reown_sign/test/tests/sign_disconnect.dart new file mode 100644 index 0000000..5338201 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_disconnect.dart @@ -0,0 +1,301 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/sign_client_constants.dart'; +import 'sign_client_helpers.dart'; + +void signDisconnect({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('disconnect', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('using pairing works', () async { + TestConnectMethodReturn connectionInfo = + await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + String pairingATopic = connectionInfo.pairing.topic; + String sessionATopic = connectionInfo.session.topic; + + // Create another proposal that we will check is deleted on disconnect + await clientA.connect( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + pairingTopic: pairingATopic, + ); + expect(clientA.getPendingSessionProposals().length, 1); + + Completer completerA = Completer(); + Completer completerB = Completer(); + Completer completerSessionA = Completer(); + Completer completerSessionB = Completer(); + int counterA = 0; + int counterB = 0; + int counterSessionA = 0; + int counterSessionB = 0; + clientA.core.pairing.onPairingDelete.subscribe((PairingEvent? e) { + expect(e != null, true); + expect(e!.topic, pairingATopic); + counterA++; + completerA.complete(); + }); + clientB.core.pairing.onPairingDelete.subscribe((PairingEvent? e) { + expect(e != null, true); + expect(e!.topic, pairingATopic); + counterB++; + completerB.complete(); + }); + clientA.onSessionDelete.subscribe((SessionDelete? e) { + expect(e != null, true); + expect(e!.topic, sessionATopic); + counterSessionA++; + completerSessionA.complete(); + }); + clientB.onSessionDelete.subscribe((SessionDelete? e) { + expect(e != null, true); + expect(e!.topic, sessionATopic); + counterSessionB++; + completerSessionB.complete(); + }); + + await clientA.disconnectSession( + topic: pairingATopic, + reason: ReownSignError.fromJson( + Errors.getSdkError(Errors.USER_DISCONNECTED).toJson(), + ), + ); + + // await Future.delayed(Duration(milliseconds: 150)); + await completerA.future; + await completerB.future; + await completerSessionA.future; + await completerSessionB.future; + + expect(clientA.pairings.get(pairingATopic), null); + expect(clientB.pairings.get(pairingATopic), null); + expect(clientA.sessions.get(sessionATopic), null); + expect(clientB.sessions.get(sessionATopic), null); + expect(clientA.getPendingSessionProposals().length, 0); + + expect(counterA, 1); + expect(counterB, 1); + expect(counterSessionA, 1); + expect(counterSessionB, 1); + + completerA = Completer(); + completerB = Completer(); + completerSessionA = Completer(); + completerSessionB = Completer(); + + connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + pairingATopic = connectionInfo.pairing.topic; + sessionATopic = connectionInfo.session.topic; + + await clientB.disconnectSession( + topic: pairingATopic, + reason: ReownSignError.fromJson( + Errors.getSdkError(Errors.USER_DISCONNECTED).toJson(), + ), + ); + + // await Future.delayed(Duration(milliseconds: 150)); + await completerA.future; + await completerB.future; + await completerSessionA.future; + await completerSessionB.future; + + expect(clientA.pairings.get(pairingATopic), null); + expect(clientB.pairings.get(pairingATopic), null); + expect(clientA.sessions.get(sessionATopic), null); + expect(clientB.sessions.get(sessionATopic), null); + + expect(counterA, 2); + expect(counterB, 2); + expect(counterSessionA, 2); + expect(counterSessionB, 2); + + clientA.core.pairing.onPairingDelete.unsubscribeAll(); + clientB.core.pairing.onPairingDelete.unsubscribeAll(); + clientA.onSessionDelete.unsubscribeAll(); + clientB.onSessionDelete.unsubscribeAll(); + }); + + test('using session works', () async { + TestConnectMethodReturn connectionInfo = + await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + String sessionATopic = connectionInfo.session.topic; + + Completer completerA = Completer(); + Completer completerB = Completer(); + int counterA = 0; + int counterB = 0; + clientA.onSessionDelete.subscribe((SessionDelete? e) { + expect(e != null, true); + expect(e!.topic, sessionATopic); + counterA++; + completerA.complete(); + }); + clientB.onSessionDelete.subscribe((SessionDelete? e) { + expect(e != null, true); + expect(e!.topic, sessionATopic); + counterB++; + completerB.complete(); + }); + + await clientA.disconnectSession( + topic: sessionATopic, + reason: ReownSignError.fromJson( + Errors.getSdkError(Errors.USER_DISCONNECTED).toJson(), + ), + ); + + // await Future.delayed(Duration(milliseconds: 250)); + await completerA.future; + await completerB.future; + + expect(clientA.sessions.get(sessionATopic), null); + expect(clientB.sessions.get(sessionATopic), null); + + expect(counterA, 1); + expect(counterB, 1); + + completerA = Completer(); + completerB = Completer(); + + connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + sessionATopic = connectionInfo.session.topic; + + await clientB.disconnectSession( + topic: sessionATopic, + reason: ReownSignError.fromJson( + Errors.getSdkError(Errors.USER_DISCONNECTED).toJson(), + ), + ); + + await completerA.future; + + expect(clientA.pairings.get(sessionATopic), null); + expect(clientB.pairings.get(sessionATopic), null); + + expect(counterA, 2); + expect(counterB, 2); + + clientA.onSessionDelete.unsubscribeAll(); + clientB.onSessionDelete.unsubscribeAll(); + }); + + for (var client in clients) { + setUp(() async { + await client.sessions.set( + TEST_SESSION_VALID_TOPIC, + testSessionValid, + ); + await client.sessions.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired, + ); + await clientA.core.expirer.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired.expiry, + ); + }); + + test('invalid topic', () async { + final reason = Errors.getSdkError(Errors.USER_DISCONNECTED); + expect( + () async => await client.disconnectSession( + topic: TEST_SESSION_INVALID_TOPIC, + reason: ReownSignError( + code: reason.code, + message: reason.message, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. session or pairing topic doesn\'t exist: $TEST_SESSION_INVALID_TOPIC', + ), + ), + ); + + int counter = 0; + Completer completer = Completer(); + client.core.expirer.onExpire.subscribe((e) { + counter++; + completer.complete(); + }); + int counterSession = 0; + Completer completerSession = Completer(); + client.onSessionExpire.subscribe((args) { + counterSession++; + completerSession.complete(); + }); + expect( + () async => await client.disconnectSession( + topic: TEST_SESSION_EXPIRED_TOPIC, + reason: ReownSignError( + code: reason.code, + message: reason.message, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expired. session topic: $TEST_SESSION_EXPIRED_TOPIC', + ), + ), + ); + + // await Future.delayed(Duration(milliseconds: 150)); + await completer.future; + await completerSession.future; + + expect( + client.sessions.has( + TEST_SESSION_EXPIRED_TOPIC, + ), + false, + ); + expect(counter, 1); + expect(counterSession, 1); + client.core.expirer.onExpire.unsubscribeAll(); + client.onSessionExpire.unsubscribeAll(); + }); + } + }); +} diff --git a/packages/reown_sign/test/tests/sign_emit_session_event.dart b/packages/reown_sign/test/tests/sign_emit_session_event.dart new file mode 100644 index 0000000..cff0c95 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_emit_session_event.dart @@ -0,0 +1,219 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/sign_client_constants.dart'; +import 'sign_client_helpers.dart'; + +void signEmitSessionEvent({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('emitSessionEvent and handler', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + + await clientB.sessions.set( + TEST_SESSION_VALID_TOPIC, + testSessionValid, + ); + await clientB.sessions.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired, + ); + await clientB.core.expirer.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired.expiry, + ); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('register an event handler and recieve events with it', () async { + final connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + final sessionTopic = connectionInfo.session.topic; + + try { + await clientB.emitSessionEvent( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + event: const SessionEventParams( + name: TEST_EVENT_1, + data: TEST_MESSAGE_1, + ), + ); + } on JsonRpcError catch (e) { + expect( + e.toString(), + JsonRpcError.methodNotFound( + 'No handler found for chainId:event -> $TEST_ETHEREUM_CHAIN:$TEST_EVENT_1', + ).toString(), + ); + } + + final completer = Completer(); + clientA.onSessionEvent.subscribe((SessionEvent? session) { + expect(session != null, true); + expect(session!.topic, sessionTopic); + expect(session.data, TEST_MESSAGE_1); + completer.complete(); + }); + + requestHandler(topic, request) async { + expect(topic, sessionTopic); + expect(request, TEST_MESSAGE_1); + + // Events return no responses + } + + clientA.registerEventHandler( + chainId: TEST_ETHEREUM_CHAIN, + event: TEST_EVENT_1, + handler: requestHandler, + ); + + try { + await clientB.emitSessionEvent( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + event: const SessionEventParams( + name: TEST_EVENT_1, + data: TEST_MESSAGE_1, + ), + ); + + // Events receive no responses + } on JsonRpcError catch (_) { + // print(e); + expect(false, true); + } + + // Wait a second for the event to fire + await completer.future; + + clientA.onSessionEvent.unsubscribeAll(); + }); + + test('invalid session topic', () async { + expect( + () async => await clientB.emitSessionEvent( + topic: TEST_SESSION_INVALID_TOPIC, + chainId: TEST_ETHEREUM_CHAIN, + event: const SessionEventParams( + name: TEST_EVENT_1, + data: TEST_MESSAGE_1, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. session topic doesn\'t exist: $TEST_SESSION_INVALID_TOPIC', + ), + ), + ); + + int counter = 0; + Completer completer = Completer(); + clientB.core.expirer.onExpire.subscribe((args) { + counter++; + completer.complete(); + }); + int counterSession = 0; + Completer completerSession = Completer(); + clientB.onSessionExpire.subscribe((args) { + counterSession++; + completerSession.complete(); + }); + expect( + () async => await clientB.emitSessionEvent( + topic: TEST_SESSION_EXPIRED_TOPIC, + chainId: TEST_ETHEREUM_CHAIN, + event: const SessionEventParams( + name: TEST_EVENT_1, + data: TEST_MESSAGE_1, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expired. session topic: $TEST_SESSION_EXPIRED_TOPIC', + ), + ), + ); + + // await Future.delayed(Duration(milliseconds: 150)); + await completer.future; + await completerSession.future; + + expect( + clientB.sessions.has( + TEST_SESSION_EXPIRED_TOPIC, + ), + false, + ); + expect(counter, 1); + expect(counterSession, 1); + clientB.core.expirer.onExpire.unsubscribeAll(); + clientB.onSessionExpire.unsubscribeAll(); + }); + + test('invalid chains or events', () async { + expect( + () async => await clientB.emitSessionEvent( + topic: TEST_SESSION_VALID_TOPIC, + chainId: TEST_UNINCLUDED_CHAIN, + event: const SessionEventParams( + name: TEST_EVENT_1, + data: TEST_MESSAGE_1, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. The chain $TEST_UNINCLUDED_CHAIN is not supported', + ), + ), + ); + expect( + () async => await clientB.emitSessionEvent( + topic: TEST_SESSION_VALID_TOPIC, + chainId: TEST_ETHEREUM_CHAIN, + event: const SessionEventParams( + name: TEST_EVENT_INVALID_1, + data: TEST_MESSAGE_1, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported events. The event $TEST_EVENT_INVALID_1 is not supported', + ), + ), + ); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_expiration.dart b/packages/reown_sign/test/tests/sign_expiration.dart new file mode 100644 index 0000000..d63a59e --- /dev/null +++ b/packages/reown_sign/test/tests/sign_expiration.dart @@ -0,0 +1,72 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/engine_constants.dart'; +import '../utils/sign_client_constants.dart'; + +void signExpiration({ + required Future Function(PairingMetadata) clientACreator, +}) { + group('expiration', () { + late IReownSignDapp clientA; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + }); + + tearDown(() async { + await clientA.core.relayClient.disconnect(); + }); + + test('deletes session', () async { + int counter = 0; + final completer = Completer.sync(); + clientA.onSessionExpire.subscribe((args) { + counter++; + completer.complete(); + }); + + clientA.sessions.set(TEST_SESSION_TOPIC, testSessionExpired); + clientA.core.expirer.set( + TEST_SESSION_TOPIC.toString(), + testSessionExpired.expiry, + ); + + clientA.core.expirer.expire(TEST_SESSION_TOPIC); + + // await Future.delayed(Duration(milliseconds: 150)); + await completer.future; + + expect(clientA.sessions.has(TEST_SESSION_TOPIC), false); + expect(counter, 1); + }); + + test('deletes proposal', () async { + await clientA.proposals.set( + TEST_PROPOSAL_EXPIRED_ID.toString(), + TEST_PROPOSAL_EXPIRED, + ); + await clientA.core.expirer.set( + TEST_PROPOSAL_EXPIRED_ID.toString(), + TEST_PROPOSAL_EXPIRED.expiry, + ); + + await clientA.core.expirer.expire( + TEST_PROPOSAL_EXPIRED_ID.toString(), + ); + + // await Future.delayed(Duration(milliseconds: 150)); + + expect( + clientA.proposals.has( + TEST_PROPOSAL_EXPIRED_ID.toString(), + ), + false, + ); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_extend_session.dart b/packages/reown_sign/test/tests/sign_extend_session.dart new file mode 100644 index 0000000..45ab7ba --- /dev/null +++ b/packages/reown_sign/test/tests/sign_extend_session.dart @@ -0,0 +1,168 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/models/basic_models.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/sign_client_constants.dart'; +import 'sign_client_helpers.dart'; + +void signExtendSession({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('extendSession', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + + await clientB.sessions.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired, + ); + await clientB.core.expirer.set( + TEST_SESSION_EXPIRED_TOPIC.toString(), + testSessionExpired.expiry, + ); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('works', () async { + final connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + + final startingExpiryA = + clientA.sessions.get(connectionInfo.session.topic)!.expiry; + final startingExpiryB = + clientB.sessions.get(connectionInfo.session.topic)!.expiry; + // TODO: Figure out why the expirer and session expiry are not the same + // expect( + // clientA.core.expirer.get(connectionInfo.session.topic) == + // startingExpiryA, + // true, + // ); + // expect( + // clientB.core.expirer.get(connectionInfo.session.topic) == + // startingExpiryB, + // true, + // ); + + int counter = 0; + Completer completer = Completer(); + clientA.onSessionExtend.subscribe((args) { + counter++; + completer.complete(); + }); + + // This is used to ensure that new expiry is different from the old one + const offset = 100; + await Future.delayed(const Duration(milliseconds: offset)); + + await clientB.extendSession( + topic: connectionInfo.session.topic, + ); + + // await Future.delayed(Duration(milliseconds: 100)); + + if (!completer.isCompleted) { + await completer.future; + } + + final endingExpiryA = + clientA.sessions.get(connectionInfo.session.topic)!.expiry; + final endingExpiryB = + clientB.sessions.get(connectionInfo.session.topic)!.expiry; + + expect( + endingExpiryA >= startingExpiryA, + true, + ); + expect( + endingExpiryB >= startingExpiryB, + true, + ); + expect( + clientA.core.expirer.get(connectionInfo.session.topic) == endingExpiryA, + true, + ); + expect( + clientB.core.expirer.get(connectionInfo.session.topic) == endingExpiryB, + true, + ); + expect(counter, 1); + + clientA.onSessionExtend.unsubscribeAll(); + }); + test('invalid session topic', () async { + expect( + () async => await clientB.extendSession( + topic: TEST_SESSION_INVALID_TOPIC, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. session topic doesn\'t exist: $TEST_SESSION_INVALID_TOPIC', + ), + ), + ); + + int counter = 0; + Completer completer = Completer(); + clientB.core.expirer.onExpire.subscribe((args) { + counter++; + completer.complete(); + }); + int counterSession = 0; + Completer completerSession = Completer(); + clientB.onSessionExpire.subscribe((args) { + counterSession++; + completerSession.complete(); + }); + expect( + () async => await clientB.extendSession( + topic: TEST_SESSION_EXPIRED_TOPIC, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expired. session topic: $TEST_SESSION_EXPIRED_TOPIC', + ), + ), + ); + + // await Future.delayed(Duration(milliseconds: 150)); + await completer.future; + await completerSession.future; + + expect( + clientB.sessions.has( + TEST_SESSION_EXPIRED_TOPIC, + ), + false, + ); + expect(counter, 1); + expect(counterSession, 1); + clientB.core.expirer.onExpire.unsubscribeAll(); + clientB.onSessionExpire.unsubscribeAll(); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_happy_path.dart b/packages/reown_sign/test/tests/sign_happy_path.dart new file mode 100644 index 0000000..7efa574 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_happy_path.dart @@ -0,0 +1,131 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; + +import '../shared/shared_test_values.dart'; +import 'sign_client_helpers.dart'; + +void signHappyPath({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('happy path', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('Initializes', () async { + expect(clientA.core.pairing.getPairings().length, 0); + expect(clientB.core.pairing.getPairings().length, 0); + }); + + test('connects, reconnects, and emits proper events', () async { + Completer completerA = Completer(); + Completer completerB = Completer(); + int counterA = 0; + int counterB = 0; + clientA.onSessionConnect.subscribe((args) { + counterA++; + completerA.complete(); + }); + clientB.onSessionProposal.subscribe((args) { + counterB++; + completerB.complete(); + }); + + final connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + + if (!completerA.isCompleted) { + await completerA.future; + } + if (!completerB.isCompleted) { + await completerB.future; + } + + completerA = Completer(); + completerB = Completer(); + + expect(counterA, 1); + expect(counterB, 1); + + expect( + clientA.pairings.getAll().length, + clientB.pairings.getAll().length, + ); + expect(clientA.getActiveSessions().length, 1); + expect(clientB.getActiveSessions().length, 1); + expect( + clientA + .getSessionsForPairing( + pairingTopic: connectionInfo.pairing.topic, + ) + .length, + 1, + ); + expect( + clientB + .getSessionsForPairing( + pairingTopic: connectionInfo.pairing.topic, + ) + .length, + 1, + ); + final _ = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + pairingTopic: connectionInfo.pairing.topic, + ); + + if (!completerA.isCompleted) { + await completerA.future; + } + if (!completerB.isCompleted) { + await completerB.future; + } + + expect(counterA, 2); + expect(counterB, 2); + + clientA.onSessionConnect.unsubscribeAll(); + clientB.onSessionProposal.unsubscribeAll(); + }); + + test('connects, and reconnects with scan latency', () async { + final connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + qrCodeScanLatencyMs: 1000, + ); + expect( + clientA.pairings.getAll().length, + clientB.pairings.getAll().length, + ); + final _ = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + pairingTopic: connectionInfo.pairing.topic, + qrCodeScanLatencyMs: 1000, + ); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_pair.dart b/packages/reown_sign/test/tests/sign_pair.dart new file mode 100644 index 0000000..6c6e605 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_pair.dart @@ -0,0 +1,62 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/models/basic_models.dart'; + +import '../shared/shared_test_values.dart'; + +void signPair({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('pair', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('throws with v1 url', () { + const String uri = TEST_URI_V1; + + expect( + () async => await clientB.pair(uri: Uri.parse(uri)), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Missing or invalid. URI is not WalletConnect version 2 URI', + ), + ), + ); + }); + + test('throws with invalid methods', () { + const String uriWithMethods = '$TEST_URI&methods=[wc_swag]'; + + expect( + () async => await clientB.pair(uri: Uri.parse(uriWithMethods)), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported wc_ method. The following methods are not registered: wc_swag.', + ), + ), + ); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_ping.dart b/packages/reown_sign/test/tests/sign_ping.dart new file mode 100644 index 0000000..1667a66 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_ping.dart @@ -0,0 +1,150 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/sign_client_constants.dart'; +import 'sign_client_helpers.dart'; + +void signPing({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('ping', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + + await clientA.sessions.set( + TEST_SESSION_VALID_TOPIC, + testSessionValid, + ); + await clientA.sessions.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired, + ); + await clientA.core.expirer.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired.expiry, + ); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('works from pairing and session', () async { + final connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + final sessionTopic = connectionInfo.session.topic; + final pairingTopic = connectionInfo.pairing.topic; + + Completer completerA = Completer(); + Completer completerB = Completer(); + int counterAP = 0; + int counterBP = 0; + clientB.onSessionPing.subscribe((SessionPing? ping) { + expect(ping != null, true); + expect(ping!.topic, sessionTopic); + counterAP++; + completerA.complete(); + }); + clientB.core.pairing.onPairingPing.subscribe((PairingEvent? pairing) { + expect(pairing != null, true); + expect(pairing!.topic, pairingTopic); + counterBP++; + completerB.complete(); + }); + + await clientA.ping(topic: sessionTopic); + await clientA.ping(topic: pairingTopic); + + if (!completerA.isCompleted) { + await completerA.future; + } + if (!completerB.isCompleted) { + await completerB.future; + } + + expect(counterAP, 1); + expect(counterBP, 1); + + clientA.onSessionPing.unsubscribeAll(); + clientA.core.pairing.onPairingPing.unsubscribeAll(); + clientB.core.pairing.onPairingPing.unsubscribeAll(); + }); + + test('invalid topic', () async { + expect( + () async => await clientA.ping( + topic: TEST_SESSION_INVALID_TOPIC, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. session or pairing topic doesn\'t exist: $TEST_SESSION_INVALID_TOPIC', + ), + ), + ); + + int counter = 0; + Completer completer = Completer(); + clientA.core.expirer.onExpire.subscribe((args) { + counter++; + completer.complete(); + }); + int counterSession = 0; + Completer completerSession = Completer(); + clientA.onSessionExpire.subscribe((args) { + counterSession++; + completerSession.complete(); + }); + expect( + () async => await clientA.ping( + topic: TEST_SESSION_EXPIRED_TOPIC, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expired. session topic: $TEST_SESSION_EXPIRED_TOPIC', + ), + ), + ); + + if (!completer.isCompleted) { + await completer.future; + } + if (!completerSession.isCompleted) { + await completerSession.future; + } + + expect( + clientA.sessions.has( + TEST_SESSION_EXPIRED_TOPIC, + ), + false, + ); + expect(counter, 1); + expect(counterSession, 1); + clientA.core.expirer.onExpire.unsubscribeAll(); + clientA.onSessionExpire.unsubscribeAll(); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_reject_session.dart b/packages/reown_sign/test/tests/sign_reject_session.dart new file mode 100644 index 0000000..cf089a5 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_reject_session.dart @@ -0,0 +1,135 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/models/basic_models.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/engine_constants.dart'; +import 'sign_client_helpers.dart'; + +void signRejectSession({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('rejectSession', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + + await clientB.proposals.set( + TEST_PROPOSAL_VALID_ID.toString(), + TEST_PROPOSAL_VALID, + ); + await clientB.proposals.set( + TEST_PROPOSAL_EXPIRED_ID.toString(), + TEST_PROPOSAL_EXPIRED, + ); + await clientB.core.expirer.set( + TEST_PROPOSAL_EXPIRED_ID.toString(), + TEST_PROPOSAL_EXPIRED.expiry, + ); + await clientB.proposals.set( + TEST_PROPOSAL_INVALID_REQUIRED_NAMESPACES_ID.toString(), + TEST_PROPOSAL_INVALID_REQUIRED_NAMESPACES, + ); + await clientB.proposals.set( + TEST_PROPOSAL_INVALID_OPTIONAL_NAMESPACES_ID.toString(), + TEST_PROPOSAL_INVALID_OPTIONAL_NAMESPACES, + ); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('throws catchable error properly', () async { + await SignClientHelpers.testConnectPairReject( + clientA, + clientB, + ); + }); + + test('deletes the proposal', () async { + await clientB.rejectSession( + id: TEST_PROPOSAL_VALID_ID, + reason: const ReownSignError(code: -1, message: 'reason'), + ); + + expect( + clientB.proposals.has( + TEST_PROPOSAL_VALID_ID.toString(), + ), + false, + ); + }); + + test('invalid proposal id', () async { + expect( + () async => await clientB.rejectSession( + id: TEST_APPROVE_ID_INVALID, + reason: const ReownSignError(code: -1, message: 'reason'), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. proposal id doesn\'t exist: $TEST_APPROVE_ID_INVALID', + ), + ), + ); + + int counter = 0; + Completer completer = Completer(); + clientB.core.expirer.onExpire.subscribe((args) { + counter++; + completer.complete(); + }); + int counter2 = 0; + Completer completer2 = Completer(); + clientB.onProposalExpire.subscribe((args) { + counter2++; + completer2.complete(); + }); + expect( + () async => await clientB.rejectSession( + id: TEST_PROPOSAL_EXPIRED_ID, + reason: const ReownSignError(code: -1, message: 'reason'), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expired. proposal id: $TEST_PROPOSAL_EXPIRED_ID', + ), + ), + ); + + // await Future.delayed(Duration(milliseconds: 150)); + await completer.future; + await completer2.future; + + expect( + clientB.proposals.has( + TEST_PROPOSAL_EXPIRED_ID.toString(), + ), + false, + ); + expect(counter, 1); + expect(counter2, 1); + clientB.core.expirer.onExpire.unsubscribeAll(); + clientB.onProposalExpire.unsubscribeAll(); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_request_and_handler.dart b/packages/reown_sign/test/tests/sign_request_and_handler.dart new file mode 100644 index 0000000..3dc9617 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_request_and_handler.dart @@ -0,0 +1,461 @@ +// ignore_for_file: avoid_print + +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/sign_client_constants.dart'; +import 'sign_client_helpers.dart'; + +void signRequestAndHandler({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('request and handler', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + + await clientA.sessions.set( + TEST_SESSION_VALID_TOPIC, + testSessionValid, + ); + await clientA.sessions.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired, + ); + await clientA.core.expirer.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired.expiry, + ); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('register a request handler and receive method calls with it', + () async { + final connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + ); + final sessionTopic = connectionInfo.session.topic; + + // No handler + clientA.core.logger.i('No handler'); + try { + final _ = await clientA.request( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: 'nonexistant', + params: TEST_MESSAGE_1, + ), + ); + } on ReownSignError catch (e) { + expect( + e.toString(), + 'ReownSignError(code: 5101, message: Unsupported methods. The method nonexistant is not supported, data: null)', + ); + } + expect(clientB.getPendingSessionRequests().length, 0); + + // Valid handler + Future Function(String, dynamic) requestHandler = ( + String topic, + dynamic request, + ) async { + expect(topic, sessionTopic); + // expect(request, TEST_MESSAGE_1); + // print(clientB.getPendingSessionRequests()); + expect(clientB.getPendingSessionRequests().length, 1); + final pRequest = clientB.pendingRequests.getAll().last; + return await clientB.respondSessionRequest( + topic: sessionTopic, + response: JsonRpcResponse( + id: pRequest.id, + result: request, + ), + ); + }; + clientB.registerRequestHandler( + chainId: TEST_ETHEREUM_CHAIN, + method: TEST_METHOD_1, + handler: requestHandler, + ); + + Completer clientBReady = Completer(); + clientB.pendingRequests.onSync.subscribe((args) { + if (clientB.getPendingSessionRequests().isEmpty && + !clientBReady.isCompleted) { + clientBReady.complete(); + } + }); + + try { + clientA.core.logger.i('Request handler 1'); + final Map response = await clientA.request( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: TEST_MESSAGE_1, + ), + ); + + expect(response, TEST_MESSAGE_1); + + clientA.core.logger.i('Request handler 1, waiting for clientBReady'); + if (clientBReady.isCompleted) { + clientBReady = Completer(); + } else { + await clientBReady.future; + clientBReady = Completer(); + } + + // print('swag 3'); + clientA.core.logger.i('Request handler 2'); + final String response2 = await clientA.request( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: TEST_MESSAGE_2, + ), + ); + + expect(response2, TEST_MESSAGE_2); + + clientA.core.logger.i('Request handler 2, waiting for clientBReady'); + if (!clientBReady.isCompleted) { + await clientBReady.future; + } + + clientB.sessions.onSync.unsubscribeAll(); + } on JsonRpcError catch (e) { + print(e); + expect(false, true); + } + + // Valid handler that throws an error + requestHandler = ( + String topic, + dynamic request, + ) async { + expect(topic, sessionTopic); + expect(clientB.getPendingSessionRequests().length, 1); + if (request == 'silent') { + throw ReownSignErrorSilent(); + } + final pRequest = clientB.pendingRequests.getAll().last; + late dynamic error = (request is String) + ? Errors.getSdkError(Errors.USER_REJECTED_SIGN) + : JsonRpcError.invalidParams('swag'); + return await clientB.respondSessionRequest( + topic: sessionTopic, + response: JsonRpcResponse( + id: pRequest.id, + error: JsonRpcError(code: error.code, message: error.message), + ), + ); + }; + clientB.registerRequestHandler( + chainId: TEST_ETHEREUM_CHAIN, + method: TEST_METHOD_1, + handler: requestHandler, + ); + + try { + // print('user rejected sign'); + await clientA.request( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: TEST_MESSAGE_2, + ), + ); + + expect(true, false); + } on JsonRpcError catch (e) { + // print('user rejected sign error received'); + expect( + e.code, + Errors.getSdkError(Errors.USER_REJECTED_SIGN).code, + ); + expect( + e.message, + Errors.getSdkError(Errors.USER_REJECTED_SIGN).message, + ); + } + + Completer pendingRequestCompleter = Completer(); + clientB.pendingRequests.onSync.subscribe((_) { + if (clientB.getPendingSessionRequests().isEmpty) { + pendingRequestCompleter.complete(); + } + }); + if (!pendingRequestCompleter.isCompleted) { + clientA.core.logger.i('waiting pendingRequestCompleter'); + await pendingRequestCompleter.future; + } + clientB.pendingRequests.onSync.unsubscribeAll(); + + try { + await clientA.request( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: {'test': 'swag'}, + ), + ); + + expect(true, false); + } on JsonRpcError catch (e) { + expect( + e.code, + JsonRpcError.invalidParams('swag').code, + ); + } + + pendingRequestCompleter = Completer(); + clientB.pendingRequests.onSync.subscribe((_) { + if (clientB.getPendingSessionRequests().isEmpty) { + pendingRequestCompleter.complete(); + } + }); + if (!pendingRequestCompleter.isCompleted) { + clientA.core.logger.i('waiting pendingRequestCompleter'); + await pendingRequestCompleter.future; + } + clientB.pendingRequests.onSync.unsubscribeAll(); + + clientB.registerRequestHandler( + chainId: TEST_ETHEREUM_CHAIN, + method: TEST_METHOD_1, + ); + clientB.registerRequestHandler( + chainId: TEST_ETHEREUM_CHAIN, + method: TEST_METHOD_2, + ); + clientB.onSessionRequest.subscribe(( + SessionRequestEvent? event, + ) async { + expect(event != null, true); + expect(event!.topic, sessionTopic); + expect(event.params, TEST_MESSAGE_1); + + if (event.method == TEST_METHOD_1) { + expect(clientB.pendingRequests.has(event.id.toString()), true); + expect(clientB.getPendingSessionRequests().length, 1); + + await clientB.respondSessionRequest( + topic: event.topic, + response: JsonRpcResponse>( + id: event.id, + result: TEST_MESSAGE_1, + ), + ); + + expect(clientB.pendingRequests.has(event.id.toString()), false); + } else if (event.method == TEST_METHOD_2) { + await clientB.respondSessionRequest( + topic: event.topic, + response: JsonRpcResponse( + id: event.id, + error: JsonRpcError.invalidParams(event.params.toString()), + ), + ); + } + }); + + try { + Map response = await clientA.request( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: TEST_MESSAGE_1, + ), + ); + + expect(response, TEST_MESSAGE_1); + + Map _ = await clientA.request( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_2, + params: TEST_MESSAGE_1, + ), + ); + + expect(true, false); + } on JsonRpcError catch (e) { + // print(e); + expect( + e.code, + JsonRpcError.invalidParams('swag').code, + ); + expect(e.message!.contains(TEST_MESSAGE_1.toString()), true); + } + + // Try an error + clientB.onSessionRequest.unsubscribeAll(); + clientB.onSessionRequest.subscribe(( + SessionRequestEvent? event, + ) async { + expect(event != null, true); + expect(event!.topic, sessionTopic); + expect(event.params, TEST_MESSAGE_1); + + expect(clientB.pendingRequests.has(event.id.toString()), true); + + await clientB.respondSessionRequest( + topic: event.topic, + response: JsonRpcResponse( + id: event.id, + error: JsonRpcError.invalidParams('invalid'), + ), + ); + + expect(clientB.pendingRequests.has(event.id.toString()), false); + }); + + try { + final _ = await clientA.request( + topic: connectionInfo.session.topic, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: TEST_MESSAGE_1, + ), + ); + } on JsonRpcError catch (e) { + expect(e.message, 'invalid'); + } + + clientB.onSessionRequest.unsubscribeAll(); + }); + + test('invalid session topic', () async { + expect( + () async => await clientA.request( + topic: TEST_SESSION_INVALID_TOPIC, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: TEST_MESSAGE_1, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. session topic doesn\'t exist: $TEST_SESSION_INVALID_TOPIC', + ), + ), + ); + + int counter = 0; + Completer completer = Completer(); + clientA.core.expirer.onExpire.subscribe((args) { + counter++; + completer.complete(); + }); + int counterSession = 0; + Completer completerSession = Completer(); + clientA.onSessionExpire.subscribe((args) { + counterSession++; + completerSession.complete(); + }); + // print( + // 'clientA.session exiry: ${clientA.sessions.get(TEST_SESSION_EXPIRED_TOPIC)!.expiry}'); + expect( + () async => await clientA.request( + topic: TEST_SESSION_EXPIRED_TOPIC, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: TEST_MESSAGE_1, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expired. session topic: $TEST_SESSION_EXPIRED_TOPIC', + ), + ), + ); + + // await Future.delayed(Duration(milliseconds: 150)); + await completer.future; + await completerSession.future; + + expect( + clientA.sessions.has( + TEST_SESSION_EXPIRED_TOPIC, + ), + false, + ); + expect(counter, 1); + expect(counterSession, 1); + clientA.core.expirer.onExpire.unsubscribeAll(); + clientB.onSessionExpire.unsubscribeAll(); + }); + + test('invalid chains or methods', () async { + expect( + () async => await clientA.request( + topic: TEST_SESSION_VALID_TOPIC, + chainId: TEST_UNINCLUDED_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_1, + params: TEST_MESSAGE_1, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. The chain $TEST_UNINCLUDED_CHAIN is not supported', + ), + ), + ); + expect( + () async => await clientA.request( + topic: TEST_SESSION_VALID_TOPIC, + chainId: TEST_ETHEREUM_CHAIN, + request: const SessionRequestParams( + method: TEST_METHOD_INVALID_1, + params: TEST_MESSAGE_1, + ), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported methods. The method $TEST_METHOD_INVALID_1 is not supported', + ), + ), + ); + }); + }); +} diff --git a/packages/reown_sign/test/tests/sign_update_session.dart b/packages/reown_sign/test/tests/sign_update_session.dart new file mode 100644 index 0000000..f09d107 --- /dev/null +++ b/packages/reown_sign/test/tests/sign_update_session.dart @@ -0,0 +1,182 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/i_sign_common.dart'; +import 'package:reown_sign/i_sign_dapp.dart'; +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/models/basic_models.dart'; + +import '../shared/shared_test_values.dart'; +import '../utils/sign_client_constants.dart'; +import 'sign_client_helpers.dart'; + +void signUpdateSession({ + required Future Function(PairingMetadata) clientACreator, + required Future Function(PairingMetadata) clientBCreator, +}) { + group('updateSession', () { + late IReownSignDapp clientA; + late IReownSignWallet clientB; + List clients = []; + + setUp(() async { + clientA = await clientACreator(PROPOSER); + clientB = await clientBCreator(RESPONDER); + clients.add(clientA); + clients.add(clientB); + + await clientB.sessions.set( + TEST_SESSION_VALID_TOPIC, + testSessionValid, + ); + await clientB.sessions.set( + TEST_SESSION_EXPIRED_TOPIC, + testSessionExpired, + ); + await clientB.core.expirer.set( + TEST_SESSION_EXPIRED_TOPIC.toString(), + testSessionExpired.expiry, + ); + }); + + tearDown(() async { + clients.clear(); + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + test('works', () async { + final connectionInfo = await SignClientHelpers.testConnectPairApprove( + clientA, + clientB, + requiredNamespaces: { + EVM_NAMESPACE: TEST_ETH_ARB_REQUIRED_NAMESPACE, + }, + accounts: { + TEST_ETHEREUM_CHAIN: [TEST_ETHEREUM_ADDRESS], + TEST_ARBITRUM_CHAIN: [TEST_ETHEREUM_ADDRESS], + }, + methods: { + TEST_ETHEREUM_CHAIN: TEST_METHODS_1, + TEST_ARBITRUM_CHAIN: TEST_METHODS_1, + }, + events: { + TEST_ETHEREUM_CHAIN: [TEST_EVENT_1], + TEST_ARBITRUM_CHAIN: [TEST_EVENT_1], + TEST_AVALANCHE_CHAIN: [TEST_EVENT_2], + }, + ); + + int counter = 0; + Completer completer = Completer(); + clientA.onSessionUpdate.subscribe((args) { + counter++; + completer.complete(); + }); + + await clientB.updateSession( + topic: connectionInfo.session.topic, + namespaces: {EVM_NAMESPACE: TEST_ETH_ARB_NAMESPACE}, + ); + + // await Future.delayed(Duration(milliseconds: 100)); + await completer.future; + + final resultA = + clientA.sessions.get(connectionInfo.session.topic)!.namespaces; + final resultB = + clientB.sessions.get(connectionInfo.session.topic)!.namespaces; + expect(resultA, equals({EVM_NAMESPACE: TEST_ETH_ARB_NAMESPACE})); + expect(resultB, equals({EVM_NAMESPACE: TEST_ETH_ARB_NAMESPACE})); + expect(counter, 1); + + clientA.onSessionUpdate.unsubscribeAll(); + }); + + test('invalid session topic', () async { + expect( + () async => await clientB.updateSession( + topic: TEST_SESSION_INVALID_TOPIC, + namespaces: TEST_NAMESPACES, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'No matching key. session topic doesn\'t exist: $TEST_SESSION_INVALID_TOPIC', + ), + ), + ); + + int counterExpire = 0; + Completer completerExpire = Completer(); + clientB.core.expirer.onExpire.subscribe((args) { + counterExpire++; + completerExpire.complete(); + }); + int counterSession = 0; + Completer completerSession = Completer(); + clientB.onSessionExpire.subscribe((args) { + counterSession++; + completerSession.complete(); + }); + expect( + () async => await clientB.updateSession( + topic: TEST_SESSION_EXPIRED_TOPIC, + namespaces: TEST_NAMESPACES, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Expired. session topic: $TEST_SESSION_EXPIRED_TOPIC', + ), + ), + ); + // await Future.delayed(Duration(milliseconds: 150)); + await completerExpire.future; + await completerSession.future; + + expect( + clientB.sessions.has( + TEST_SESSION_EXPIRED_TOPIC, + ), + false, + ); + expect(counterExpire, 1); + expect(counterSession, 1); + clientB.core.expirer.onExpire.unsubscribeAll(); + clientB.onSessionExpire.unsubscribeAll(); + }); + + test('invalid namespaces', () async { + expect( + () async => await clientB.updateSession( + topic: TEST_SESSION_VALID_TOPIC, + namespaces: TEST_NAMESPACES_INVALID_ACCOUNTS, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported accounts. update() namespace, account swag should conform to "namespace:chainId:address" format', + ), + ), + ); + expect( + () async => await clientB.updateSession( + topic: TEST_SESSION_VALID_TOPIC, + namespaces: TEST_NAMESPACES_NONCONFORMING_CHAINS, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. update() namespaces chains don\'t satisfy requiredNamespaces chains for eip155. Requested: [eip155:1, eip155:42161], Supported: [eip155:1]', + ), + ), + ); + }); + }); +} diff --git a/packages/reown_sign/test/utils/address_utils_test.dart b/packages/reown_sign/test/utils/address_utils_test.dart new file mode 100644 index 0000000..fc9a92d --- /dev/null +++ b/packages/reown_sign/test/utils/address_utils_test.dart @@ -0,0 +1,25 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import '../shared/signature_constants.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('AddressUtils', () { + test('getDidAddress', () async { + expect( + AddressUtils.getDidAddress(TEST_ISSUER_EIP191), + TEST_ADDRESS_EIP191, + ); + }); + + test('getDidChainId', () async { + expect( + AddressUtils.getDidChainId(TEST_ISSUER_EIP191), + TEST_ETHEREUM_CHAIN.split(':')[1], + ); + }); + }); +} diff --git a/packages/reown_sign/test/utils/engine_constants.dart b/packages/reown_sign/test/utils/engine_constants.dart new file mode 100644 index 0000000..2593bbb --- /dev/null +++ b/packages/reown_sign/test/utils/engine_constants.dart @@ -0,0 +1,64 @@ +// Engine Data + +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; +import 'sign_client_constants.dart'; + +const TEST_TOPIC_INVALID = 'abc'; +const TEST_APPROVE_ID_INVALID = -1; + +const TEST_PUBLIC_KEY_A = '0x123'; +const TEST_PUBLIC_KEY_B = '0xxyz'; + +const TEST_CONNECTION_METADATA_A = ConnectionMetadata( + publicKey: TEST_PUBLIC_KEY_A, + metadata: PairingMetadata( + name: 'Test Name', + description: 'Test Description', + url: 'https://test.com', + icons: ['https://test.com/icon.png'], + ), +); + +const TEST_PROPOSAL_VALID_ID = 1; +const TEST_PROPOSAL_EXPIRED_ID = 50; +const TEST_PROPOSAL_INVALID_REQUIRED_NAMESPACES_ID = 100; +const TEST_PROPOSAL_INVALID_OPTIONAL_NAMESPACES_ID = 1000; +const TEST_PROPOSAL_VALID = ProposalData( + id: TEST_PROPOSAL_VALID_ID, + expiry: 1000000000000, + relays: [], + proposer: TEST_CONNECTION_METADATA_A, + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + optionalNamespaces: {}, + pairingTopic: TEST_PAIRING_TOPIC, +); +const TEST_PROPOSAL_EXPIRED = ProposalData( + id: TEST_PROPOSAL_EXPIRED_ID, + expiry: -1, + relays: [], + proposer: TEST_CONNECTION_METADATA_A, + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + optionalNamespaces: {}, + pairingTopic: TEST_PAIRING_TOPIC, +); +const TEST_PROPOSAL_INVALID_REQUIRED_NAMESPACES = ProposalData( + id: TEST_PROPOSAL_INVALID_REQUIRED_NAMESPACES_ID, + expiry: 1000000000000, + relays: [], + proposer: TEST_CONNECTION_METADATA_A, + requiredNamespaces: TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1, + optionalNamespaces: {}, + pairingTopic: TEST_PAIRING_TOPIC, +); +const TEST_PROPOSAL_INVALID_OPTIONAL_NAMESPACES = ProposalData( + id: TEST_PROPOSAL_INVALID_OPTIONAL_NAMESPACES_ID, + expiry: 1000000000000, + relays: [], + proposer: TEST_CONNECTION_METADATA_A, + requiredNamespaces: {}, + optionalNamespaces: TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1, + pairingTopic: TEST_PAIRING_TOPIC, +); diff --git a/packages/reown_sign/test/utils/namespace_utils_test.dart b/packages/reown_sign/test/utils/namespace_utils_test.dart new file mode 100644 index 0000000..cf620dc --- /dev/null +++ b/packages/reown_sign/test/utils/namespace_utils_test.dart @@ -0,0 +1,562 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; +import 'package:reown_sign/utils/sign_api_validator_utils.dart'; + +import '../shared/shared_test_values.dart'; +import 'sign_client_constants.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('NamespaceUtils', () { + test('isValidChainId', () { + expect( + NamespaceUtils.isValidChainId(TEST_ETHEREUM_CHAIN), + true, + ); + expect(NamespaceUtils.isValidChainId(TEST_CHAIN_INVALID_1), false); + expect(NamespaceUtils.isValidChainId(TEST_CHAIN_INVALID_2), false); + }); + + test('isValidAccount', () { + expect( + NamespaceUtils.isValidAccount( + TEST_ETHEREUM_ACCOUNT, + ), + true, + ); + expect(NamespaceUtils.isValidAccount(TEST_ACCOUNT_INVALID_1), false); + expect(NamespaceUtils.isValidAccount(TEST_ACCOUNT_INVALID_2), false); + expect(NamespaceUtils.isValidAccount(TEST_ACCOUNT_INVALID_3), false); + }); + + test('isValidUrl', () { + expect( + NamespaceUtils.isValidUrl(TEST_RELAY_URL), + true, + ); + }); + + test('getAccount', () { + expect('invalid', 'invalid'); + expect( + NamespaceUtils.getAccount(TEST_ACCOUNTS[0]), + TEST_ACCOUNTS[0].split(':')[2], + ); + }); + + test('getChainFromAccount', () { + expect('invalid', 'invalid'); + expect( + NamespaceUtils.getChainFromAccount(TEST_ACCOUNTS[0]), + TEST_CHAINS[0], + ); + }); + + test('getChainsFromAccounts', () { + expect(NamespaceUtils.getChainsFromAccounts([]), []); + expect( + NamespaceUtils.getChainsFromAccounts(TEST_ACCOUNTS), + TEST_CHAINS, + ); + expect( + NamespaceUtils.getChainsFromAccounts( + [ + ...TEST_ACCOUNTS, + ...TEST_ACCOUNTS, + ], + ), + TEST_CHAINS, + ); + }); + + test('getNamespaceFromChain', () { + expect(NamespaceUtils.getNamespaceFromChain('invalid'), 'invalid'); + expect( + NamespaceUtils.getNamespaceFromChain(TEST_ETHEREUM_CHAIN), + 'eip155', + ); + }); + + test('getChainsFromNamespace', () { + expect( + NamespaceUtils.getChainIdsFromNamespace( + nsOrChainId: EVM_NAMESPACE, + namespace: TEST_ETH_ARB_NAMESPACE, + ), + TEST_CHAINS, + ); + expect( + NamespaceUtils.getChainIdsFromNamespace( + nsOrChainId: TEST_AVALANCHE_CHAIN, + namespace: TEST_AVA_NAMESPACE, + ), + [TEST_AVALANCHE_CHAIN], + ); + }); + + test('getChainsFromNamespaces', () { + expect( + NamespaceUtils.getChainIdsFromNamespaces( + namespaces: TEST_NAMESPACES, + ), + [...TEST_CHAINS, TEST_AVALANCHE_CHAIN], + ); + }); + + test('getNamespacesMethodsForChainId', () { + expect( + NamespaceUtils.getNamespacesMethodsForChainId( + chainId: TEST_ETHEREUM_CHAIN, + namespaces: TEST_NAMESPACES, + ), + TEST_METHODS_1, + ); + expect( + NamespaceUtils.getNamespacesMethodsForChainId( + chainId: TEST_ARBITRUM_CHAIN, + namespaces: TEST_NAMESPACES, + ), + TEST_METHODS_1, + ); + expect( + NamespaceUtils.getNamespacesMethodsForChainId( + chainId: TEST_AVALANCHE_CHAIN, + namespaces: TEST_NAMESPACES, + ), + TEST_METHODS_2, + ); + }); + + test('getNamespacesEventsForChainId', () { + expect( + NamespaceUtils.getNamespacesEventsForChain( + chainId: TEST_ETHEREUM_CHAIN, + namespaces: TEST_NAMESPACES, + ), + [TEST_EVENT_1], + ); + expect( + NamespaceUtils.getNamespacesEventsForChain( + chainId: TEST_ARBITRUM_CHAIN, + namespaces: TEST_NAMESPACES, + ), + [TEST_EVENT_1], + ); + expect( + NamespaceUtils.getNamespacesEventsForChain( + chainId: TEST_AVALANCHE_CHAIN, + namespaces: TEST_NAMESPACES, + ), + [TEST_EVENT_2], + ); + }); + + test('getChainsFromRequiredNamespace', () { + expect( + NamespaceUtils.getChainsFromRequiredNamespace( + nsOrChainId: EVM_NAMESPACE, + requiredNamespace: TEST_ETH_ARB_REQUIRED_NAMESPACE, + ), + TEST_CHAINS, + ); + expect( + NamespaceUtils.getChainsFromRequiredNamespace( + nsOrChainId: TEST_AVALANCHE_CHAIN, + requiredNamespace: TEST_AVA_REQUIRED_NAMESPACE, + ), + [TEST_AVALANCHE_CHAIN], + ); + }); + + test('getChainsFromRequiredNamespaces', () { + expect( + NamespaceUtils.getChainIdsFromRequiredNamespaces( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + ), + [...TEST_CHAINS, TEST_AVALANCHE_CHAIN], + ); + }); + + group('constructNamespaces', () { + test('constructs namespaces with required namespaces', () { + Map namespaces = NamespaceUtils.constructNamespaces( + availableAccounts: availableAccounts, + availableMethods: availableMethods, + availableEvents: availableEvents, + requiredNamespaces: requiredNamespacesInAvailable, + ); + + expect(namespaces.length, 2); + expect( + namespaces['namespace1:chain1']!.accounts, + ['namespace1:chain1:address1', 'namespace1:chain1:address2'], + ); + expect( + namespaces['namespace1:chain1']!.methods, + ['method1'], + ); + expect( + namespaces['namespace1:chain1']!.events, + ['event1'], + ); + + expect(namespaces['namespace2']!.accounts, [ + 'namespace2:chain1:address3', + 'namespace2:chain1:address4', + 'namespace2:chain2:address5', + ]); + expect( + namespaces['namespace2']!.methods, + ['method3'], + ); + expect( + namespaces['namespace2']!.events, + ['event3'], + ); + + expect( + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: requiredNamespacesInAvailable, + namespaces: namespaces, + context: '', + ), + true, + ); + + namespaces = NamespaceUtils.constructNamespaces( + availableAccounts: availableAccounts, + availableMethods: availableMethods, + availableEvents: availableEvents, + requiredNamespaces: requiredNamespacesInAvailable2, + ); + + expect(namespaces.length, 2); + expect( + namespaces['namespace1']!.accounts, + ['namespace1:chain1:address1', 'namespace1:chain1:address2'], + ); + expect( + namespaces['namespace1']!.methods, + ['method1'], + ); + expect( + namespaces['namespace1']!.events, + ['event1'], + ); + + expect(namespaces['namespace2']!.accounts, [ + 'namespace2:chain1:address3', + 'namespace2:chain1:address4', + 'namespace2:chain2:address5', + ]); + expect( + namespaces['namespace2']!.methods, + ['method3'], + ); + expect( + namespaces['namespace2']!.events, + ['event3'], + ); + + expect( + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: requiredNamespacesInAvailable2, + namespaces: namespaces, + context: '', + ), + true, + ); + + namespaces = NamespaceUtils.constructNamespaces( + availableAccounts: availableAccounts, + availableMethods: availableMethods, + availableEvents: availableEvents, + requiredNamespaces: requiredNamespacesMatchingAvailable1, + ); + + expect(namespaces.length, 2); + expect( + namespaces['namespace1:chain1']!.accounts, + ['namespace1:chain1:address1', 'namespace1:chain1:address2'], + ); + expect( + namespaces['namespace1:chain1']!.methods, + ['method1', 'method2'], + ); + expect( + namespaces['namespace1:chain1']!.events, + ['event1', 'event2'], + ); + + expect(namespaces['namespace2']!.accounts, [ + 'namespace2:chain1:address3', + 'namespace2:chain1:address4', + ]); + expect( + namespaces['namespace2']!.methods, + ['method3', 'method4'], + ); + expect( + namespaces['namespace2']!.events, + ['event3', 'event4'], + ); + + expect( + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: requiredNamespacesMatchingAvailable1, + namespaces: namespaces, + context: '', + ), + true, + ); + }); + + test('constructs namespaces with required and optional namespaces', () { + Map namespaces = NamespaceUtils.constructNamespaces( + availableAccounts: availableAccounts3, + availableMethods: availableMethods3, + availableEvents: availableEvents3, + requiredNamespaces: requiredNamespacesInAvailable3, + optionalNamespaces: optionalNamespacesInAvailable3, + ); + + expect(namespaces.length, 1); + expect( + namespaces['eip155']!.accounts, + availableAccounts3.toList(), + ); + expect( + namespaces['eip155']!.methods, + availableMethods3.map((m) => m.split(':').last).toList(), + ); + expect( + namespaces['eip155']!.events, + availableEvents3.map((m) => m.split(':').last).toList(), + ); + + expect( + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: requiredNamespacesInAvailable3, + namespaces: namespaces, + context: '', + ), + true, + ); + }); + + test('constructNamespaces trims off unrequested', () { + final reqNamespace = { + 'eip155': const RequiredNamespace( + chains: ['eip155:1'], + methods: ['eth_sendTransaction', 'personal_sign'], + events: ['chainChanged', 'accountsChanged']), + }; + final optionalNamespace = { + 'eip155': const RequiredNamespace( + chains: ['eip155:137'], + methods: [ + 'eth_sendTransaction', + 'personal_sign', + 'eth_accounts', + 'eth_requestAccounts', + 'eth_call', + 'eth_getBalance', + 'eth_sendRawTransaction', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + 'wallet_switchEthereumChain', + 'wallet_addEthereumChain', + 'wallet_getPermissions', + 'wallet_requestPermissions', + 'wallet_registerOnboarding', + 'wallet_watchAsset', + 'wallet_scanQRCode' + ], + events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ], + ) + }; + final Set availableAccounts = { + 'eip155:1:0x83ba3013f776d4e2801010ee88581aedf5349b43', + 'eip155:5:0x83ba3013f776d4e2801010ee88581aedf5349b43', + 'eip155:137:0x83ba3013f776d4e2801010ee88581aedf5349b43', + 'eip155:80001:0x83ba3013f776d4e2801010ee88581aedf5349b43', + }; + final Set availableMethods = { + 'eip155:1:personal_sign', + 'eip155:1:eth_sign', + 'eip155:1:eth_signTransaction', + 'eip155:1:eth_sendTransaction', + 'eip155:1:eth_signTypedData', + 'eip155:137:personal_sign', + 'eip155:137:eth_sign', + 'eip155:137:eth_signTransaction', + 'eip155:137:eth_sendTransaction', + 'eip155:137:eth_signTypedData', + 'eip155:5:personal_sign', + 'eip155:5:eth_sign', + 'eip155:5:eth_signTransaction', + 'eip155:5:eth_sendTransaction', + 'eip155:5:eth_signTypedData', + 'eip155:80001:personal_sign', + 'eip155:80001:eth_sign', + 'eip155:80001:eth_signTransaction', + 'eip155:80001:eth_sendTransaction', + 'eip155:80001:eth_signTypedData', + }; + final Set availableEvents = { + 'eip155:1:chainChanged', + 'eip155:1:accountsChanged', + 'eip155:137:chainChanged', + 'eip155:137:accountsChanged', + 'eip155:5:chainChanged', + 'eip155:5:accountsChanged', + 'eip155:80001:chainChanged', + 'eip155:80001:accountsChanged', + }; + + final Map namespaces = + NamespaceUtils.constructNamespaces( + availableAccounts: availableAccounts, + availableMethods: availableMethods, + availableEvents: availableEvents, + requiredNamespaces: reqNamespace, + optionalNamespaces: optionalNamespace, + ); + final Namespace? eip155 = namespaces['eip155']; + // print(eip155); + + expect(eip155 != null, true); + expect( + eip155!.accounts, + [ + 'eip155:1:0x83ba3013f776d4e2801010ee88581aedf5349b43', + 'eip155:137:0x83ba3013f776d4e2801010ee88581aedf5349b43' + ], + ); + expect( + eip155.methods, + [ + 'personal_sign', + 'eth_sendTransaction', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData' + ], + ); + expect( + eip155.events, + ['chainChanged', 'accountsChanged'], + ); + }); + + test('nonconforming if available information does not satisfy required', + () { + final List nonconforming = [ + requiredNamespacesNonconformingAccounts1, + requiredNamespacesNonconformingMethods1, + requiredNamespacesNonconformingMethods2, + requiredNamespacesNonconformingEvents1, + requiredNamespacesNonconformingEvents2, + ]; + final List expected = [ + Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: + " namespaces chains don't satisfy requiredNamespaces chains for namespace3. Requested: [namespace3:chain1], Supported: []", + ).message, + Errors.getSdkError( + Errors.UNSUPPORTED_METHODS, + context: + " namespaces methods don't satisfy requiredNamespaces methods for namespace1:chain1. Requested: [method1, method2, method3], Supported: [method1, method2]", + ).message, + Errors.getSdkError( + Errors.UNSUPPORTED_METHODS, + context: + " namespaces methods don't satisfy requiredNamespaces methods for namespace1:chain1. Requested: [method1, method2, method3], Supported: [method1, method2]", + ).message, + Errors.getSdkError( + Errors.UNSUPPORTED_EVENTS, + context: + " namespaces events don't satisfy requiredNamespaces events for namespace1:chain1. Requested: [event1, event2, event3], Supported: [event1, event2]", + ).message, + Errors.getSdkError( + Errors.UNSUPPORTED_EVENTS, + context: + " namespaces events don't satisfy requiredNamespaces events for namespace1:chain1. Requested: [event1, event2, event3], Supported: [event1, event2]", + ).message, + ]; + + for (int i = 0; i < nonconforming.length; i++) { + final namespaces = NamespaceUtils.constructNamespaces( + availableAccounts: availableAccounts, + availableMethods: availableMethods, + availableEvents: availableEvents, + requiredNamespaces: nonconforming[i], + ); + + // Expect a thrown error + expect( + () => SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: nonconforming[i], + namespaces: namespaces, + context: '', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + expected[i], + ), + ), + ); + } + }); + + test('constructs namespaces with optional namespaces', () { + Map namespaces = NamespaceUtils.constructNamespaces( + availableAccounts: availableAccounts, + availableMethods: availableMethods, + availableEvents: availableEvents, + requiredNamespaces: requiredNamespacesInAvailable, + optionalNamespaces: optionalNamespaces, + ); + + // print(namespaces); + expect(namespaces.keys.length, 3); + + expect( + namespaces['namespace4:chain1']!.accounts, + ['namespace4:chain1:address6'], + ); + expect( + namespaces['namespace4:chain1']!.methods, + ['method5'], + ); + expect( + namespaces['namespace4:chain1']!.events, + ['event5'], + ); + + expect( + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: requiredNamespacesInAvailable, + namespaces: namespaces, + context: '', + ), + true, + ); + }); + }); + }); +} diff --git a/packages/reown_sign/test/utils/sign_client_constants.dart b/packages/reown_sign/test/utils/sign_client_constants.dart new file mode 100644 index 0000000..6a7e658 --- /dev/null +++ b/packages/reown_sign/test/utils/sign_client_constants.dart @@ -0,0 +1,227 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; + +const TEST_RELAY_OPTIONS = { + 'protocol': ReownConstants.RELAYER_DEFAULT_PROTOCOL, +}; + +const EVM_NAMESPACE = 'eip155'; + +const TEST_ARBITRUM_CHAIN = 'eip155:42161'; +const TEST_AVALANCHE_CHAIN = 'eip155:43114'; +const TEST_UNINCLUDED_CHAIN = 'eip155:2'; + +const TEST_CHAINS = [ + TEST_ETHEREUM_CHAIN, + TEST_ARBITRUM_CHAIN, +]; +const TEST_CHAIN_INVALID_1 = 'swag'; +const TEST_CHAIN_INVALID_2 = 's:w:a'; +const TEST_CHAINS_INVALID = [ + TEST_CHAIN_INVALID_1, + TEST_CHAIN_INVALID_2, +]; + +const TEST_ETHEREUM_ADDRESS = '0x3c582121909DE92Dc89A36898633C1aE4790382b'; + +const TEST_ETHEREUM_ACCOUNT = '$TEST_ETHEREUM_CHAIN:$TEST_ETHEREUM_ADDRESS'; +const TEST_ARBITRUM_ACCOUNT = '$TEST_ARBITRUM_CHAIN:$TEST_ETHEREUM_ADDRESS'; +const TEST_AVALANCHE_ACCOUNT = '$TEST_AVALANCHE_CHAIN:$TEST_ETHEREUM_ADDRESS'; + +const TEST_ACCOUNTS = [ + TEST_ETHEREUM_ACCOUNT, + TEST_ARBITRUM_ACCOUNT, +]; +const TEST_ACCOUNT_INVALID_1 = 'swag'; +const TEST_ACCOUNT_INVALID_2 = 's:w'; +const TEST_ACCOUNT_INVALID_3 = 's:w:a:g'; +const TEST_ACCOUNTS_INVALID = [ + TEST_ACCOUNT_INVALID_1, + TEST_ACCOUNT_INVALID_2, + TEST_ACCOUNT_INVALID_3, +]; + +const TEST_METHOD_1 = 'eth_sendTransaction'; +const TEST_METHOD_2 = 'eth_signTransaction'; +const TEST_METHOD_3 = 'personal_sign'; +const TEST_METHOD_4 = 'eth_signTypedData'; +const TEST_METHODS_1 = [ + TEST_METHOD_1, + TEST_METHOD_2, +]; +const TEST_METHODS_2 = [ + TEST_METHOD_3, + TEST_METHOD_4, +]; +const TEST_METHODS_FULL = [ + ...TEST_METHODS_1, + ...TEST_METHODS_2, +]; +const TEST_METHOD_INVALID_1 = 'eth_invalid'; + +const TEST_EVENT_1 = 'chainChanged'; +const TEST_EVENT_2 = 'accountsChanged'; +const TEST_EVENTS_FULL = [ + TEST_EVENT_1, + TEST_EVENT_2, +]; +const TEST_EVENT_INVALID_1 = 'eth_event_invalid'; + +const TEST_ETH_ARB_REQUIRED_NAMESPACE = RequiredNamespace( + chains: TEST_CHAINS, + methods: TEST_METHODS_1, + events: [TEST_EVENT_1], +); +const TEST_AVA_REQUIRED_NAMESPACE = RequiredNamespace( + methods: TEST_METHODS_2, + events: [TEST_EVENT_2], +); +const TEST_REQUIRED_NAMESPACES = { + EVM_NAMESPACE: TEST_ETH_ARB_REQUIRED_NAMESPACE, + TEST_AVALANCHE_CHAIN: TEST_AVA_REQUIRED_NAMESPACE, +}; + +const TEST_ETH_ARB_NAMESPACE = Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_1, + events: [TEST_EVENT_1], +); +const TEST_AVA_NAMESPACE = Namespace( + accounts: [TEST_AVALANCHE_ACCOUNT], + methods: TEST_METHODS_2, + events: [TEST_EVENT_2], +); +const TEST_NAMESPACES = { + EVM_NAMESPACE: TEST_ETH_ARB_NAMESPACE, + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; + +// Invalid RequiredNamespaces +const TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1 = { + 'eip155:2': TEST_ETH_ARB_REQUIRED_NAMESPACE, +}; +const TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_2 = { + EVM_NAMESPACE: RequiredNamespace( + chains: ['eip155:1', TEST_CHAIN_INVALID_1], + methods: [], + events: [], + ), +}; + +// Invalid Namespaces +const TEST_NAMESPACES_INVALID_ACCOUNTS = { + EVM_NAMESPACE: Namespace( + accounts: [TEST_ACCOUNT_INVALID_1], + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), +}; + +// Invalid Conforming Namespaces +const TEST_NAMESPACES_NONCONFORMING_KEY_VALUE = 'eip1111'; +const TEST_NAMESPACES_NONCONFORMING_KEY_1 = { + TEST_NAMESPACES_NONCONFORMING_KEY_VALUE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ) +}; +const TEST_NAMESPACES_NONCONFORMING_KEY_2 = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), +}; +const TEST_NAMESPACES_NONCONFORMING_CHAINS = { + EVM_NAMESPACE: Namespace( + accounts: [TEST_ETHEREUM_ACCOUNT], + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; +const TEST_NAMESPACES_NONCONFORMING_METHODS = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: [TEST_METHOD_INVALID_1], + events: TEST_EVENTS_FULL, + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; +const TEST_NAMESPACES_NONCONFORMING_EVENTS = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: [TEST_EVENT_INVALID_1], + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; + +// Session Data +const TEST_SESSION_INVALID_TOPIC = 'swagmaster'; +const TEST_SESSION_VALID_TOPIC = 'abc'; +const TEST_SESSION_EXPIRED_TOPIC = 'expired'; +final testSessionValid = SessionData( + topic: TEST_SESSION_VALID_TOPIC, + pairingTopic: TEST_PAIRING_TOPIC, + relay: Relay('irn'), + expiry: 1000000000000, + acknowledged: true, + controller: 'test', + namespaces: TEST_NAMESPACES, + requiredNamespaces: { + EVM_NAMESPACE: TEST_ETH_ARB_REQUIRED_NAMESPACE, + }, + optionalNamespaces: {}, + self: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), + peer: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), +); +final testSessionExpired = SessionData( + topic: TEST_SESSION_EXPIRED_TOPIC, + pairingTopic: TEST_PAIRING_TOPIC, + relay: Relay('irn'), + expiry: -1, + acknowledged: true, + controller: 'test', + namespaces: TEST_NAMESPACES, + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + optionalNamespaces: {}, + self: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), + peer: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), +); + +// Test Messages + +const TEST_MESSAGE_1 = {'test': 'Hello'}; +const TEST_MESSAGE_2 = 'Hello'; + +const TEST_MESSAGE = 'My name is John Doe'; +const TEST_SIGNATURE = + '0xc8906b32c9f74d0805226ffff5ecd6897ea55cdf58f54a53a2e5b5d5a21fb67f43ef1d4c2ed790a724a1549b4cc40137403048c4aed9825cfd5ba6c1d15bd0721c'; + +const TEST_SIGN_METHOD = 'personal_sign'; +const TEST_SIGN_PARAMS = [ + TEST_MESSAGE, + TEST_ETHEREUM_ADDRESS, +]; +const TEST_SIGN_REQUEST = { + 'method': TEST_SIGN_METHOD, + 'params': TEST_SIGN_PARAMS +}; + +const TEST_RANDOM_REQUEST = {'method': 'random_method', 'params': []}; diff --git a/packages/reown_sign/test/utils/sign_client_test_wrapper.dart b/packages/reown_sign/test/utils/sign_client_test_wrapper.dart new file mode 100644 index 0000000..9020a1a --- /dev/null +++ b/packages/reown_sign/test/utils/sign_client_test_wrapper.dart @@ -0,0 +1,569 @@ +import 'package:event/event.dart'; +import 'package:reown_core/relay_client/websocket/http_client.dart'; +import 'package:reown_core/relay_client/websocket/i_http_client.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_sign/reown_sign.dart'; + +class SignClientTestWrapper implements IReownSign { + bool _initialized = false; + + @override + Event get onSessionDelete => client.onSessionDelete; + @override + Event get onSessionConnect => client.onSessionConnect; + @override + Event get onSessionEvent => client.onSessionEvent; + @override + Event get onSessionExpire => client.onSessionExpire; + @override + Event get onSessionExtend => client.onSessionExtend; + @override + Event get onSessionPing => client.onSessionPing; + @override + Event get onSessionProposal => client.onSessionProposal; + @override + Event get onSessionProposalError => + client.onSessionProposalError; + @override + Event get onProposalExpire => client.onProposalExpire; + @override + Event get onSessionRequest => client.onSessionRequest; + @override + Event get onSessionUpdate => client.onSessionUpdate; + + @override + IReownCore get core => client.core; + @override + PairingMetadata get metadata => client.metadata; + @override + IGenericStore get proposals => client.proposals; + @override + ISessions get sessions => client.sessions; + @override + IGenericStore get pendingRequests => client.pendingRequests; + + late IReownSignClient client; + + static Future createInstance({ + required String projectId, + String relayUrl = ReownConstants.DEFAULT_RELAY_URL, + required PairingMetadata metadata, + bool memoryStore = false, + LogLevel logLevel = LogLevel.nothing, + IHttpClient httpClient = const HttpWrapper(), + }) async { + final client = SignClientTestWrapper( + core: ReownCore( + projectId: projectId, + relayUrl: relayUrl, + memoryStore: memoryStore, + httpClient: httpClient, + ), + metadata: metadata, + ); + await client.init(); + + return client; + } + + SignClientTestWrapper({ + required IReownCore core, + required PairingMetadata metadata, + }) { + client = ReownSignClient( + core: core, + metadata: metadata, + ); + } + + @override + Future init() async { + if (_initialized) { + return; + } + + await core.start(); + await client.init(); + + _initialized = true; + } + + @override + Future connect({ + Map? requiredNamespaces, + Map? optionalNamespaces, + Map? sessionProperties, + String? pairingTopic, + List? relays, + List>? methods = ReownSign.DEFAULT_METHODS, + }) async { + try { + return await client.connect( + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: sessionProperties, + pairingTopic: pairingTopic, + relays: relays, + methods: methods, + ); + } catch (e) { + // print(e); + rethrow; + } + } + + @override + Future pair({ + required Uri uri, + }) async { + try { + return await client.pair(uri: uri); + } catch (e) { + rethrow; + } + } + + @override + Future approveSession({ + required int id, + required Map namespaces, + Map? sessionProperties, + String? relayProtocol, + }) async { + try { + return await client.approve( + id: id, + namespaces: namespaces, + relayProtocol: relayProtocol, + ); + } catch (e) { + rethrow; + } + } + + @override + Future rejectSession({ + required int id, + required ReownSignError reason, + }) async { + try { + return await client.reject( + id: id, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + Future updateSession({ + required String topic, + required Map namespaces, + }) async { + try { + return await client.update( + topic: topic, + namespaces: namespaces, + ); + } catch (e) { + // final error = e as WCError; + rethrow; + } + } + + @override + Future extendSession({ + required String topic, + }) async { + try { + return await client.extend(topic: topic); + } catch (e) { + rethrow; + } + } + + @override + void registerRequestHandler({ + required String chainId, + required String method, + void Function(String, dynamic)? handler, + }) { + try { + return client.registerRequestHandler( + chainId: chainId, + method: method, + handler: handler, + ); + } catch (e) { + rethrow; + } + } + + @override + Future request({ + required String topic, + required String chainId, + required SessionRequestParams request, + }) async { + try { + return await client.request( + topic: topic, + chainId: chainId, + request: request, + ); + } catch (e) { + rethrow; + } + } + + @override + Future> requestReadContract({ + required DeployedContract deployedContract, + required String functionName, + required String rpcUrl, + EthereumAddress? sender, + List parameters = const [], + }) async { + try { + return await client.requestReadContract( + sender: sender, + deployedContract: deployedContract, + functionName: functionName, + rpcUrl: rpcUrl, + parameters: parameters, + ); + } catch (e) { + rethrow; + } + } + + @override + Future requestWriteContract({ + required String topic, + required String chainId, + required DeployedContract deployedContract, + required String functionName, + required Transaction transaction, + String? method, + List parameters = const [], + }) async { + try { + return await client.requestWriteContract( + topic: topic, + chainId: chainId, + deployedContract: deployedContract, + functionName: functionName, + transaction: transaction, + method: method, + parameters: parameters, + ); + } catch (e) { + rethrow; + } + } + + @override + Future respondSessionRequest({ + required String topic, + required JsonRpcResponse response, + }) { + try { + return client.respond( + topic: topic, + response: response, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerEventHandler({ + required String chainId, + required String event, + dynamic Function(String, dynamic)? handler, + }) { + try { + return client.registerEventHandler( + chainId: chainId, + event: event, + handler: handler, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerEventEmitter({ + required String chainId, + required String event, + }) { + try { + return client.registerEventEmitter( + chainId: chainId, + event: event, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerAccount({ + required String chainId, + required String accountAddress, + }) { + try { + return client.registerAccount( + chainId: chainId, + accountAddress: accountAddress, + ); + } catch (e) { + rethrow; + } + } + + @override + Future emitSessionEvent({ + required String topic, + required String chainId, + required SessionEventParams event, + }) async { + try { + return await client.emit( + topic: topic, + chainId: chainId, + event: event, + ); + } catch (e) { + rethrow; + } + } + + @override + Future ping({ + required String topic, + }) async { + try { + return await client.ping(topic: topic); + } catch (e) { + rethrow; + } + } + + @override + Future disconnectSession({ + required String topic, + required ReownSignError reason, + }) async { + try { + return await client.disconnect( + topic: topic, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + SessionData? find({ + required Map requiredNamespaces, + }) { + try { + return client.find(requiredNamespaces: requiredNamespaces); + } catch (e) { + rethrow; + } + } + + @override + Map getActiveSessions() { + try { + return client.getActiveSessions(); + } catch (e) { + rethrow; + } + } + + @override + Map getSessionsForPairing({ + required String pairingTopic, + }) { + try { + return client.getSessionsForPairing( + pairingTopic: pairingTopic, + ); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionProposals() { + try { + return client.getPendingSessionProposals(); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionRequests() { + try { + return client.getPendingSessionRequests(); + } catch (e) { + rethrow; + } + } + + @override + Future dispatchEnvelope(String url) { + try { + return client.dispatchEnvelope(url); + } catch (e) { + rethrow; + } + } + + @override + Future redirectToDapp({ + required String topic, + required Redirect? redirect, + }) { + return client.redirectToDapp( + topic: topic, + redirect: redirect, + ); + } + + @override + Future redirectToWallet({ + required String topic, + required Redirect? redirect, + }) { + return client.redirectToWallet( + topic: topic, + redirect: redirect, + ); + } + + @override + IPairingStore get pairings => core.pairing.getStore(); + + @override + Future checkAndExpire() async { + for (var session in sessions.getAll()) { + await core.expirer.checkAndExpire(session.topic); + } + } + + @override + IGenericStore get authKeys => client.authKeys; + + @override + Future validateSignedCacao({ + required Cacao cacao, + required String projectId, + }) { + try { + return client.validateSignedCacao( + cacao: cacao, + projectId: projectId, + ); + } catch (e) { + rethrow; + } + } + + @override + String formatAuthMessage({ + required String iss, + required CacaoRequestPayload cacaoPayload, + }) { + try { + return client.formatAuthMessage( + iss: iss, + cacaoPayload: cacaoPayload, + ); + } catch (e) { + rethrow; + } + } + + @override + Event get onSessionAuthResponse => + client.onSessionAuthResponse; + + @override + IGenericStore get pairingTopics => client.pairingTopics; + + @override + IGenericStore get sessionAuthRequests => + client.sessionAuthRequests; + + @override + Event get onSessionAuthRequest => + client.onSessionAuthRequest; + + @override + Future authenticate({ + required SessionAuthRequestParams params, + String? walletUniversalLink, + String? pairingTopic, + List>? methods, + }) async { + try { + return await client.authenticate( + params: params, + walletUniversalLink: walletUniversalLink, + pairingTopic: pairingTopic, + methods: methods, + ); + } catch (e) { + rethrow; + } + } + + @override + Future approveSessionAuthenticate({ + required int id, + List? auths, + }) async { + try { + return await client.approveSessionAuthenticate( + id: id, + auths: auths, + ); + } catch (e) { + rethrow; + } + } + + @override + Future rejectSessionAuthenticate({ + required int id, + required ReownSignError reason, + }) async { + try { + return await client.rejectSessionAuthenticate( + id: id, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionAuthRequests() { + try { + return client.getPendingSessionAuthRequests(); + } catch (e) { + rethrow; + } + } +} diff --git a/packages/reown_sign/test/validation_test.dart b/packages/reown_sign/test/validation_test.dart new file mode 100644 index 0000000..9bbbed0 --- /dev/null +++ b/packages/reown_sign/test/validation_test.dart @@ -0,0 +1,418 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reown_sign/reown_sign.dart'; +import 'package:reown_sign/utils/sign_api_validator_utils.dart'; + +import 'shared/shared_test_values.dart'; +import 'utils/sign_client_constants.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('SignApiValidatorUtils', () { + test('isContainedIn', () { + expect( + SignApiValidatorUtils.isContainedIn( + container: ['a', 'b'], + contained: ['a', 'b'], + ), + true, + ); + expect( + SignApiValidatorUtils.isContainedIn( + container: ['a', 'b'], + contained: ['b'], + ), + true, + ); + expect( + SignApiValidatorUtils.isContainedIn( + container: ['a', 'b'], + contained: [], + ), + true, + ); + expect( + SignApiValidatorUtils.isContainedIn( + container: ['a', 'b'], + contained: ['c'], + ), + false, + ); + expect( + SignApiValidatorUtils.isContainedIn( + container: ['a', 'b'], + contained: ['a', 'b', 'c'], + ), + false, + ); + expect( + SignApiValidatorUtils.isContainedIn( + container: ['a', 'b'], + contained: ['a', 'c'], + ), + false, + ); + }); + }); + + test('isValidChains', () { + expect( + SignApiValidatorUtils.isValidChains( + nsOrChainId: TEST_ETHEREUM_CHAIN, + context: 'test', + ), + true, + ); + expect( + SignApiValidatorUtils.isValidChains( + nsOrChainId: EVM_NAMESPACE, + chains: TEST_CHAINS, + context: 'test', + ), + true, + ); + expect( + () => SignApiValidatorUtils.isValidChains( + nsOrChainId: TEST_ETHEREUM_CHAIN, + chains: TEST_CHAINS, + context: 'test', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. test, namespace is a chainId, but chains is not empty', + ), + ), + ); + expect( + () => SignApiValidatorUtils.isValidChains( + nsOrChainId: EVM_NAMESPACE, + chains: TEST_CHAINS_INVALID, + context: 'test', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. test, chain $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId" format', + ), + ), + ); + }); + + test('isValidRequiredNamespaces', () { + expect( + SignApiValidatorUtils.isValidRequiredNamespaces( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + context: 'test', + ), + true, + ); + expect( + () => SignApiValidatorUtils.isValidRequiredNamespaces( + requiredNamespaces: TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1, + context: 'test', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. test requiredNamespace, namespace is a chainId, but chains is not empty', + ), + ), + ); + expect( + () => SignApiValidatorUtils.isValidRequiredNamespaces( + requiredNamespaces: TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_2, + context: 'test', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. test requiredNamespace, chain $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId" format', + ), + ), + ); + }); + + test('isValidAccounts', () { + expect( + SignApiValidatorUtils.isValidAccounts( + accounts: TEST_ACCOUNTS, + context: 'test', + ), + true, + ); + expect( + () => SignApiValidatorUtils.isValidAccounts( + accounts: TEST_ACCOUNTS_INVALID, + context: 'test', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported accounts. test, account $TEST_ACCOUNT_INVALID_1 should conform to "namespace:chainId:address" format', + ), + ), + ); + }); + + test('isValidNamespaces', () { + expect( + SignApiValidatorUtils.isValidNamespaces( + namespaces: TEST_NAMESPACES, + context: 'test', + ), + true, + ); + expect( + () => SignApiValidatorUtils.isValidNamespaces( + namespaces: TEST_NAMESPACES_INVALID_ACCOUNTS, + context: 'test', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported accounts. test namespace, account $TEST_ACCOUNT_INVALID_1 should conform to "namespace:chainId:address" format', + ), + ), + ); + }); + + test('isValidNamespacesChainId', () { + expect( + SignApiValidatorUtils.isValidNamespacesChainId( + namespaces: TEST_NAMESPACES, + chainId: TEST_ETHEREUM_CHAIN, + ), + true, + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesChainId( + namespaces: TEST_NAMESPACES, + chainId: TEST_UNINCLUDED_CHAIN, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. The chain $TEST_UNINCLUDED_CHAIN is not supported', + ), + ), + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesChainId( + namespaces: TEST_NAMESPACES, + chainId: TEST_CHAIN_INVALID_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. chain $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId" format', + ), + ), + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesChainId( + namespaces: TEST_NAMESPACES_INVALID_ACCOUNTS, + chainId: TEST_ETHEREUM_CHAIN, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported accounts. isValidNamespacesChainId namespace, account $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId:address" format', + ), + ), + ); + }); + + test('isValidNamespacesRequest', () { + expect( + SignApiValidatorUtils.isValidNamespacesRequest( + namespaces: TEST_NAMESPACES, + chainId: TEST_ETHEREUM_CHAIN, + method: TEST_METHOD_1, + ), + true, + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesRequest( + namespaces: TEST_NAMESPACES, + chainId: TEST_ETHEREUM_CHAIN, + method: TEST_METHOD_3, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported methods. The method $TEST_METHOD_3 is not supported', + ), + ), + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesRequest( + namespaces: TEST_NAMESPACES, + chainId: TEST_CHAIN_INVALID_1, + method: TEST_METHOD_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. chain $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId" format', + ), + ), + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesRequest( + namespaces: TEST_NAMESPACES_INVALID_ACCOUNTS, + chainId: TEST_ETHEREUM_CHAIN, + method: TEST_METHOD_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported accounts. isValidNamespacesRequest namespace, account $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId:address" format', + ), + ), + ); + }); + + test('isValidNamespacesEvent', () { + expect( + SignApiValidatorUtils.isValidNamespacesEvent( + namespaces: TEST_NAMESPACES, + chainId: TEST_ETHEREUM_CHAIN, + eventName: TEST_EVENT_1, + ), + true, + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesEvent( + namespaces: TEST_NAMESPACES, + chainId: TEST_ETHEREUM_CHAIN, + eventName: TEST_EVENT_2, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported events. The event $TEST_EVENT_2 is not supported', + ), + ), + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesEvent( + namespaces: TEST_NAMESPACES, + chainId: TEST_CHAIN_INVALID_1, + eventName: TEST_EVENT_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported chains. chain $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId" format', + ), + ), + ); + expect( + () => SignApiValidatorUtils.isValidNamespacesEvent( + namespaces: TEST_NAMESPACES_INVALID_ACCOUNTS, + chainId: TEST_ETHEREUM_CHAIN, + eventName: TEST_EVENT_1, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Unsupported accounts. isValidNamespacesEvent namespace, account $TEST_CHAIN_INVALID_1 should conform to "namespace:chainId:address" format', + ), + ), + ); + }); + + test('isConformingNamespaces', () { + expect( + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + namespaces: TEST_NAMESPACES, + context: 'test', + ), + true, + ); + expect( + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + namespaces: TEST_NAMESPACES, + context: 'test', + ), + true, + ); + final List nonconformingNamespaces = [ + TEST_NAMESPACES_NONCONFORMING_KEY_1, + TEST_NAMESPACES_NONCONFORMING_KEY_2, + TEST_NAMESPACES_NONCONFORMING_CHAINS, + TEST_NAMESPACES_NONCONFORMING_METHODS, + TEST_NAMESPACES_NONCONFORMING_EVENTS, + ]; + final List errors = [ + "Unsupported namespace key. test namespaces keys don't satisfy requiredNamespaces", + "Unsupported namespace key. test namespaces keys don't satisfy requiredNamespaces", + "Unsupported chains. test namespaces chains don't satisfy requiredNamespaces chains for $EVM_NAMESPACE. Requested: [eip155:1, eip155:42161], Supported: [eip155:1]", + "Unsupported methods. test namespaces methods don't satisfy requiredNamespaces methods for $EVM_NAMESPACE. Requested: [eth_sendTransaction, eth_signTransaction], Supported: [eth_invalid]", + "Unsupported events. test namespaces events don't satisfy requiredNamespaces events for $EVM_NAMESPACE. Requested: [chainChanged], Supported: [eth_event_invalid]", + ]; + for (int i = 0; i < nonconformingNamespaces.length; i++) { + expect( + () => SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + namespaces: nonconformingNamespaces[i], + context: 'test', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + errors[i], + ), + ), + ); + } + }); + + test('isSessionCompatible', () { + expect( + SignApiValidatorUtils.isSessionCompatible( + session: testSessionValid, + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + ), + true, + ); + final List nonconformingNamespaces = [ + TEST_NAMESPACES_NONCONFORMING_KEY_1, + TEST_NAMESPACES_NONCONFORMING_KEY_2, + TEST_NAMESPACES_NONCONFORMING_CHAINS, + TEST_NAMESPACES_NONCONFORMING_METHODS, + TEST_NAMESPACES_NONCONFORMING_EVENTS, + ]; + for (int i = 0; i < nonconformingNamespaces.length; i++) { + final SessionData newSessionData = testSessionValid.copyWith( + namespaces: nonconformingNamespaces[i], + ); + expect( + SignApiValidatorUtils.isSessionCompatible( + session: newSessionData, + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + ), + false, + ); + } + }); +} diff --git a/packages/reown_walletkit/.gitignore b/packages/reown_walletkit/.gitignore new file mode 100644 index 0000000..474a12e --- /dev/null +++ b/packages/reown_walletkit/.gitignore @@ -0,0 +1,58 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/packages/reown_walletkit/.metadata b/packages/reown_walletkit/.metadata new file mode 100644 index 0000000..bc3f0ae --- /dev/null +++ b/packages/reown_walletkit/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "300451adae589accbece3490f4396f10bdf15e6e" + channel: "stable" + +project_type: package diff --git a/packages/reown_walletkit/CHANGELOG.md b/packages/reown_walletkit/CHANGELOG.md new file mode 100644 index 0000000..69b4a02 --- /dev/null +++ b/packages/reown_walletkit/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +Initial release. diff --git a/packages/reown_walletkit/LICENSE b/packages/reown_walletkit/LICENSE new file mode 100644 index 0000000..212a53d --- /dev/null +++ b/packages/reown_walletkit/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Reown, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/reown_walletkit/README.md b/packages/reown_walletkit/README.md new file mode 100644 index 0000000..4522d65 --- /dev/null +++ b/packages/reown_walletkit/README.md @@ -0,0 +1,12 @@ +# **Reown - WalletKit Flutter** + +Read more about it on our [website](https://reown.com/walletkit) + +## Documentation + +For a full reference please check the [Official Documentation](https://docs.reown.com/walletkit/flutter/installation) + +## Example + +Please check the [example](https://github.com/WalletConnect/Web3ModalFlutter/tree/master/packages/reown_walletkit/example) folder for the example. + diff --git a/packages/reown_walletkit/analysis_options.yaml b/packages/reown_walletkit/analysis_options.yaml new file mode 100644 index 0000000..b677fc6 --- /dev/null +++ b/packages/reown_walletkit/analysis_options.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:lints/recommended.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + non_constant_identifier_names: false + constant_identifier_names: false + avoid_print: true + prefer_single_quotes: true + sort_pub_dependencies: true + avoid_unnecessary_containers: true + cancel_subscriptions: true + +analyzer: + exclude: + - '**.freezed.dart' + - '**.g.dart' + - '**/*.freezed.dart' + - '**/*.g.dart' + - '**/generated_plugin_registrant.dart' + errors: + invalid_annotation_target: ignore +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reown_walletkit/build.yaml b/packages/reown_walletkit/build.yaml new file mode 100644 index 0000000..71e3c33 --- /dev/null +++ b/packages/reown_walletkit/build.yaml @@ -0,0 +1,16 @@ +targets: + $default: + builders: + build_version: + options: + output: lib/version.dart + freezed: + generate_for: + - lib/**.dart + - test/shared/shared_test_utils.dart + json_serializable: + options: + explicit_to_json: true + generate_for: + - lib/**.dart + - test/shared/shared_test_utils.dart diff --git a/packages/reown_walletkit/example/AppIcon.png b/packages/reown_walletkit/example/AppIcon.png new file mode 100644 index 0000000..523d4fd Binary files /dev/null and b/packages/reown_walletkit/example/AppIcon.png differ diff --git a/packages/reown_walletkit/example/README.md b/packages/reown_walletkit/example/README.md new file mode 100644 index 0000000..e704054 --- /dev/null +++ b/packages/reown_walletkit/example/README.md @@ -0,0 +1,7 @@ +# dapp + +An example wallet built using flutter. + +## To Run + +`flutter run --dart-define=PROJECT_ID=xxx` \ No newline at end of file diff --git a/packages/reown_walletkit/example/analysis_options.yaml b/packages/reown_walletkit/example/analysis_options.yaml new file mode 100644 index 0000000..b677fc6 --- /dev/null +++ b/packages/reown_walletkit/example/analysis_options.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:lints/recommended.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + non_constant_identifier_names: false + constant_identifier_names: false + avoid_print: true + prefer_single_quotes: true + sort_pub_dependencies: true + avoid_unnecessary_containers: true + cancel_subscriptions: true + +analyzer: + exclude: + - '**.freezed.dart' + - '**.g.dart' + - '**/*.freezed.dart' + - '**/*.g.dart' + - '**/generated_plugin_registrant.dart' + errors: + invalid_annotation_target: ignore +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reown_walletkit/example/android/.gitignore b/packages/reown_walletkit/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/packages/reown_walletkit/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/reown_walletkit/example/android/Gemfile b/packages/reown_walletkit/example/android/Gemfile new file mode 100644 index 0000000..cdd3a6b --- /dev/null +++ b/packages/reown_walletkit/example/android/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "fastlane" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/packages/reown_walletkit/example/android/Gemfile.lock b/packages/reown_walletkit/example/android/Gemfile.lock new file mode 100644 index 0000000..1d021b0 --- /dev/null +++ b/packages/reown_walletkit/example/android/Gemfile.lock @@ -0,0 +1,228 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.3.0) + aws-partitions (1.956.0) + aws-sdk-core (3.201.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.8.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.111.0) + faraday (1.10.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.3.1) + fastlane (2.221.1) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-firebase_app_distribution (0.9.1) + google-apis-firebaseappdistribution_v1 (~> 0.3.0) + google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-firebaseappdistribution_v1 (0.3.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-firebaseappdistribution_v1alpha (0.2.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.6) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.6.2) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.15.0) + multipart-post (2.4.1) + nanaimo (0.3.0) + naturally (2.2.1) + nkf (0.2.0) + optparse (0.5.0) + os (1.1.4) + plist (3.7.1) + public_suffix (6.0.0) + rake (13.2.1) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.9) + strscan + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.5) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + strscan (3.1.0) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.5.0) + word_wrap (1.0.0) + xcodeproj (1.24.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + fastlane + fastlane-plugin-firebase_app_distribution + +BUNDLED WITH + 2.5.14 diff --git a/packages/reown_walletkit/example/android/app/build.gradle b/packages/reown_walletkit/example/android/app/build.gradle new file mode 100644 index 0000000..b646eaa --- /dev/null +++ b/packages/reown_walletkit/example/android/app/build.gradle @@ -0,0 +1,109 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('secrets.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace "com.walletconnect.flutterwallet" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.walletconnect.flutterwallet" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion 21 + targetSdkVersion 33 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + // Specifies one flavor dimension. + flavorDimensions = ["version"] + + productFlavors { + internal { + dimension "version" + applicationIdSuffix ".internal" + manifestPlaceholders = [ + appIcon: "@mipmap/ic_launcher_internal", + applicationLabel: "WalletKit Flutter (internal)", + ] + } + production { + dimension "version" + manifestPlaceholders = [ + appIcon: "@mipmap/ic_launcher", + applicationLabel: "WalletKit Flutter", + ] + } + } + + signingConfigs { + debug { + storeFile file(keystoreProperties['WC_FILENAME']) + storePassword keystoreProperties['WC_STORE_PASSWORD'] + keyAlias keystoreProperties['WC_KEYSTORE_ALIAS'] + keyPassword keystoreProperties['WC_KEY_PASSWORD'] + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } + namespace 'com.walletconnect.flutterwallet' +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/reown_walletkit/example/android/app/src/debug/AndroidManifest.xml b/packages/reown_walletkit/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/reown_walletkit/example/android/app/src/main/AndroidManifest.xml b/packages/reown_walletkit/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..98f71ee --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_walletkit/example/android/app/src/main/ic_launcher-playstore.png b/packages/reown_walletkit/example/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..13c53fd Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/ic_launcher-playstore.png differ diff --git a/packages/reown_walletkit/example/android/app/src/main/ic_launcher_internal-playstore.png b/packages/reown_walletkit/example/android/app/src/main/ic_launcher_internal-playstore.png new file mode 100644 index 0000000..3ddcfd7 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/ic_launcher_internal-playstore.png differ diff --git a/packages/reown_walletkit/example/android/app/src/main/kotlin/com/example/wallet/MainActivity.kt b/packages/reown_walletkit/example/android/app/src/main/kotlin/com/example/wallet/MainActivity.kt new file mode 100644 index 0000000..cecdd8b --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/kotlin/com/example/wallet/MainActivity.kt @@ -0,0 +1,65 @@ +package com.walletconnect.flutterwallet + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.annotation.NonNull + +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +class MainActivity: FlutterActivity() { + private val eventsChannel = "com.walletconnect.flutterwallet/events" + private val methodsChannel = "com.walletconnect.flutterwallet/methods" + + private var initialLink: String? = null + private var linksReceiver: BroadcastReceiver? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val intent: Intent? = intent + initialLink = intent?.data?.toString() + + EventChannel(flutterEngine?.dartExecutor?.binaryMessenger, eventsChannel).setStreamHandler( + object : EventChannel.StreamHandler { + override fun onListen(args: Any?, events: EventChannel.EventSink) { + linksReceiver = createChangeReceiver(events) + } + override fun onCancel(args: Any?) { + linksReceiver = null + } + } + ) + + MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, methodsChannel).setMethodCallHandler { call, result -> + if (call.method == "initialLink") { + if (initialLink != null) { + result.success(initialLink) + } + } + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + if (intent.action === Intent.ACTION_VIEW) { + linksReceiver?.onReceive(this.applicationContext, intent) + } + } + + fun createChangeReceiver(events: EventChannel.EventSink): BroadcastReceiver? { + return object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val dataString = intent.dataString ?: + events.error("UNAVAILABLE", "Link unavailable", null) + events.success(dataString) + } + } + } +} diff --git a/packages/reown_walletkit/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/reown_walletkit/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/reown_walletkit/example/android/app/src/main/res/drawable/launch_background.xml b/packages/reown_walletkit/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal.xml b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal.xml new file mode 100644 index 0000000..d7da343 --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal_round.xml b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal_round.xml new file mode 100644 index 0000000..d7da343 --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_internal_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..e09e802 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..ac5c5ab Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..d4b5dc1 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..35ff97f Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..641a045 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..d1f55da Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..d33a956 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..8b6807c Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..f3f8e65 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..3e913aa Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..fa530e8 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..69255e0 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..2d56154 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..68c67d3 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..7137082 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..af4414a Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..124e24e Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..e1ca676 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..b05e29c Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..e809421 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..84421f4 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..33a8343 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..0a9b8ff Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..5e0d598 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..1c28564 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..2d34e5d Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal.webp new file mode 100644 index 0000000..76dae9b Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_foreground.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_foreground.webp new file mode 100644 index 0000000..6fc57ea Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_foreground.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_round.webp new file mode 100644 index 0000000..43e8f16 Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_internal_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..025435d Binary files /dev/null and b/packages/reown_walletkit/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/packages/reown_walletkit/example/android/app/src/main/res/values-night/styles.xml b/packages/reown_walletkit/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/reown_walletkit/example/android/app/src/main/res/values/ic_launcher_background.xml b/packages/reown_walletkit/example/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..0b6c92b --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #F5EDFF + \ No newline at end of file diff --git a/packages/reown_walletkit/example/android/app/src/main/res/values/ic_launcher_internal_background.xml b/packages/reown_walletkit/example/android/app/src/main/res/values/ic_launcher_internal_background.xml new file mode 100644 index 0000000..9ab84df --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/values/ic_launcher_internal_background.xml @@ -0,0 +1,4 @@ + + + #F5EDFF + \ No newline at end of file diff --git a/packages/reown_walletkit/example/android/app/src/main/res/values/styles.xml b/packages/reown_walletkit/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/reown_walletkit/example/android/app/src/profile/AndroidManifest.xml b/packages/reown_walletkit/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/packages/reown_walletkit/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/reown_walletkit/example/android/build.gradle b/packages/reown_walletkit/example/android/build.gradle new file mode 100644 index 0000000..482db1b --- /dev/null +++ b/packages/reown_walletkit/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.9.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.1.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/reown_walletkit/example/android/fastlane/Appfile b/packages/reown_walletkit/example/android/fastlane/Appfile new file mode 100644 index 0000000..0270cf9 --- /dev/null +++ b/packages/reown_walletkit/example/android/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one +package_name("com.walletconnect.flutterwallet") # e.g. com.krausefx.app diff --git a/packages/reown_walletkit/example/android/fastlane/Fastfile b/packages/reown_walletkit/example/android/fastlane/Fastfile new file mode 100644 index 0000000..679bf68 --- /dev/null +++ b/packages/reown_walletkit/example/android/fastlane/Fastfile @@ -0,0 +1,98 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + + # Helper method to read the changelog + def read_changelog + changelog_file = '../../../../CHANGELOG.md' + changelog = "" + + if File.exist?(changelog_file) + File.open(changelog_file, 'r') do |file| + changelog = file.read + end + + # Split the changelog into entries based on the version header pattern + entries = changelog.split(/^##\s/) + + # Get the latest entry, which is the first one after splitting + latest_entry = entries[1] + + # Re-add the '##' header to the latest entry and remove empty lines + changelog = latest_entry.strip if latest_entry + changelog = changelog.gsub /^$\n/, '' + else + UI.user_error!("CHANGELOG.md file not found") + end + + changelog + end + + + lane :release_firebase do |options| + + slack_url = ENV['SLACK_URL'] + firebase_token = ENV['FIREBASE_TOKEN'] + firebase_wallet_id = ENV['FIREBASE_WALLET_ID'] + _flavor = ENV['FLAVOR'] + _download_url = ENV['DOWNLOAD_URL'] + + _latest_release = firebase_app_distribution_get_latest_release( + app: "#{firebase_wallet_id}", + ) + if _latest_release && _latest_release[:buildVersion] && !_latest_release[:buildVersion].empty? + _new_build_number = _latest_release[:buildVersion].to_i + 1 + else + _new_build_number = 1 + end + + _app_version = "#{options[:app_version]}" + _project_id = "#{options[:project_id]}" + + # gradle(task: 'assemble', build_type: 'Release') + sh "flutter build apk --build-name #{_app_version} --build-number #{_new_build_number} --dart-define='PROJECT_ID=#{_project_id}' --flavor #{_flavor} --release" + + changelog = read_changelog + + firebase_app_distribution( + app: "#{firebase_wallet_id}", + groups: "flutter-team, javascript-team, kotlin-team, rust-team, unity, wc-testers", + android_artifact_path: "../build/app/outputs/flutter-apk/app-#{_flavor}-release.apk", + release_notes: changelog, + android_artifact_type: "APK", + ) + + slack( + message: "🔐 WalletKit Flutter #{_app_version}-#{_flavor} (#{_new_build_number}) for 🤖 Android successfully released!\n\n", + default_payloads: [], + attachment_properties: { + fields: [ + { + title: "CHANGELOG", + value: changelog, + }, + { + title: "LINK", + value: "#{_download_url}", + }, + ] + } + ) + + end +end diff --git a/packages/reown_walletkit/example/android/fastlane/Pluginfile b/packages/reown_walletkit/example/android/fastlane/Pluginfile new file mode 100644 index 0000000..b18539b --- /dev/null +++ b/packages/reown_walletkit/example/android/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-firebase_app_distribution' diff --git a/packages/reown_walletkit/example/android/gradle.properties b/packages/reown_walletkit/example/android/gradle.properties new file mode 100644 index 0000000..b9a9a24 --- /dev/null +++ b/packages/reown_walletkit/example/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/packages/reown_walletkit/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/reown_walletkit/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8bc9958 --- /dev/null +++ b/packages/reown_walletkit/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip diff --git a/packages/reown_walletkit/example/android/settings.gradle b/packages/reown_walletkit/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/packages/reown_walletkit/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/MTBBarcodeScanner.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/MTBBarcodeScanner.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/MTBBarcodeScanner.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus-connectivity_plus_privacy.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus-connectivity_plus_privacy.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus-connectivity_plus_privacy.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/package_info_plus-package_info_plus_privacy.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/package_info_plus-package_info_plus_privacy.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/package_info_plus-package_info_plus_privacy.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/package_info_plus.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/package_info_plus.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/package_info_plus.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/qr_bar_code_scanner_dialog.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/qr_bar_code_scanner_dialog.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/qr_bar_code_scanner_dialog.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/qr_code_scanner.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/qr_code_scanner.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/qr_code_scanner.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation-shared_preferences_foundation_privacy.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation-shared_preferences_foundation_privacy.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation-shared_preferences_foundation_privacy.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios-url_launcher_ios_privacy.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios-url_launcher_ios_privacy.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios-url_launcher_ios_privacy.build/dgph differ diff --git a/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios.build/dgph b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios.build/dgph new file mode 100644 index 0000000..189301b Binary files /dev/null and b/packages/reown_walletkit/example/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios.build/dgph differ diff --git a/packages/reown_walletkit/example/ios/.gitignore b/packages/reown_walletkit/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/packages/reown_walletkit/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/reown_walletkit/example/ios/Flutter/AppFrameworkInfo.plist b/packages/reown_walletkit/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..1dc6cf7 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/packages/reown_walletkit/example/ios/Flutter/Debug.xcconfig b/packages/reown_walletkit/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..4e71b8d --- /dev/null +++ b/packages/reown_walletkit/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-internal.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_walletkit/example/ios/Flutter/Release.xcconfig b/packages/reown_walletkit/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..daaf1c7 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-internal.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/reown_walletkit/example/ios/Gemfile b/packages/reown_walletkit/example/ios/Gemfile new file mode 100644 index 0000000..7a118b4 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/packages/reown_walletkit/example/ios/Gemfile.lock b/packages/reown_walletkit/example/ios/Gemfile.lock new file mode 100644 index 0000000..550231d --- /dev/null +++ b/packages/reown_walletkit/example/ios/Gemfile.lock @@ -0,0 +1,220 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.3.0) + aws-partitions (1.956.0) + aws-sdk-core (3.201.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.8.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.111.0) + faraday (1.10.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.3.1) + fastlane (2.221.1) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.6) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.6.2) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.15.0) + multipart-post (2.4.1) + nanaimo (0.3.0) + naturally (2.2.1) + nkf (0.2.0) + optparse (0.5.0) + os (1.1.4) + plist (3.7.1) + public_suffix (6.0.0) + rake (13.2.1) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.9) + strscan + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.5) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + strscan (3.1.0) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.5.0) + word_wrap (1.0.0) + xcodeproj (1.24.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.5.14 diff --git a/packages/reown_walletkit/example/ios/Podfile b/packages/reown_walletkit/example/ios/Podfile new file mode 100644 index 0000000..2562dc1 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug-production' => :debug, + 'Profile-production' => :release, + 'Release-production' => :release, + 'Debug-internal' => :debug, + 'Profile-internal' => :release, + 'Release-internal' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/reown_walletkit/example/ios/Podfile.lock b/packages/reown_walletkit/example/ios/Podfile.lock new file mode 100644 index 0000000..8cc5efd --- /dev/null +++ b/packages/reown_walletkit/example/ios/Podfile.lock @@ -0,0 +1,61 @@ +PODS: + - connectivity_plus (0.0.1): + - Flutter + - FlutterMacOS + - Flutter (1.0.0) + - MTBBarcodeScanner (5.0.11) + - package_info_plus (0.4.5): + - Flutter + - qr_bar_code_scanner_dialog (0.0.1): + - Flutter + - qr_code_scanner (0.2.0): + - Flutter + - MTBBarcodeScanner + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) + - Flutter (from `Flutter`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - qr_bar_code_scanner_dialog (from `.symlinks/plugins/qr_bar_code_scanner_dialog/ios`) + - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +SPEC REPOS: + trunk: + - MTBBarcodeScanner + +EXTERNAL SOURCES: + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/darwin" + Flutter: + :path: Flutter + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + qr_bar_code_scanner_dialog: + :path: ".symlinks/plugins/qr_bar_code_scanner_dialog/ios" + qr_code_scanner: + :path: ".symlinks/plugins/qr_code_scanner/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + qr_bar_code_scanner_dialog: d59c27f37c96ef8649711e6eee8033a69191f907 + qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + +PODFILE CHECKSUM: 0772a2bd8cd4c7aaeb2576ddfaf6b03be722593b + +COCOAPODS: 1.15.2 diff --git a/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b2aa74d --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,844 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 091D8F542C4A7A5000904D6C /* Info-internal.plist in Resources */ = {isa = PBXBuildFile; fileRef = 091D8F532C4A7A5000904D6C /* Info-internal.plist */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + C17F95A96B51DDC5CF9C9327 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C4AAD78C53DE9EE1DA34C97 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 091D8F532C4A7A5000904D6C /* Info-internal.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-internal.plist"; sourceTree = ""; }; + 0978D7E02C6B682E00E3593C /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 0C3406B961A1CFCF33E412D2 /* Pods-Runner.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-internal.xcconfig"; sourceTree = ""; }; + 0C4AAD78C53DE9EE1DA34C97 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 659F0AF64858A4958A0A2B80 /* Pods-Runner.profile-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-production.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 96AF370382365434B2E5A6F9 /* Pods-Runner.profile-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-internal.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BD66B248B34C2E571B8129E0 /* Pods-Runner.debug-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-production.xcconfig"; sourceTree = ""; }; + DF80EF547400CE9289F59424 /* Pods-Runner.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-production.xcconfig"; sourceTree = ""; }; + F8521F3F313B041007D3925C /* Pods-Runner.debug-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-internal.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C17F95A96B51DDC5CF9C9327 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3DD2F581E63356A0269F45E2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0C4AAD78C53DE9EE1DA34C97 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + C4281BF2C021B5570082F0DC /* Pods */, + 3DD2F581E63356A0269F45E2 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 0978D7E02C6B682E00E3593C /* Runner.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 091D8F532C4A7A5000904D6C /* Info-internal.plist */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + C4281BF2C021B5570082F0DC /* Pods */ = { + isa = PBXGroup; + children = ( + BD66B248B34C2E571B8129E0 /* Pods-Runner.debug-production.xcconfig */, + F8521F3F313B041007D3925C /* Pods-Runner.debug-internal.xcconfig */, + DF80EF547400CE9289F59424 /* Pods-Runner.release-production.xcconfig */, + 0C3406B961A1CFCF33E412D2 /* Pods-Runner.release-internal.xcconfig */, + 659F0AF64858A4958A0A2B80 /* Pods-Runner.profile-production.xcconfig */, + 96AF370382365434B2E5A6F9 /* Pods-Runner.profile-internal.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 98196377CF9B4B6BD88D7036 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 027540888B75CC744D18CBF7 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 091D8F542C4A7A5000904D6C /* Info-internal.plist in Resources */, + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 027540888B75CC744D18CBF7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 98196377CF9B4B6BD88D7036 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 09A0EDBE2C4A6FFF0018ABF6 /* Debug-internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-internal"; + }; + 09A0EDBF2C4A6FFF0018ABF6 /* Debug-internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-internal"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "Runner/Info-internal.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "WalletKit Flutter Internal"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterwallet.internal; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.walletconnect.flutterwallet.internal"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-internal"; + }; + 09A0EDC02C4A70120018ABF6 /* Release-internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-internal"; + }; + 09A0EDC12C4A70120018ABF6 /* Release-internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-internal"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "Runner/Info-internal.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "WalletKit Flutter Internal"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterwallet.internal; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterwallet.internal"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-internal"; + }; + 09A0EDC22C4A70190018ABF6 /* Profile-internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-internal"; + }; + 09A0EDC32C4A70190018ABF6 /* Profile-internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-internal"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "Runner/Info-internal.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "WalletKit Flutter Internal"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterwallet.internal; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.walletconnect.flutterwallet.internal"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-internal"; + }; + 249021D3217E4FDB00AE95B9 /* Profile-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-production"; + }; + 249021D4217E4FDB00AE95B9 /* Profile-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "WalletKit Flutter"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterwallet; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.walletconnect.flutterwallet"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-production"; + }; + 97C147031CF9000F007C117D /* Debug-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-production"; + }; + 97C147041CF9000F007C117D /* Release-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-production"; + }; + 97C147061CF9000F007C117D /* Debug-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "WalletKit Flutter"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterwallet; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.walletconnect.flutterwallet"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-production"; + }; + 97C147071CF9000F007C117D /* Release-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "WalletKit Flutter"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterwallet; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterwallet"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-production"; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug-production */, + 09A0EDBE2C4A6FFF0018ABF6 /* Debug-internal */, + 97C147041CF9000F007C117D /* Release-production */, + 09A0EDC02C4A70120018ABF6 /* Release-internal */, + 249021D3217E4FDB00AE95B9 /* Profile-production */, + 09A0EDC22C4A70190018ABF6 /* Profile-internal */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-production"; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug-production */, + 09A0EDBF2C4A6FFF0018ABF6 /* Debug-internal */, + 97C147071CF9000F007C117D /* Release-production */, + 09A0EDC12C4A70120018ABF6 /* Release-internal */, + 249021D4217E4FDB00AE95B9 /* Profile-production */, + 09A0EDC32C4A70190018ABF6 /* Profile-internal */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-production"; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/reown_walletkit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/internal.xcscheme b/packages/reown_walletkit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/internal.xcscheme new file mode 100644 index 0000000..a6e93f1 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/internal.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_walletkit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme b/packages/reown_walletkit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme new file mode 100644 index 0000000..8c0bdda --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_walletkit/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/reown_walletkit/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/reown_walletkit/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_walletkit/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_walletkit/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/reown_walletkit/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/reown_walletkit/example/ios/Runner/AppDelegate.swift b/packages/reown_walletkit/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6888081 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,98 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + + private static let EVENTS_CHANNEL = "com.walletconnect.flutterwallet/events" + private static let METHODS_CHANNEL = "com.walletconnect.flutterwallet/methods" + + private var eventsChannel: FlutterEventChannel? + private var methodsChannel: FlutterMethodChannel? + var initialLink: String? + + private let linkStreamHandler = LinkStreamHandler() + + override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + GeneratedPluginRegistrant.register(with: self) + + let controller = window.rootViewController as! FlutterViewController + eventsChannel = FlutterEventChannel(name: AppDelegate.EVENTS_CHANNEL, binaryMessenger: controller.binaryMessenger) + eventsChannel?.setStreamHandler(linkStreamHandler) + + methodsChannel = FlutterMethodChannel(name: AppDelegate.METHODS_CHANNEL, binaryMessenger: controller.binaryMessenger) + methodsChannel?.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in + if (call.method == "initialLink") { + if let link = self?.initialLink { + let handled = self?.linkStreamHandler.handleLink(link) + if (handled == true) { + self?.initialLink = nil + } + } + } + }) + + // Add your deep link handling logic here + if let url = launchOptions?[.url] as? URL { + self.initialLink = url.absoluteString + } + + if let userActivityDictionary = launchOptions?[.userActivityDictionary] as? [String: Any], + let userActivity = userActivityDictionary["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity, + userActivity.activityType == NSUserActivityTypeBrowsingWeb { + + handleIncomingUniversalLink(userActivity: userActivity) + } + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return linkStreamHandler.handleLink(url.absoluteString) + } + + override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb { + handleIncomingUniversalLink(userActivity: userActivity) + return true + } + return false + } + + private func handleIncomingUniversalLink(userActivity: NSUserActivity) { + if let url = userActivity.webpageURL { + // Handle the URL, navigate to appropriate screen + print("App launched with Universal Link: \(url.absoluteString)") + let handled = linkStreamHandler.handleLink(url.absoluteString) + if (!handled){ + self.initialLink = url.absoluteString + } + } + } +} + +class LinkStreamHandler: NSObject, FlutterStreamHandler { + var eventSink: FlutterEventSink? + var queuedLinks = [String]() + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + self.eventSink = events + queuedLinks.forEach({ events($0) }) + queuedLinks.removeAll() + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + self.eventSink = nil + return nil + } + + func handleLink(_ link: String) -> Bool { + guard let eventSink = eventSink else { + queuedLinks.append(link) + return false + } + eventSink(link) + return true + } +} diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/100.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/100.png new file mode 100644 index 0000000..8696c5d Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/100.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/1024.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/1024.png new file mode 100644 index 0000000..9ee2262 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/1024.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/114.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/114.png new file mode 100644 index 0000000..c19ba2d Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/114.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/120.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/120.png new file mode 100644 index 0000000..b30d84b Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/120.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/144.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/144.png new file mode 100644 index 0000000..84edb17 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/144.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/152.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/152.png new file mode 100644 index 0000000..2534670 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/152.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/167.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/167.png new file mode 100644 index 0000000..92d8edc Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/167.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/180.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/180.png new file mode 100644 index 0000000..6b246e1 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/180.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/20.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/20.png new file mode 100644 index 0000000..ee657c6 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/20.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/29.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/29.png new file mode 100644 index 0000000..4ad690f Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/29.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/40.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/40.png new file mode 100644 index 0000000..5c1d348 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/40.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/50.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/50.png new file mode 100644 index 0000000..b9aceb3 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/50.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/57.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/57.png new file mode 100644 index 0000000..a71855d Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/57.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/58.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/58.png new file mode 100644 index 0000000..3c9620c Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/58.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/60.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/60.png new file mode 100644 index 0000000..0df41b3 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/60.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/72.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/72.png new file mode 100644 index 0000000..5108855 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/72.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/76.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/76.png new file mode 100644 index 0000000..9252423 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/76.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/80.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/80.png new file mode 100644 index 0000000..7346ad1 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/80.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/87.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/87.png new file mode 100644 index 0000000..3564612 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/87.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/Contents.json b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/Contents.json new file mode 100644 index 0000000..4fdf882 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon-internal.appiconset/Contents.json @@ -0,0 +1,158 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..f4d4fe6 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..523d4fd Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..d1a65df Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..d405d48 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..9832e38 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..8f5045c Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..3fc9594 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..cab830c Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..0f5bd6c Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..f091c53 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..7f021b0 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..81c5036 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..700ec3c Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..6e6d502 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..90950b3 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..908249b Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..b55a1f5 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..a7a56e8 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..e978232 Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..4fdf882 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,158 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/Contents.json b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..cfbbc77 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "appstore.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "appstore@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "appstore@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore.png new file mode 100644 index 0000000..396c5ab Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore@2x.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore@2x.png new file mode 100644 index 0000000..8752e4e Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore@2x.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore@3x.png b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore@3x.png new file mode 100644 index 0000000..523d4fd Binary files /dev/null and b/packages/reown_walletkit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/appstore@3x.png differ diff --git a/packages/reown_walletkit/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/reown_walletkit/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..7977954 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_walletkit/example/ios/Runner/Base.lproj/Main.storyboard b/packages/reown_walletkit/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..6b254bf --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_walletkit/example/ios/Runner/Info-internal.plist b/packages/reown_walletkit/example/ios/Runner/Info-internal.plist new file mode 100644 index 0000000..69256a3 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Info-internal.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + WalletKit Flutter Internal + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.walletconnect.flutterwallet.internal + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + WalletKit Flutter Internal + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.walletconnect.flutterwallet.internal + CFBundleURLSchemes + + wcflutterwallet-internal + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + NSCameraUsageDescription + This app needs camera access to scan QR codes + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + FlutterDeepLinkingEnabled + + + diff --git a/packages/reown_walletkit/example/ios/Runner/Info.plist b/packages/reown_walletkit/example/ios/Runner/Info.plist new file mode 100644 index 0000000..71815a7 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Info.plist @@ -0,0 +1,72 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + WalletKit Flutter + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.walletconnect.flutterwallet + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + WalletKit Flutter + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.walletconnect.flutterwallet + CFBundleURLSchemes + + wcflutterwallet + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + FlutterDeepLinkingEnabled + + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + NSCameraUsageDescription + This app needs camera access to scan QR codes + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + FlutterDeepLinkingEnabled + + + diff --git a/packages/reown_walletkit/example/ios/Runner/Runner-Bridging-Header.h b/packages/reown_walletkit/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/reown_walletkit/example/ios/Runner/Runner.entitlements b/packages/reown_walletkit/example/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..da67a55 --- /dev/null +++ b/packages/reown_walletkit/example/ios/Runner/Runner.entitlements @@ -0,0 +1,11 @@ + + + + + com.apple.developer.associated-domains + + applinks:lab.web3modal.com + applinks:dev.lab.web3modal.com + + + diff --git a/packages/reown_walletkit/example/ios/fastlane/Appfile b/packages/reown_walletkit/example/ios/fastlane/Appfile new file mode 100644 index 0000000..de1b076 --- /dev/null +++ b/packages/reown_walletkit/example/ios/fastlane/Appfile @@ -0,0 +1,5 @@ +itc_team_id("123564616") # App Store Connect Team ID +team_id("W5R8AG9K22") # Developer Portal Team ID + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/packages/reown_walletkit/example/ios/fastlane/Fastfile b/packages/reown_walletkit/example/ios/fastlane/Fastfile new file mode 100644 index 0000000..62c7cd2 --- /dev/null +++ b/packages/reown_walletkit/example/ios/fastlane/Fastfile @@ -0,0 +1,141 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" + +default_platform(:ios) + +platform :ios do + + # Helper method to read the changelog + def read_changelog + changelog_file = '../../../../CHANGELOG.md' + changelog = "" + + if File.exist?(changelog_file) + File.open(changelog_file, 'r') do |file| + changelog = file.read + end + + # Split the changelog into entries based on the version header pattern + entries = changelog.split(/^##\s/) + + # Get the latest entry, which is the first one after splitting + latest_entry = entries[1] + + # Re-add the '##' header to the latest entry and remove empty lines + changelog = latest_entry.strip if latest_entry + changelog = changelog.gsub /^$\n/, '' + else + UI.user_error!("CHANGELOG.md file not found") + end + + changelog + end + + lane :release_testflight do |options| + + # Setup the keychain and match to work with CI + setup_ci + + match_password = ENV['MATCH_PASSWORD'] + slack_url = ENV['SLACK_URL'] + app_identifier = ENV['BUNDLE_ID'] + _testflight_url = ENV['TESTFLIGHT_URL'] + + api_key = app_store_connect_api_key( + key_id: options[:app_store_key_id], + issuer_id: options[:apple_issuer_id], + key_content: options[:app_store_connect_key], + duration: 1200, + in_house: false, + ) + + match( + readonly: false, + type: "appstore", + app_identifier: "#{app_identifier}", + git_url: options[:match_git_url], + git_basic_authorization: options[:token], + api_key: api_key, + include_all_certificates: true, + force_for_new_devices: true, + force_for_new_certificates: true, + ) + + number = latest_testflight_build_number( + app_identifier: "#{app_identifier}", + username: options[:username], + ) + if number && !number.to_s.empty? + new_build_number = number.to_i + 1 + else + # Handle the case where there is no previous build number + new_build_number = 1 + end + + increment_build_number( + build_number: new_build_number, + xcodeproj: "Runner.xcodeproj" + ) + + _flavor = options[:flavor] + + gym( + configuration: "Release-#{_flavor}", + # project: "Runner.xcodeproj", + workspace: "Runner.xcworkspace", + scheme: _flavor, + export_method: "app-store", + clean: true, + xcargs: "PROJECT_ID='#{options[:project_id]}'" + ) + + changelog = read_changelog + + upload_to_testflight( + apple_id: options[:app_id], + app_version: options[:app_version], + build_number: "#{new_build_number}", + app_identifier: "#{app_identifier}", + changelog: changelog, + distribute_external: true, + notify_external_testers: true, + skip_waiting_for_build_processing: false, + groups: ["External Testers"] + ) + + slack( + message: "🔐 WalletKit Flutter #{options[:app_version]}-#{_flavor} (#{new_build_number}) for 🍎 iOS successfully released!\n\n", + default_payloads: [], + attachment_properties: { + fields: [ + { + title: "CHANGELOG", + value: changelog, + }, + { + title: "LINK", + value: "#{_testflight_url}", + }, + ] + } + ) + + clean_build_artifacts() + + end + +end diff --git a/packages/reown_walletkit/example/lib/dependencies/bip32/bip32_base.dart b/packages/reown_walletkit/example/lib/dependencies/bip32/bip32_base.dart new file mode 100644 index 0000000..d1254cb --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bip32/bip32_base.dart @@ -0,0 +1,267 @@ +import 'dart:typed_data'; +import 'package:bs58/bs58.dart'; + +import 'utils/crypto.dart'; +import 'utils/ecurve.dart' as ecc; +import 'utils/wif.dart' as wif; +import 'dart:convert'; + +class Bip32Type { + int public; + int private; + Bip32Type({required this.public, required this.private}); +} + +class NetworkType { + int wif; + Bip32Type bip32; + NetworkType({required this.wif, required this.bip32}); +} + +final BITCOIN = NetworkType( + wif: 0x80, + bip32: Bip32Type( + public: 0x0488b21e, + private: 0x0488ade4, + ), +); +const HIGHEST_BIT = 0x80000000; +const UINT31_MAX = 2147483647; // 2^31 - 1 +const UINT32_MAX = 4294967295; // 2^32 - 1 + +/// Checks if you are awesome. Spoiler: you are. +class BIP32 { + Uint8List? _d; + Uint8List? _Q; + Uint8List chainCode; + int depth = 0; + int index = 0; + NetworkType network; + int parentFingerprint = 0x00000000; + BIP32(this._d, this._Q, this.chainCode, this.network); + + Uint8List get publicKey { + _Q ??= ecc.pointFromScalar(_d!, true)!; + return _Q!; + } + + Uint8List? get privateKey => _d; + Uint8List get identifier => hash160(publicKey); + Uint8List get fingerprint => identifier.sublist(0, 4); + + bool isNeutered() { + return _d == null; + } + + BIP32 neutered() { + final neutered = BIP32.fromPublicKey(publicKey, chainCode, network); + neutered.depth = depth; + neutered.index = index; + neutered.parentFingerprint = parentFingerprint; + return neutered; + } + + String toBase58() { + final version = + (!isNeutered()) ? network.bip32.private : network.bip32.public; + Uint8List buffer = Uint8List(78); + ByteData bytes = buffer.buffer.asByteData(); + bytes.setUint32(0, version); + bytes.setUint8(4, depth); + bytes.setUint32(5, parentFingerprint); + bytes.setUint32(9, index); + buffer.setRange(13, 45, chainCode); + if (!isNeutered()) { + bytes.setUint8(45, 0); + buffer.setRange(46, 78, privateKey!); + } else { + buffer.setRange(45, 78, publicKey); + } + return base58.encode(buffer); + } + + String toWIF() { + if (privateKey == null) { + throw ArgumentError('Missing private key'); + } + return wif.encode( + wif.WIF( + version: network.wif, + privateKey: privateKey!, + compressed: true, + ), + ); + } + + BIP32 derive(int index) { + if (index > UINT32_MAX || index < 0) { + throw ArgumentError('Expected UInt32'); + } + final isHardened = index >= HIGHEST_BIT; + Uint8List data = Uint8List(37); + if (isHardened) { + if (isNeutered()) { + throw ArgumentError('Missing private key for hardened child key'); + } + data[0] = 0x00; + data.setRange(1, 33, privateKey!); + data.buffer.asByteData().setUint32(33, index); + } else { + data.setRange(0, 33, publicKey); + data.buffer.asByteData().setUint32(33, index); + } + final I = hmacSHA512(chainCode, data); + final IL = I.sublist(0, 32); + final IR = I.sublist(32); + if (!ecc.isPrivate(IL)) { + return derive(index + 1); + } + BIP32 hd; + if (!isNeutered()) { + final ki = ecc.privateAdd(privateKey!, IL); + if (ki == null) return derive(index + 1); + hd = BIP32.fromPrivateKey(ki, IR, network); + } else { + final ki = ecc.pointAddScalar(publicKey, IL, true); + if (ki == null) return derive(index + 1); + hd = BIP32.fromPublicKey(ki, IR, network); + } + hd.depth = depth + 1; + hd.index = index; + hd.parentFingerprint = fingerprint.buffer.asByteData().getUint32(0); + return hd; + } + + BIP32 deriveHardened(int index) { + if (index > UINT31_MAX || index < 0) { + throw ArgumentError('Expected UInt31'); + } + return derive(index + HIGHEST_BIT); + } + + BIP32 derivePath(String path) { + final regex = RegExp(r"^(m\/)?(\d+'?\/)*\d+'?$"); + if (!regex.hasMatch(path)) { + throw ArgumentError('Expected BIP32 Path'); + } + List splitPath = path.split('/'); + if (splitPath[0] == 'm') { + if (parentFingerprint != 0) { + throw ArgumentError('Expected master, got child'); + } + splitPath = splitPath.sublist(1); + } + return splitPath.fold(this, (BIP32 prevHd, String indexStr) { + int index; + if (indexStr.substring(indexStr.length - 1) == "'") { + index = int.parse(indexStr.substring(0, indexStr.length - 1)); + return prevHd.deriveHardened(index); + } else { + index = int.parse(indexStr); + return prevHd.derive(index); + } + }); + } + + sign(Uint8List hash) { + return ecc.sign(hash, privateKey!); + } + + verify(Uint8List hash, Uint8List signature) { + return ecc.verify(hash, publicKey, signature); + } + + factory BIP32.fromBase58(String string, [NetworkType? nw]) { + Uint8List buffer = base58.decode(string); + if (buffer.length != 78) { + throw ArgumentError('Invalid buffer length'); + } + NetworkType network = nw ?? BITCOIN; + ByteData bytes = buffer.buffer.asByteData(); + // 4 bytes: version bytes + var version = bytes.getUint32(0); + if (version != network.bip32.private && version != network.bip32.public) { + throw ArgumentError('Invalid network version'); + } + // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... + var depth = buffer[4]; + + // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) + var parentFingerprint = bytes.getUint32(5); + if (depth == 0) { + if (parentFingerprint != 0x00000000) { + throw ArgumentError('Invalid parent fingerprint'); + } + } + + // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. + // This is encoded in MSB order. (0x00000000 if master key) + var index = bytes.getUint32(9); + if (depth == 0 && index != 0) { + throw ArgumentError('Invalid index'); + } + + // 32 bytes: the chain code + Uint8List chainCode = buffer.sublist(13, 45); + BIP32 hd; + + // 33 bytes: private key data (0x00 + k) + if (version == network.bip32.private) { + if (bytes.getUint8(45) != 0x00) { + throw ArgumentError('Invalid private key'); + } + Uint8List k = buffer.sublist(46, 78); + hd = BIP32.fromPrivateKey(k, chainCode, network); + } else { + // 33 bytes: public key data (0x02 + X or 0x03 + X) + Uint8List X = buffer.sublist(45, 78); + hd = BIP32.fromPublicKey(X, chainCode, network); + } + hd.depth = depth; + hd.index = index; + hd.parentFingerprint = parentFingerprint; + return hd; + } + + factory BIP32.fromPublicKey( + Uint8List publicKey, + Uint8List chainCode, [ + NetworkType? nw, + ]) { + NetworkType network = nw ?? BITCOIN; + if (!ecc.isPoint(publicKey)) { + throw ArgumentError('Point is not on the curve'); + } + return BIP32(null, publicKey, chainCode, network); + } + + factory BIP32.fromPrivateKey( + Uint8List privateKey, + Uint8List chainCode, [ + NetworkType? nw, + ]) { + NetworkType network = nw ?? BITCOIN; + if (privateKey.length != 32) { + throw ArgumentError( + 'Expected property privateKey of type Buffer(Length: 32)'); + } + if (!ecc.isPrivate(privateKey)) { + throw ArgumentError('Private key not in range [1, n]'); + } + return BIP32(privateKey, null, chainCode, network); + } + + factory BIP32.fromSeed(Uint8List seed, [NetworkType? nw]) { + if (seed.length < 16) { + throw ArgumentError('Seed should be at least 128 bits'); + } + if (seed.length > 64) { + throw ArgumentError('Seed should be at most 512 bits'); + } + NetworkType network = nw ?? BITCOIN; + final I = hmacSHA512(utf8.encode('Bitcoin seed'), seed); + final IL = I.sublist(0, 32); + final IR = I.sublist(32); + return BIP32.fromPrivateKey(IL, IR, network); + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/bip32/utils/crypto.dart b/packages/reown_walletkit/example/lib/dependencies/bip32/utils/crypto.dart new file mode 100644 index 0000000..8a20ae2 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bip32/utils/crypto.dart @@ -0,0 +1,19 @@ +import 'dart:typed_data'; +import 'package:pointycastle/digests/sha512.dart'; +import 'package:pointycastle/api.dart'; +import 'package:pointycastle/macs/hmac.dart'; +import 'package:pointycastle/digests/ripemd160.dart'; +import 'package:pointycastle/digests/sha256.dart'; + +final ONE1 = Uint8List.fromList([1]); +final ZERO1 = Uint8List.fromList([0]); + +Uint8List hash160(Uint8List buffer) { + Uint8List tmp = SHA256Digest().process(buffer); + return RIPEMD160Digest().process(tmp); +} + +Uint8List hmacSHA512(Uint8List key, Uint8List data) { + final tmp = HMac(SHA512Digest(), 128)..init(KeyParameter(key)); + return tmp.process(data); +} diff --git a/packages/reown_walletkit/example/lib/dependencies/bip32/utils/ecurve.dart b/packages/reown_walletkit/example/lib/dependencies/bip32/utils/ecurve.dart new file mode 100644 index 0000000..6bf8960 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bip32/utils/ecurve.dart @@ -0,0 +1,262 @@ +import 'dart:typed_data'; +import 'package:convert/convert.dart'; +import 'package:pointycastle/ecc/curves/secp256k1.dart'; +import 'package:pointycastle/api.dart'; +import 'package:pointycastle/ecc/api.dart'; +import 'package:pointycastle/signers/ecdsa_signer.dart'; +import 'package:pointycastle/macs/hmac.dart'; +import 'package:pointycastle/digests/sha256.dart'; +// import 'package:pointycastle/src/utils.dart'; + +final ZERO32 = Uint8List.fromList(List.generate(32, (index) => 0)); +final EC_GROUP_ORDER = hex + .decode('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'); +final EC_P = hex + .decode('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'); +final secp256k1 = ECCurve_secp256k1(); +final n = secp256k1.n; +final G = secp256k1.G; +BigInt nDiv2 = n >> 1; +const THROW_BAD_PRIVATE = 'Expected Private'; +const THROW_BAD_POINT = 'Expected Point'; +const THROW_BAD_TWEAK = 'Expected Tweak'; +const THROW_BAD_HASH = 'Expected Hash'; +const THROW_BAD_SIGNATURE = 'Expected Signature'; + +bool isPrivate(Uint8List x) { + if (!isScalar(x)) return false; + return _compare(x, ZERO32) > 0 && // > 0 + _compare(x, EC_GROUP_ORDER as Uint8List) < 0; // < G +} + +bool isPoint(Uint8List p) { + if (p.length < 33) { + return false; + } + var t = p[0]; + var x = p.sublist(1, 33); + + if (_compare(x, ZERO32) == 0) { + return false; + } + if (_compare(x, EC_P as Uint8List) == 1) { + return false; + } + try { + decodeFrom(p); + } catch (err) { + return false; + } + if ((t == 0x02 || t == 0x03) && p.length == 33) { + return true; + } + var y = p.sublist(33); + if (_compare(y, ZERO32) == 0) { + return false; + } + if (_compare(y, EC_P as Uint8List) == 1) { + return false; + } + if (t == 0x04 && p.length == 65) { + return true; + } + return false; +} + +bool isScalar(Uint8List x) { + return x.length == 32; +} + +bool isOrderScalar(x) { + if (!isScalar(x)) return false; + return _compare(x, EC_GROUP_ORDER as Uint8List) < 0; // < G +} + +bool isSignature(Uint8List value) { + Uint8List r = value.sublist(0, 32); + Uint8List s = value.sublist(32, 64); + + return value.length == 64 && + _compare(r, EC_GROUP_ORDER as Uint8List) < 0 && + _compare(s, EC_GROUP_ORDER as Uint8List) < 0; +} + +bool _isPointCompressed(Uint8List p) { + return p[0] != 0x04; +} + +bool assumeCompression(bool? value, Uint8List? pubkey) { + if (value == null && pubkey != null) return _isPointCompressed(pubkey); + if (value == null) return true; + return value; +} + +Uint8List? pointFromScalar(Uint8List d, bool compressed) { + if (!isPrivate(d)) { + throw ArgumentError(THROW_BAD_PRIVATE); + } + BigInt dd = fromBuffer(d); + ECPoint pp = (G * dd) as ECPoint; + if (pp.isInfinity) return null; + return getEncoded(pp, compressed); +} + +Uint8List? pointAddScalar(Uint8List p, Uint8List tweak, bool comprsd) { + if (!isPoint(p)) throw ArgumentError(THROW_BAD_POINT); + if (!isOrderScalar(tweak)) throw ArgumentError(THROW_BAD_TWEAK); + bool compressed = assumeCompression(comprsd, p); + ECPoint? pp = decodeFrom(p); + if (_compare(tweak, ZERO32) == 0) return getEncoded(pp, compressed); + BigInt tt = fromBuffer(tweak); + ECPoint qq = (G * tt) as ECPoint; + ECPoint uu = (pp! + qq) as ECPoint; + if (uu.isInfinity) return null; + return getEncoded(uu, compressed); +} + +Uint8List? privateAdd(Uint8List d, Uint8List tweak) { + if (!isPrivate(d)) throw ArgumentError(THROW_BAD_PRIVATE); + if (!isOrderScalar(tweak)) throw ArgumentError(THROW_BAD_TWEAK); + BigInt dd = fromBuffer(d); + BigInt tt = fromBuffer(tweak); + Uint8List dt = toBuffer((dd + tt) % n); + + if (dt.length < 32) { + Uint8List padLeadingZero = Uint8List(32 - dt.length); + dt = Uint8List.fromList(padLeadingZero + dt); + } + + if (!isPrivate(dt)) return null; + return dt; +} + +Uint8List sign(Uint8List hash, Uint8List x) { + if (!isScalar(hash)) throw ArgumentError(THROW_BAD_HASH); + if (!isPrivate(x)) throw ArgumentError(THROW_BAD_PRIVATE); + ECSignature sig = deterministicGenerateK(hash, x); + Uint8List buffer = Uint8List(64); + buffer.setRange(0, 32, _encodeBigInt(sig.r)); + BigInt s; + if (sig.s.compareTo(nDiv2) > 0) { + s = n - sig.s; + } else { + s = sig.s; + } + buffer.setRange(32, 64, _encodeBigInt(s)); + return buffer; +} + +bool verify(Uint8List hash, Uint8List q, Uint8List signature) { + if (!isScalar(hash)) throw ArgumentError(THROW_BAD_HASH); + if (!isPoint(q)) throw ArgumentError(THROW_BAD_POINT); + // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (1, isSignature enforces '< n - 1') + if (!isSignature(signature)) throw ArgumentError(THROW_BAD_SIGNATURE); + + ECPoint? Q = decodeFrom(q); + BigInt r = fromBuffer(signature.sublist(0, 32)); + BigInt s = fromBuffer(signature.sublist(32, 64)); + + final signer = ECDSASigner(null, HMac(SHA256Digest(), 64)); + signer.init(false, PublicKeyParameter(ECPublicKey(Q, secp256k1))); + return signer.verifySignature(hash, ECSignature(r, s)); + /* STEP BY STEP + // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (2, enforces '> 0') + if (r.compareTo(n) >= 0) return false; + if (s.compareTo(n) >= 0) return false; + + // 1.4.2 H = Hash(M), already done by the user + // 1.4.3 e = H + BigInt e = fromBuffer(hash); + + BigInt sInv = s.modInverse(n); + BigInt u1 = (e * sInv) % n; + BigInt u2 = (r * sInv) % n; + + // 1.4.5 Compute R = (xR, yR) + // R = u1G + u2Q + ECPoint R = G * u1 + Q * u2; + + // 1.4.5 (cont.) Enforce R is not at infinity + if (R.isInfinity) return false; + + // 1.4.6 Convert the field element R.x to an integer + BigInt xR = R.x.toBigInteger(); + + // 1.4.7 Set v = xR mod n + BigInt v = xR % n; + + // 1.4.8 If v = r, output "valid", and if v != r, output "invalid" + return v.compareTo(r) == 0; + */ +} + +/// Decode a BigInt from bytes in big-endian encoding. +BigInt _decodeBigInt(List bytes) { + BigInt result = BigInt.from(0); + for (int i = 0; i < bytes.length; i++) { + result += BigInt.from(bytes[bytes.length - i - 1]) << (8 * i); + } + return result; +} + +var _byteMask = BigInt.from(0xff); + +/// Encode a BigInt into bytes using big-endian encoding. +Uint8List _encodeBigInt(BigInt number) { + int needsPaddingByte; + int rawSize; + final negativeFlag = BigInt.from(0x80); + + if (number > BigInt.zero) { + rawSize = (number.bitLength + 7) >> 3; + needsPaddingByte = + ((number >> (rawSize - 1) * 8) & negativeFlag) == negativeFlag ? 1 : 0; + + if (rawSize < 32) { + needsPaddingByte = 1; + } + } else { + needsPaddingByte = 0; + rawSize = (number.bitLength + 8) >> 3; + } + + final size = rawSize < 32 ? rawSize + needsPaddingByte : rawSize; + var result = Uint8List(size); + for (int i = 0; i < size; i++) { + result[size - i - 1] = (number & _byteMask).toInt(); + number = number >> 8; + } + return result; +} + +BigInt fromBuffer(Uint8List d) { + return _decodeBigInt(d); +} + +Uint8List toBuffer(BigInt d) { + return _encodeBigInt(d); +} + +ECPoint? decodeFrom(Uint8List P) { + return secp256k1.curve.decodePoint(P); +} + +Uint8List getEncoded(ECPoint? P, compressed) { + return P!.getEncoded(compressed); +} + +ECSignature deterministicGenerateK(Uint8List hash, Uint8List x) { + final signer = ECDSASigner(null, HMac(SHA256Digest(), 64)); + var pkp = PrivateKeyParameter(ECPrivateKey(_decodeBigInt(x), secp256k1)); + signer.init(true, pkp); +// signer.init(false, new PublicKeyParameter(new ECPublicKey(secp256k1.curve.decodePoint(x), secp256k1))); + return signer.generateSignature(hash) as ECSignature; +} + +int _compare(Uint8List a, Uint8List b) { + BigInt aa = fromBuffer(a); + BigInt bb = fromBuffer(b); + if (aa == bb) return 0; + if (aa > bb) return 1; + return -1; +} diff --git a/packages/reown_walletkit/example/lib/dependencies/bip32/utils/wif.dart b/packages/reown_walletkit/example/lib/dependencies/bip32/utils/wif.dart new file mode 100644 index 0000000..7d308ea --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bip32/utils/wif.dart @@ -0,0 +1,54 @@ +import 'dart:typed_data'; +import 'package:bs58/bs58.dart'; + +class WIF { + int version; + Uint8List privateKey; + bool compressed; + WIF( + {required this.version, + required this.privateKey, + required this.compressed}); +} + +WIF decodeRaw(Uint8List buffer, [int? version]) { + if (version != null && buffer[0] != version) { + throw ArgumentError('Invalid network version'); + } + if (buffer.length == 33) { + return WIF( + version: buffer[0], + privateKey: buffer.sublist(1, 33), + compressed: false); + } + if (buffer.length != 34) { + throw ArgumentError('Invalid WIF length'); + } + if (buffer[33] != 0x01) { + throw ArgumentError('Invalid compression flag'); + } + return WIF( + version: buffer[0], privateKey: buffer.sublist(1, 33), compressed: true); +} + +Uint8List encodeRaw(int version, Uint8List privateKey, bool compressed) { + if (privateKey.length != 32) { + throw ArgumentError('Invalid privateKey length'); + } + Uint8List result = Uint8List(compressed ? 34 : 33); + ByteData bytes = result.buffer.asByteData(); + bytes.setUint8(0, version); + result.setRange(1, 33, privateKey); + if (compressed) { + result[33] = 0x01; + } + return result; +} + +WIF decode(String string, [int? version]) { + return decodeRaw(base58.decode(string), version); +} + +String encode(WIF wif) { + return base58.encode(encodeRaw(wif.version, wif.privateKey, wif.compressed)); +} diff --git a/packages/reown_walletkit/example/lib/dependencies/bip39/bip39_base.dart b/packages/reown_walletkit/example/lib/dependencies/bip39/bip39_base.dart new file mode 100644 index 0000000..4f2f2d0 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bip39/bip39_base.dart @@ -0,0 +1,150 @@ +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:pointycastle/digests/sha256.dart'; + +import 'utils/pbkdf2.dart'; +import 'wordlists/english.dart'; + +const int _SIZE_BYTE = 255; +const _INVALID_MNEMONIC = 'Invalid mnemonic'; +const _INVALID_ENTROPY = 'Invalid entropy'; +const _INVALID_CHECKSUM = 'Invalid mnemonic checksum'; + +typedef RandomBytes = Uint8List Function(int size); + +int _binaryToByte(String binary) { + return int.parse(binary, radix: 2); +} + +String _bytesToBinary(Uint8List bytes) { + return bytes.map((byte) => byte.toRadixString(2).padLeft(8, '0')).join(''); +} + +//Uint8List _createUint8ListFromString( String s ) { +// var ret = new Uint8List(s.length); +// for( var i=0 ; i 32) { + throw ArgumentError(_INVALID_ENTROPY); + } + if (entropy.length % 4 != 0) { + throw ArgumentError(_INVALID_ENTROPY); + } + final entropyBits = _bytesToBinary(entropy); + final checksumBits = _deriveChecksumBits(entropy); + final bits = entropyBits + checksumBits; + final regex = RegExp(r'.{1,11}', caseSensitive: false, multiLine: false); + final chunks = regex + .allMatches(bits) + .map((match) => match.group(0)!) + .toList(growable: false); + List wordlist = WORDLIST; + String words = + chunks.map((binary) => wordlist[_binaryToByte(binary)]).join(' '); + return words; +} + +Uint8List mnemonicToSeed(String mnemonic, {String passphrase = ''}) { + final pbkdf2 = PBKDF2(); + return pbkdf2.process(mnemonic, passphrase: passphrase); +} + +String mnemonicToSeedHex(String mnemonic, {String passphrase = ''}) { + return mnemonicToSeed(mnemonic, passphrase: passphrase).map((byte) { + return byte.toRadixString(16).padLeft(2, '0'); + }).join(''); +} + +bool validateMnemonic(String mnemonic) { + try { + mnemonicToEntropy(mnemonic); + } catch (e) { + return false; + } + return true; +} + +String mnemonicToEntropy(mnemonic) { + var words = mnemonic.split(' '); + if (words.length % 3 != 0) { + throw ArgumentError(_INVALID_MNEMONIC); + } + // convert word indices to 11 bit binary strings + final bits = words.map((word) { + final index = WORDLIST.indexOf(word); + if (index == -1) { + throw ArgumentError(_INVALID_MNEMONIC); + } + return index.toRadixString(2).padLeft(11, '0'); + }).join(''); + // split the binary string into ENT/CS + final dividerIndex = (bits.length / 33).floor() * 32; + final entropyBits = bits.substring(0, dividerIndex); + final checksumBits = bits.substring(dividerIndex); + + // calculate the checksum and compare + final regex = RegExp(r'.{1,8}'); + final entropyBytes = Uint8List.fromList(regex + .allMatches(entropyBits) + .map((match) => _binaryToByte(match.group(0)!)) + .toList(growable: false)); + if (entropyBytes.length < 16) { + throw StateError(_INVALID_ENTROPY); + } + if (entropyBytes.length > 32) { + throw StateError(_INVALID_ENTROPY); + } + if (entropyBytes.length % 4 != 0) { + throw StateError(_INVALID_ENTROPY); + } + final newChecksum = _deriveChecksumBits(entropyBytes); + if (newChecksum != checksumBits) { + throw StateError(_INVALID_CHECKSUM); + } + return entropyBytes.map((byte) { + return byte.toRadixString(16).padLeft(2, '0'); + }).join(''); +} +// List> _loadWordList() { +// final res = new Resource('package:bip39/src/wordlists/english.json').readAsString(); +// List words = (json.decode(res) as List).map((e) => e.toString()).toList(); +// return words; +// } diff --git a/packages/reown_walletkit/example/lib/dependencies/bip39/utils/pbkdf2.dart b/packages/reown_walletkit/example/lib/dependencies/bip39/utils/pbkdf2.dart new file mode 100644 index 0000000..0227d4e --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bip39/utils/pbkdf2.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:pointycastle/digests/sha512.dart'; +import 'package:pointycastle/key_derivators/api.dart'; +import 'package:pointycastle/key_derivators/pbkdf2.dart'; +import 'package:pointycastle/macs/hmac.dart'; + +class PBKDF2 { + final int blockLength; + final int iterationCount; + final int desiredKeyLength; + final String saltPrefix = 'mnemonic'; + + final PBKDF2KeyDerivator _derivator; + + PBKDF2({ + this.blockLength = 128, + this.iterationCount = 2048, + this.desiredKeyLength = 64, + }) : _derivator = PBKDF2KeyDerivator(HMac(SHA512Digest(), blockLength)); + + Uint8List process(String mnemonic, {passphrase = ''}) { + final salt = Uint8List.fromList(utf8.encode(saltPrefix + passphrase)); + _derivator.reset(); + _derivator.init(Pbkdf2Parameters(salt, iterationCount, desiredKeyLength)); + return _derivator.process(Uint8List.fromList(mnemonic.codeUnits)); + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/bip39/wordlists/english.dart b/packages/reown_walletkit/example/lib/dependencies/bip39/wordlists/english.dart new file mode 100644 index 0000000..28e0d7a --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bip39/wordlists/english.dart @@ -0,0 +1,2050 @@ +const WORDLIST = [ + 'abandon', + 'ability', + 'able', + 'about', + 'above', + 'absent', + 'absorb', + 'abstract', + 'absurd', + 'abuse', + 'access', + 'accident', + 'account', + 'accuse', + 'achieve', + 'acid', + 'acoustic', + 'acquire', + 'across', + 'act', + 'action', + 'actor', + 'actress', + 'actual', + 'adapt', + 'add', + 'addict', + 'address', + 'adjust', + 'admit', + 'adult', + 'advance', + 'advice', + 'aerobic', + 'affair', + 'afford', + 'afraid', + 'again', + 'age', + 'agent', + 'agree', + 'ahead', + 'aim', + 'air', + 'airport', + 'aisle', + 'alarm', + 'album', + 'alcohol', + 'alert', + 'alien', + 'all', + 'alley', + 'allow', + 'almost', + 'alone', + 'alpha', + 'already', + 'also', + 'alter', + 'always', + 'amateur', + 'amazing', + 'among', + 'amount', + 'amused', + 'analyst', + 'anchor', + 'ancient', + 'anger', + 'angle', + 'angry', + 'animal', + 'ankle', + 'announce', + 'annual', + 'another', + 'answer', + 'antenna', + 'antique', + 'anxiety', + 'any', + 'apart', + 'apology', + 'appear', + 'apple', + 'approve', + 'april', + 'arch', + 'arctic', + 'area', + 'arena', + 'argue', + 'arm', + 'armed', + 'armor', + 'army', + 'around', + 'arrange', + 'arrest', + 'arrive', + 'arrow', + 'art', + 'artefact', + 'artist', + 'artwork', + 'ask', + 'aspect', + 'assault', + 'asset', + 'assist', + 'assume', + 'asthma', + 'athlete', + 'atom', + 'attack', + 'attend', + 'attitude', + 'attract', + 'auction', + 'audit', + 'august', + 'aunt', + 'author', + 'auto', + 'autumn', + 'average', + 'avocado', + 'avoid', + 'awake', + 'aware', + 'away', + 'awesome', + 'awful', + 'awkward', + 'axis', + 'baby', + 'bachelor', + 'bacon', + 'badge', + 'bag', + 'balance', + 'balcony', + 'ball', + 'bamboo', + 'banana', + 'banner', + 'bar', + 'barely', + 'bargain', + 'barrel', + 'base', + 'basic', + 'basket', + 'battle', + 'beach', + 'bean', + 'beauty', + 'because', + 'become', + 'beef', + 'before', + 'begin', + 'behave', + 'behind', + 'believe', + 'below', + 'belt', + 'bench', + 'benefit', + 'best', + 'betray', + 'better', + 'between', + 'beyond', + 'bicycle', + 'bid', + 'bike', + 'bind', + 'biology', + 'bird', + 'birth', + 'bitter', + 'black', + 'blade', + 'blame', + 'blanket', + 'blast', + 'bleak', + 'bless', + 'blind', + 'blood', + 'blossom', + 'blouse', + 'blue', + 'blur', + 'blush', + 'board', + 'boat', + 'body', + 'boil', + 'bomb', + 'bone', + 'bonus', + 'book', + 'boost', + 'border', + 'boring', + 'borrow', + 'boss', + 'bottom', + 'bounce', + 'box', + 'boy', + 'bracket', + 'brain', + 'brand', + 'brass', + 'brave', + 'bread', + 'breeze', + 'brick', + 'bridge', + 'brief', + 'bright', + 'bring', + 'brisk', + 'broccoli', + 'broken', + 'bronze', + 'broom', + 'brother', + 'brown', + 'brush', + 'bubble', + 'buddy', + 'budget', + 'buffalo', + 'build', + 'bulb', + 'bulk', + 'bullet', + 'bundle', + 'bunker', + 'burden', + 'burger', + 'burst', + 'bus', + 'business', + 'busy', + 'butter', + 'buyer', + 'buzz', + 'cabbage', + 'cabin', + 'cable', + 'cactus', + 'cage', + 'cake', + 'call', + 'calm', + 'camera', + 'camp', + 'can', + 'canal', + 'cancel', + 'candy', + 'cannon', + 'canoe', + 'canvas', + 'canyon', + 'capable', + 'capital', + 'captain', + 'car', + 'carbon', + 'card', + 'cargo', + 'carpet', + 'carry', + 'cart', + 'case', + 'cash', + 'casino', + 'castle', + 'casual', + 'cat', + 'catalog', + 'catch', + 'category', + 'cattle', + 'caught', + 'cause', + 'caution', + 'cave', + 'ceiling', + 'celery', + 'cement', + 'census', + 'century', + 'cereal', + 'certain', + 'chair', + 'chalk', + 'champion', + 'change', + 'chaos', + 'chapter', + 'charge', + 'chase', + 'chat', + 'cheap', + 'check', + 'cheese', + 'chef', + 'cherry', + 'chest', + 'chicken', + 'chief', + 'child', + 'chimney', + 'choice', + 'choose', + 'chronic', + 'chuckle', + 'chunk', + 'churn', + 'cigar', + 'cinnamon', + 'circle', + 'citizen', + 'city', + 'civil', + 'claim', + 'clap', + 'clarify', + 'claw', + 'clay', + 'clean', + 'clerk', + 'clever', + 'click', + 'client', + 'cliff', + 'climb', + 'clinic', + 'clip', + 'clock', + 'clog', + 'close', + 'cloth', + 'cloud', + 'clown', + 'club', + 'clump', + 'cluster', + 'clutch', + 'coach', + 'coast', + 'coconut', + 'code', + 'coffee', + 'coil', + 'coin', + 'collect', + 'color', + 'column', + 'combine', + 'come', + 'comfort', + 'comic', + 'common', + 'company', + 'concert', + 'conduct', + 'confirm', + 'congress', + 'connect', + 'consider', + 'control', + 'convince', + 'cook', + 'cool', + 'copper', + 'copy', + 'coral', + 'core', + 'corn', + 'correct', + 'cost', + 'cotton', + 'couch', + 'country', + 'couple', + 'course', + 'cousin', + 'cover', + 'coyote', + 'crack', + 'cradle', + 'craft', + 'cram', + 'crane', + 'crash', + 'crater', + 'crawl', + 'crazy', + 'cream', + 'credit', + 'creek', + 'crew', + 'cricket', + 'crime', + 'crisp', + 'critic', + 'crop', + 'cross', + 'crouch', + 'crowd', + 'crucial', + 'cruel', + 'cruise', + 'crumble', + 'crunch', + 'crush', + 'cry', + 'crystal', + 'cube', + 'culture', + 'cup', + 'cupboard', + 'curious', + 'current', + 'curtain', + 'curve', + 'cushion', + 'custom', + 'cute', + 'cycle', + 'dad', + 'damage', + 'damp', + 'dance', + 'danger', + 'daring', + 'dash', + 'daughter', + 'dawn', + 'day', + 'deal', + 'debate', + 'debris', + 'decade', + 'december', + 'decide', + 'decline', + 'decorate', + 'decrease', + 'deer', + 'defense', + 'define', + 'defy', + 'degree', + 'delay', + 'deliver', + 'demand', + 'demise', + 'denial', + 'dentist', + 'deny', + 'depart', + 'depend', + 'deposit', + 'depth', + 'deputy', + 'derive', + 'describe', + 'desert', + 'design', + 'desk', + 'despair', + 'destroy', + 'detail', + 'detect', + 'develop', + 'device', + 'devote', + 'diagram', + 'dial', + 'diamond', + 'diary', + 'dice', + 'diesel', + 'diet', + 'differ', + 'digital', + 'dignity', + 'dilemma', + 'dinner', + 'dinosaur', + 'direct', + 'dirt', + 'disagree', + 'discover', + 'disease', + 'dish', + 'dismiss', + 'disorder', + 'display', + 'distance', + 'divert', + 'divide', + 'divorce', + 'dizzy', + 'doctor', + 'document', + 'dog', + 'doll', + 'dolphin', + 'domain', + 'donate', + 'donkey', + 'donor', + 'door', + 'dose', + 'double', + 'dove', + 'draft', + 'dragon', + 'drama', + 'drastic', + 'draw', + 'dream', + 'dress', + 'drift', + 'drill', + 'drink', + 'drip', + 'drive', + 'drop', + 'drum', + 'dry', + 'duck', + 'dumb', + 'dune', + 'during', + 'dust', + 'dutch', + 'duty', + 'dwarf', + 'dynamic', + 'eager', + 'eagle', + 'early', + 'earn', + 'earth', + 'easily', + 'east', + 'easy', + 'echo', + 'ecology', + 'economy', + 'edge', + 'edit', + 'educate', + 'effort', + 'egg', + 'eight', + 'either', + 'elbow', + 'elder', + 'electric', + 'elegant', + 'element', + 'elephant', + 'elevator', + 'elite', + 'else', + 'embark', + 'embody', + 'embrace', + 'emerge', + 'emotion', + 'employ', + 'empower', + 'empty', + 'enable', + 'enact', + 'end', + 'endless', + 'endorse', + 'enemy', + 'energy', + 'enforce', + 'engage', + 'engine', + 'enhance', + 'enjoy', + 'enlist', + 'enough', + 'enrich', + 'enroll', + 'ensure', + 'enter', + 'entire', + 'entry', + 'envelope', + 'episode', + 'equal', + 'equip', + 'era', + 'erase', + 'erode', + 'erosion', + 'error', + 'erupt', + 'escape', + 'essay', + 'essence', + 'estate', + 'eternal', + 'ethics', + 'evidence', + 'evil', + 'evoke', + 'evolve', + 'exact', + 'example', + 'excess', + 'exchange', + 'excite', + 'exclude', + 'excuse', + 'execute', + 'exercise', + 'exhaust', + 'exhibit', + 'exile', + 'exist', + 'exit', + 'exotic', + 'expand', + 'expect', + 'expire', + 'explain', + 'expose', + 'express', + 'extend', + 'extra', + 'eye', + 'eyebrow', + 'fabric', + 'face', + 'faculty', + 'fade', + 'faint', + 'faith', + 'fall', + 'false', + 'fame', + 'family', + 'famous', + 'fan', + 'fancy', + 'fantasy', + 'farm', + 'fashion', + 'fat', + 'fatal', + 'father', + 'fatigue', + 'fault', + 'favorite', + 'feature', + 'february', + 'federal', + 'fee', + 'feed', + 'feel', + 'female', + 'fence', + 'festival', + 'fetch', + 'fever', + 'few', + 'fiber', + 'fiction', + 'field', + 'figure', + 'file', + 'film', + 'filter', + 'final', + 'find', + 'fine', + 'finger', + 'finish', + 'fire', + 'firm', + 'first', + 'fiscal', + 'fish', + 'fit', + 'fitness', + 'fix', + 'flag', + 'flame', + 'flash', + 'flat', + 'flavor', + 'flee', + 'flight', + 'flip', + 'float', + 'flock', + 'floor', + 'flower', + 'fluid', + 'flush', + 'fly', + 'foam', + 'focus', + 'fog', + 'foil', + 'fold', + 'follow', + 'food', + 'foot', + 'force', + 'forest', + 'forget', + 'fork', + 'fortune', + 'forum', + 'forward', + 'fossil', + 'foster', + 'found', + 'fox', + 'fragile', + 'frame', + 'frequent', + 'fresh', + 'friend', + 'fringe', + 'frog', + 'front', + 'frost', + 'frown', + 'frozen', + 'fruit', + 'fuel', + 'fun', + 'funny', + 'furnace', + 'fury', + 'future', + 'gadget', + 'gain', + 'galaxy', + 'gallery', + 'game', + 'gap', + 'garage', + 'garbage', + 'garden', + 'garlic', + 'garment', + 'gas', + 'gasp', + 'gate', + 'gather', + 'gauge', + 'gaze', + 'general', + 'genius', + 'genre', + 'gentle', + 'genuine', + 'gesture', + 'ghost', + 'giant', + 'gift', + 'giggle', + 'ginger', + 'giraffe', + 'girl', + 'give', + 'glad', + 'glance', + 'glare', + 'glass', + 'glide', + 'glimpse', + 'globe', + 'gloom', + 'glory', + 'glove', + 'glow', + 'glue', + 'goat', + 'goddess', + 'gold', + 'good', + 'goose', + 'gorilla', + 'gospel', + 'gossip', + 'govern', + 'gown', + 'grab', + 'grace', + 'grain', + 'grant', + 'grape', + 'grass', + 'gravity', + 'great', + 'green', + 'grid', + 'grief', + 'grit', + 'grocery', + 'group', + 'grow', + 'grunt', + 'guard', + 'guess', + 'guide', + 'guilt', + 'guitar', + 'gun', + 'gym', + 'habit', + 'hair', + 'half', + 'hammer', + 'hamster', + 'hand', + 'happy', + 'harbor', + 'hard', + 'harsh', + 'harvest', + 'hat', + 'have', + 'hawk', + 'hazard', + 'head', + 'health', + 'heart', + 'heavy', + 'hedgehog', + 'height', + 'hello', + 'helmet', + 'help', + 'hen', + 'hero', + 'hidden', + 'high', + 'hill', + 'hint', + 'hip', + 'hire', + 'history', + 'hobby', + 'hockey', + 'hold', + 'hole', + 'holiday', + 'hollow', + 'home', + 'honey', + 'hood', + 'hope', + 'horn', + 'horror', + 'horse', + 'hospital', + 'host', + 'hotel', + 'hour', + 'hover', + 'hub', + 'huge', + 'human', + 'humble', + 'humor', + 'hundred', + 'hungry', + 'hunt', + 'hurdle', + 'hurry', + 'hurt', + 'husband', + 'hybrid', + 'ice', + 'icon', + 'idea', + 'identify', + 'idle', + 'ignore', + 'ill', + 'illegal', + 'illness', + 'image', + 'imitate', + 'immense', + 'immune', + 'impact', + 'impose', + 'improve', + 'impulse', + 'inch', + 'include', + 'income', + 'increase', + 'index', + 'indicate', + 'indoor', + 'industry', + 'infant', + 'inflict', + 'inform', + 'inhale', + 'inherit', + 'initial', + 'inject', + 'injury', + 'inmate', + 'inner', + 'innocent', + 'input', + 'inquiry', + 'insane', + 'insect', + 'inside', + 'inspire', + 'install', + 'intact', + 'interest', + 'into', + 'invest', + 'invite', + 'involve', + 'iron', + 'island', + 'isolate', + 'issue', + 'item', + 'ivory', + 'jacket', + 'jaguar', + 'jar', + 'jazz', + 'jealous', + 'jeans', + 'jelly', + 'jewel', + 'job', + 'join', + 'joke', + 'journey', + 'joy', + 'judge', + 'juice', + 'jump', + 'jungle', + 'junior', + 'junk', + 'just', + 'kangaroo', + 'keen', + 'keep', + 'ketchup', + 'key', + 'kick', + 'kid', + 'kidney', + 'kind', + 'kingdom', + 'kiss', + 'kit', + 'kitchen', + 'kite', + 'kitten', + 'kiwi', + 'knee', + 'knife', + 'knock', + 'know', + 'lab', + 'label', + 'labor', + 'ladder', + 'lady', + 'lake', + 'lamp', + 'language', + 'laptop', + 'large', + 'later', + 'latin', + 'laugh', + 'laundry', + 'lava', + 'law', + 'lawn', + 'lawsuit', + 'layer', + 'lazy', + 'leader', + 'leaf', + 'learn', + 'leave', + 'lecture', + 'left', + 'leg', + 'legal', + 'legend', + 'leisure', + 'lemon', + 'lend', + 'length', + 'lens', + 'leopard', + 'lesson', + 'letter', + 'level', + 'liar', + 'liberty', + 'library', + 'license', + 'life', + 'lift', + 'light', + 'like', + 'limb', + 'limit', + 'link', + 'lion', + 'liquid', + 'list', + 'little', + 'live', + 'lizard', + 'load', + 'loan', + 'lobster', + 'local', + 'lock', + 'logic', + 'lonely', + 'long', + 'loop', + 'lottery', + 'loud', + 'lounge', + 'love', + 'loyal', + 'lucky', + 'luggage', + 'lumber', + 'lunar', + 'lunch', + 'luxury', + 'lyrics', + 'machine', + 'mad', + 'magic', + 'magnet', + 'maid', + 'mail', + 'main', + 'major', + 'make', + 'mammal', + 'man', + 'manage', + 'mandate', + 'mango', + 'mansion', + 'manual', + 'maple', + 'marble', + 'march', + 'margin', + 'marine', + 'market', + 'marriage', + 'mask', + 'mass', + 'master', + 'match', + 'material', + 'math', + 'matrix', + 'matter', + 'maximum', + 'maze', + 'meadow', + 'mean', + 'measure', + 'meat', + 'mechanic', + 'medal', + 'media', + 'melody', + 'melt', + 'member', + 'memory', + 'mention', + 'menu', + 'mercy', + 'merge', + 'merit', + 'merry', + 'mesh', + 'message', + 'metal', + 'method', + 'middle', + 'midnight', + 'milk', + 'million', + 'mimic', + 'mind', + 'minimum', + 'minor', + 'minute', + 'miracle', + 'mirror', + 'misery', + 'miss', + 'mistake', + 'mix', + 'mixed', + 'mixture', + 'mobile', + 'model', + 'modify', + 'mom', + 'moment', + 'monitor', + 'monkey', + 'monster', + 'month', + 'moon', + 'moral', + 'more', + 'morning', + 'mosquito', + 'mother', + 'motion', + 'motor', + 'mountain', + 'mouse', + 'move', + 'movie', + 'much', + 'muffin', + 'mule', + 'multiply', + 'muscle', + 'museum', + 'mushroom', + 'music', + 'must', + 'mutual', + 'myself', + 'mystery', + 'myth', + 'naive', + 'name', + 'napkin', + 'narrow', + 'nasty', + 'nation', + 'nature', + 'near', + 'neck', + 'need', + 'negative', + 'neglect', + 'neither', + 'nephew', + 'nerve', + 'nest', + 'net', + 'network', + 'neutral', + 'never', + 'news', + 'next', + 'nice', + 'night', + 'noble', + 'noise', + 'nominee', + 'noodle', + 'normal', + 'north', + 'nose', + 'notable', + 'note', + 'nothing', + 'notice', + 'novel', + 'now', + 'nuclear', + 'number', + 'nurse', + 'nut', + 'oak', + 'obey', + 'object', + 'oblige', + 'obscure', + 'observe', + 'obtain', + 'obvious', + 'occur', + 'ocean', + 'october', + 'odor', + 'off', + 'offer', + 'office', + 'often', + 'oil', + 'okay', + 'old', + 'olive', + 'olympic', + 'omit', + 'once', + 'one', + 'onion', + 'online', + 'only', + 'open', + 'opera', + 'opinion', + 'oppose', + 'option', + 'orange', + 'orbit', + 'orchard', + 'order', + 'ordinary', + 'organ', + 'orient', + 'original', + 'orphan', + 'ostrich', + 'other', + 'outdoor', + 'outer', + 'output', + 'outside', + 'oval', + 'oven', + 'over', + 'own', + 'owner', + 'oxygen', + 'oyster', + 'ozone', + 'pact', + 'paddle', + 'page', + 'pair', + 'palace', + 'palm', + 'panda', + 'panel', + 'panic', + 'panther', + 'paper', + 'parade', + 'parent', + 'park', + 'parrot', + 'party', + 'pass', + 'patch', + 'path', + 'patient', + 'patrol', + 'pattern', + 'pause', + 'pave', + 'payment', + 'peace', + 'peanut', + 'pear', + 'peasant', + 'pelican', + 'pen', + 'penalty', + 'pencil', + 'people', + 'pepper', + 'perfect', + 'permit', + 'person', + 'pet', + 'phone', + 'photo', + 'phrase', + 'physical', + 'piano', + 'picnic', + 'picture', + 'piece', + 'pig', + 'pigeon', + 'pill', + 'pilot', + 'pink', + 'pioneer', + 'pipe', + 'pistol', + 'pitch', + 'pizza', + 'place', + 'planet', + 'plastic', + 'plate', + 'play', + 'please', + 'pledge', + 'pluck', + 'plug', + 'plunge', + 'poem', + 'poet', + 'point', + 'polar', + 'pole', + 'police', + 'pond', + 'pony', + 'pool', + 'popular', + 'portion', + 'position', + 'possible', + 'post', + 'potato', + 'pottery', + 'poverty', + 'powder', + 'power', + 'practice', + 'praise', + 'predict', + 'prefer', + 'prepare', + 'present', + 'pretty', + 'prevent', + 'price', + 'pride', + 'primary', + 'print', + 'priority', + 'prison', + 'private', + 'prize', + 'problem', + 'process', + 'produce', + 'profit', + 'program', + 'project', + 'promote', + 'proof', + 'property', + 'prosper', + 'protect', + 'proud', + 'provide', + 'public', + 'pudding', + 'pull', + 'pulp', + 'pulse', + 'pumpkin', + 'punch', + 'pupil', + 'puppy', + 'purchase', + 'purity', + 'purpose', + 'purse', + 'push', + 'put', + 'puzzle', + 'pyramid', + 'quality', + 'quantum', + 'quarter', + 'question', + 'quick', + 'quit', + 'quiz', + 'quote', + 'rabbit', + 'raccoon', + 'race', + 'rack', + 'radar', + 'radio', + 'rail', + 'rain', + 'raise', + 'rally', + 'ramp', + 'ranch', + 'random', + 'range', + 'rapid', + 'rare', + 'rate', + 'rather', + 'raven', + 'raw', + 'razor', + 'ready', + 'real', + 'reason', + 'rebel', + 'rebuild', + 'recall', + 'receive', + 'recipe', + 'record', + 'recycle', + 'reduce', + 'reflect', + 'reform', + 'refuse', + 'region', + 'regret', + 'regular', + 'reject', + 'relax', + 'release', + 'relief', + 'rely', + 'remain', + 'remember', + 'remind', + 'remove', + 'render', + 'renew', + 'rent', + 'reopen', + 'repair', + 'repeat', + 'replace', + 'report', + 'require', + 'rescue', + 'resemble', + 'resist', + 'resource', + 'response', + 'result', + 'retire', + 'retreat', + 'return', + 'reunion', + 'reveal', + 'review', + 'reward', + 'rhythm', + 'rib', + 'ribbon', + 'rice', + 'rich', + 'ride', + 'ridge', + 'rifle', + 'right', + 'rigid', + 'ring', + 'riot', + 'ripple', + 'risk', + 'ritual', + 'rival', + 'river', + 'road', + 'roast', + 'robot', + 'robust', + 'rocket', + 'romance', + 'roof', + 'rookie', + 'room', + 'rose', + 'rotate', + 'rough', + 'round', + 'route', + 'royal', + 'rubber', + 'rude', + 'rug', + 'rule', + 'run', + 'runway', + 'rural', + 'sad', + 'saddle', + 'sadness', + 'safe', + 'sail', + 'salad', + 'salmon', + 'salon', + 'salt', + 'salute', + 'same', + 'sample', + 'sand', + 'satisfy', + 'satoshi', + 'sauce', + 'sausage', + 'save', + 'say', + 'scale', + 'scan', + 'scare', + 'scatter', + 'scene', + 'scheme', + 'school', + 'science', + 'scissors', + 'scorpion', + 'scout', + 'scrap', + 'screen', + 'script', + 'scrub', + 'sea', + 'search', + 'season', + 'seat', + 'second', + 'secret', + 'section', + 'security', + 'seed', + 'seek', + 'segment', + 'select', + 'sell', + 'seminar', + 'senior', + 'sense', + 'sentence', + 'series', + 'service', + 'session', + 'settle', + 'setup', + 'seven', + 'shadow', + 'shaft', + 'shallow', + 'share', + 'shed', + 'shell', + 'sheriff', + 'shield', + 'shift', + 'shine', + 'ship', + 'shiver', + 'shock', + 'shoe', + 'shoot', + 'shop', + 'short', + 'shoulder', + 'shove', + 'shrimp', + 'shrug', + 'shuffle', + 'shy', + 'sibling', + 'sick', + 'side', + 'siege', + 'sight', + 'sign', + 'silent', + 'silk', + 'silly', + 'silver', + 'similar', + 'simple', + 'since', + 'sing', + 'siren', + 'sister', + 'situate', + 'six', + 'size', + 'skate', + 'sketch', + 'ski', + 'skill', + 'skin', + 'skirt', + 'skull', + 'slab', + 'slam', + 'sleep', + 'slender', + 'slice', + 'slide', + 'slight', + 'slim', + 'slogan', + 'slot', + 'slow', + 'slush', + 'small', + 'smart', + 'smile', + 'smoke', + 'smooth', + 'snack', + 'snake', + 'snap', + 'sniff', + 'snow', + 'soap', + 'soccer', + 'social', + 'sock', + 'soda', + 'soft', + 'solar', + 'soldier', + 'solid', + 'solution', + 'solve', + 'someone', + 'song', + 'soon', + 'sorry', + 'sort', + 'soul', + 'sound', + 'soup', + 'source', + 'south', + 'space', + 'spare', + 'spatial', + 'spawn', + 'speak', + 'special', + 'speed', + 'spell', + 'spend', + 'sphere', + 'spice', + 'spider', + 'spike', + 'spin', + 'spirit', + 'split', + 'spoil', + 'sponsor', + 'spoon', + 'sport', + 'spot', + 'spray', + 'spread', + 'spring', + 'spy', + 'square', + 'squeeze', + 'squirrel', + 'stable', + 'stadium', + 'staff', + 'stage', + 'stairs', + 'stamp', + 'stand', + 'start', + 'state', + 'stay', + 'steak', + 'steel', + 'stem', + 'step', + 'stereo', + 'stick', + 'still', + 'sting', + 'stock', + 'stomach', + 'stone', + 'stool', + 'story', + 'stove', + 'strategy', + 'street', + 'strike', + 'strong', + 'struggle', + 'student', + 'stuff', + 'stumble', + 'style', + 'subject', + 'submit', + 'subway', + 'success', + 'such', + 'sudden', + 'suffer', + 'sugar', + 'suggest', + 'suit', + 'summer', + 'sun', + 'sunny', + 'sunset', + 'super', + 'supply', + 'supreme', + 'sure', + 'surface', + 'surge', + 'surprise', + 'surround', + 'survey', + 'suspect', + 'sustain', + 'swallow', + 'swamp', + 'swap', + 'swarm', + 'swear', + 'sweet', + 'swift', + 'swim', + 'swing', + 'switch', + 'sword', + 'symbol', + 'symptom', + 'syrup', + 'system', + 'table', + 'tackle', + 'tag', + 'tail', + 'talent', + 'talk', + 'tank', + 'tape', + 'target', + 'task', + 'taste', + 'tattoo', + 'taxi', + 'teach', + 'team', + 'tell', + 'ten', + 'tenant', + 'tennis', + 'tent', + 'term', + 'test', + 'text', + 'thank', + 'that', + 'theme', + 'then', + 'theory', + 'there', + 'they', + 'thing', + 'this', + 'thought', + 'three', + 'thrive', + 'throw', + 'thumb', + 'thunder', + 'ticket', + 'tide', + 'tiger', + 'tilt', + 'timber', + 'time', + 'tiny', + 'tip', + 'tired', + 'tissue', + 'title', + 'toast', + 'tobacco', + 'today', + 'toddler', + 'toe', + 'together', + 'toilet', + 'token', + 'tomato', + 'tomorrow', + 'tone', + 'tongue', + 'tonight', + 'tool', + 'tooth', + 'top', + 'topic', + 'topple', + 'torch', + 'tornado', + 'tortoise', + 'toss', + 'total', + 'tourist', + 'toward', + 'tower', + 'town', + 'toy', + 'track', + 'trade', + 'traffic', + 'tragic', + 'train', + 'transfer', + 'trap', + 'trash', + 'travel', + 'tray', + 'treat', + 'tree', + 'trend', + 'trial', + 'tribe', + 'trick', + 'trigger', + 'trim', + 'trip', + 'trophy', + 'trouble', + 'truck', + 'true', + 'truly', + 'trumpet', + 'trust', + 'truth', + 'try', + 'tube', + 'tuition', + 'tumble', + 'tuna', + 'tunnel', + 'turkey', + 'turn', + 'turtle', + 'twelve', + 'twenty', + 'twice', + 'twin', + 'twist', + 'two', + 'type', + 'typical', + 'ugly', + 'umbrella', + 'unable', + 'unaware', + 'uncle', + 'uncover', + 'under', + 'undo', + 'unfair', + 'unfold', + 'unhappy', + 'uniform', + 'unique', + 'unit', + 'universe', + 'unknown', + 'unlock', + 'until', + 'unusual', + 'unveil', + 'update', + 'upgrade', + 'uphold', + 'upon', + 'upper', + 'upset', + 'urban', + 'urge', + 'usage', + 'use', + 'used', + 'useful', + 'useless', + 'usual', + 'utility', + 'vacant', + 'vacuum', + 'vague', + 'valid', + 'valley', + 'valve', + 'van', + 'vanish', + 'vapor', + 'various', + 'vast', + 'vault', + 'vehicle', + 'velvet', + 'vendor', + 'venture', + 'venue', + 'verb', + 'verify', + 'version', + 'very', + 'vessel', + 'veteran', + 'viable', + 'vibrant', + 'vicious', + 'victory', + 'video', + 'view', + 'village', + 'vintage', + 'violin', + 'virtual', + 'virus', + 'visa', + 'visit', + 'visual', + 'vital', + 'vivid', + 'vocal', + 'voice', + 'void', + 'volcano', + 'volume', + 'vote', + 'voyage', + 'wage', + 'wagon', + 'wait', + 'walk', + 'wall', + 'walnut', + 'want', + 'warfare', + 'warm', + 'warrior', + 'wash', + 'wasp', + 'waste', + 'water', + 'wave', + 'way', + 'wealth', + 'weapon', + 'wear', + 'weasel', + 'weather', + 'web', + 'wedding', + 'weekend', + 'weird', + 'welcome', + 'west', + 'wet', + 'whale', + 'what', + 'wheat', + 'wheel', + 'when', + 'where', + 'whip', + 'whisper', + 'wide', + 'width', + 'wife', + 'wild', + 'will', + 'win', + 'window', + 'wine', + 'wing', + 'wink', + 'winner', + 'winter', + 'wire', + 'wisdom', + 'wise', + 'wish', + 'witness', + 'wolf', + 'woman', + 'wonder', + 'wood', + 'wool', + 'word', + 'work', + 'world', + 'worry', + 'worth', + 'wrap', + 'wreck', + 'wrestle', + 'wrist', + 'write', + 'wrong', + 'yard', + 'year', + 'yellow', + 'you', + 'young', + 'youth', + 'zebra', + 'zero', + 'zone', + 'zoo' +]; diff --git a/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/bottom_sheet_listener.dart b/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/bottom_sheet_listener.dart new file mode 100644 index 0000000..41c724b --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/bottom_sheet_listener.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; + +class BottomSheetListener extends StatefulWidget { + final Widget child; + + const BottomSheetListener({ + super.key, + required this.child, + }); + + @override + BottomSheetListenerState createState() => BottomSheetListenerState(); +} + +class BottomSheetListenerState extends State { + late final IBottomSheetService _bottomSheetService; + + @override + void initState() { + super.initState(); + _bottomSheetService = GetIt.I(); + _bottomSheetService.currentSheet.addListener(_showBottomSheet); + } + + @override + void dispose() { + _bottomSheetService.currentSheet.removeListener(_showBottomSheet); + super.dispose(); + } + + Future _showBottomSheet() async { + if (_bottomSheetService.currentSheet.value != null) { + BottomSheetQueueItem item = _bottomSheetService.currentSheet.value!; + final value = await showModalBottomSheet( + context: context, + backgroundColor: StyleConstants.clear, + isScrollControlled: true, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.9, + ), + builder: (context) { + if (item.closeAfter > 0) { + Future.delayed(Duration(seconds: item.closeAfter), () { + try { + if (!mounted) return; + Navigator.pop(context); + } catch (e) { + debugPrint('[$runtimeType] close $e'); + } + }); + } + return Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(StyleConstants.linear16), + ), + ), + padding: EdgeInsets.only( + top: StyleConstants.linear16, + left: StyleConstants.linear16, + right: StyleConstants.linear16, + bottom: MediaQuery.of(context).viewInsets.bottom + + StyleConstants.linear16, + ), + margin: const EdgeInsets.all( + StyleConstants.linear16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + padding: const EdgeInsets.all(0.0), + visualDensity: VisualDensity.compact, + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.close_sharp), + ), + ], + ), + Flexible(child: item.widget), + ], + ), + ); + }, + ); + item.completer.complete(value); + _bottomSheetService.showNext(); + } + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/bottom_sheet_service.dart b/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/bottom_sheet_service.dart new file mode 100644 index 0000000..b898d4e --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/bottom_sheet_service.dart @@ -0,0 +1,46 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; + +class BottomSheetService extends IBottomSheetService { + Queue queue = Queue(); + + @override + final ValueNotifier currentSheet = ValueNotifier(null); + + @override + Future queueBottomSheet({ + required Widget widget, + int closeAfter = 0, + }) async { + // Create the bottom sheet queue item + final completer = Completer(); + final queueItem = BottomSheetQueueItem( + widget: widget, + completer: completer, + closeAfter: closeAfter, + ); + + // If the current sheet it null, set it to the queue item + if (currentSheet.value == null) { + currentSheet.value = queueItem; + } else { + // Otherwise, add it to the queue + queue.add(queueItem); + } + + // Return the future + return await completer.future; + } + + @override + void showNext() { + if (queue.isEmpty) { + currentSheet.value = null; + } else { + currentSheet.value = queue.removeFirst(); + } + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/i_bottom_sheet_service.dart b/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/i_bottom_sheet_service.dart new file mode 100644 index 0000000..48d88c5 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/bottom_sheet/i_bottom_sheet_service.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +enum WCBottomSheetResult { + reject, + one, + all, +} + +class BottomSheetQueueItem { + final Widget widget; + final Completer completer; + final int closeAfter; + + BottomSheetQueueItem({ + required this.widget, + required this.completer, + this.closeAfter = 0, + }); +} + +abstract class IBottomSheetService { + abstract final ValueNotifier currentSheet; + + Future queueBottomSheet({ + required Widget widget, + int closeAfter = 0, + }); + + void showNext(); +} diff --git a/packages/reown_walletkit/example/lib/dependencies/chain_services/cosmos_service.dart b/packages/reown_walletkit/example/lib/dependencies/chain_services/cosmos_service.dart new file mode 100644 index 0000000..4499fee --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/cosmos_service.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_metadata.dart'; +import 'package:reown_walletkit_wallet/utils/methods_utils.dart'; + +class CosmosService { + final _walletKit = GetIt.I().walletKit; + + final ChainMetadata chainSupported; + + Map get cosmosRequestHandlers => { + 'cosmos_signDirect': cosmosSignDirect, + 'cosmos_signAmino': cosmosSignAmino, + }; + + CosmosService({required this.chainSupported}) { + for (var handler in cosmosRequestHandlers.entries) { + _walletKit.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + } + + Future cosmosSignDirect(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] cosmosSignDirect request: $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + final error = Errors.getSdkError(Errors.UNSUPPORTED_METHODS); + final response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + + _handleResponseForTopic(topic, response); + } + + Future cosmosSignAmino(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] cosmosSignAmino request: $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + final error = Errors.getSdkError(Errors.UNSUPPORTED_METHODS); + final response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + + _handleResponseForTopic(topic, response); + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = _walletKit.sessions.get(topic); + + try { + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/chain_services/evm_service.dart b/packages/reown_walletkit/example/lib/dependencies/chain_services/evm_service.dart new file mode 100644 index 0000000..a3187c2 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/evm_service.dart @@ -0,0 +1,592 @@ +import 'dart:convert'; +import 'package:eth_sig_util/util/utils.dart'; +import 'package:flutter/material.dart'; + +import 'package:http/http.dart' as http; +import 'package:eth_sig_util/eth_sig_util.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_metadata.dart'; +import 'package:reown_walletkit_wallet/utils/eth_utils.dart'; +import 'package:reown_walletkit_wallet/utils/methods_utils.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_model.dart'; + +enum SupportedEVMMethods { + ethSign, + ethSignTransaction, + ethSignTypedData, + ethSignTypedDataV4, + switchChain, + personalSign, + ethSendTransaction; + + String get name { + switch (this) { + case ethSign: + return 'eth_sign'; + case ethSignTransaction: + return 'eth_signTransaction'; + case ethSignTypedData: + return 'eth_signTypedData'; + case ethSignTypedDataV4: + return 'eth_signTypedData_v4'; + case switchChain: + return 'wallet_switchEthereumChain'; + case personalSign: + return 'personal_sign'; + case ethSendTransaction: + return 'eth_sendTransaction'; + } + } +} + +class EVMService { + final _walletKit = GetIt.I().walletKit; + + final ChainMetadata chainSupported; + late final Web3Client ethClient; + + Map get sessionRequestHandlers => { + SupportedEVMMethods.ethSign.name: ethSign, + SupportedEVMMethods.ethSignTransaction.name: ethSignTransaction, + SupportedEVMMethods.ethSignTypedData.name: ethSignTypedData, + SupportedEVMMethods.ethSignTypedDataV4.name: ethSignTypedDataV4, + SupportedEVMMethods.switchChain.name: switchChain, + // 'wallet_addEthereumChain': addChain, + }; + + Map get methodRequestHandlers => { + SupportedEVMMethods.personalSign.name: personalSign, + SupportedEVMMethods.ethSendTransaction.name: ethSendTransaction, + }; + + EVMService({required this.chainSupported}) { + ethClient = Web3Client(chainSupported.rpc.first, http.Client()); + + for (final event in EventsConstants.allEvents) { + _walletKit.registerEventEmitter( + chainId: chainSupported.chainId, + event: event, + ); + } + + for (var handler in methodRequestHandlers.entries) { + _walletKit.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + for (var handler in sessionRequestHandlers.entries) { + _walletKit.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + + _walletKit.onSessionRequest.subscribe(_onSessionRequest); + } + + // personal_sign is handled using onSessionRequest event for demo purposes + Future personalSign(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] personalSign request: $parameters'); + final SessionRequest pRequest = _walletKit.pendingRequests.getAll().last; + final address = EthUtils.getAddressFromSessionRequest(pRequest); + final data = EthUtils.getDataFromSessionRequest(pRequest); + final message = EthUtils.getUtf8Message(data.toString()); + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + if (await MethodsUtils.requestApproval( + message, + method: pRequest.method, + chainId: pRequest.chainId, + address: address, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + )) { + try { + // Load the private key + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final pk = '0x${keys[0].privateKey}'; + final credentials = EthPrivateKey.fromHex(pk); + final signature = credentials.signPersonalMessageToUint8List( + utf8.encode(message), + ); + final signedTx = bytesToHex(signature, include0x: true); + + isValidSignature(signedTx, message, credentials.address.hex); + + response = response.copyWith(result: signedTx); + } catch (e) { + debugPrint('[SampleWallet] personalSign error $e'); + // TODO document errors + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSign(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] ethSign request: $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + final data = EthUtils.getDataFromSessionRequest(pRequest); + final message = EthUtils.getUtf8Message(data.toString()); + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + if (await MethodsUtils.requestApproval( + message, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + )) { + try { + // Load the private key + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final pk = '0x${keys[0].privateKey}'; + final credentials = EthPrivateKey.fromHex(pk); + final signature = credentials.signPersonalMessageToUint8List( + utf8.encode(message), + ); + final signedTx = bytesToHex(signature, include0x: true); + + isValidSignature(signedTx, message, credentials.address.hex); + + response = response.copyWith(result: signedTx); + } catch (e) { + debugPrint('[SampleWallet] ethSign error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError(); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSignTypedData(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] ethSignTypedData request: $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + final data = EthUtils.getDataFromSessionRequest(pRequest); + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + if (await MethodsUtils.requestApproval( + data, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + )) { + try { + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final signature = EthSigUtil.signTypedData( + privateKey: keys[0].privateKey, + jsonData: data, + version: TypedDataVersion.V4, + ); + + response = response.copyWith(result: signature); + } catch (e) { + debugPrint('[SampleWallet] ethSignTypedData error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError(); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSignTypedDataV4(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] ethSignTypedDataV4 request: $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + final data = EthUtils.getDataFromSessionRequest(pRequest); + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + if (await MethodsUtils.requestApproval( + data, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + )) { + try { + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final signature = EthSigUtil.signTypedData( + privateKey: keys[0].privateKey, + jsonData: data, + version: TypedDataVersion.V4, + ); + + response = response.copyWith(result: signature); + } catch (e) { + debugPrint('[SampleWallet] ethSignTypedDataV4 error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } else { + response = response.copyWith( + error: const JsonRpcError(code: 5002, message: 'User rejected method'), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSignTransaction(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] ethSignTransaction request: $parameters'); + final SessionRequest pRequest = _walletKit.pendingRequests.getAll().last; + + final data = EthUtils.getTransactionFromSessionRequest(pRequest); + if (data == null) return; + + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + final transaction = await _approveTransaction( + data, + method: pRequest.method, + chainId: pRequest.chainId, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + ); + if (transaction is Transaction) { + try { + // Load the private key + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final pk = '0x${keys[0].privateKey}'; + final credentials = EthPrivateKey.fromHex(pk); + final chainId = chainSupported.chainId.split(':').last; + + final signature = await ethClient.signTransaction( + credentials, + transaction, + chainId: int.parse(chainId), + ); + // Sign the transaction + final signedTx = bytesToHex(signature, include0x: true); + + response = response.copyWith(result: signedTx); + } on RPCError catch (e) { + debugPrint('[SampleWallet] ethSignTransaction error $e'); + response = response.copyWith( + error: JsonRpcError( + code: e.errorCode, + message: e.message, + ), + ); + } catch (e) { + debugPrint('[SampleWallet] ethSignTransaction error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } else { + response = response.copyWith(error: transaction as JsonRpcError); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSendTransaction(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] ethSendTransaction request: $parameters'); + final SessionRequest pRequest = _walletKit.pendingRequests.getAll().last; + + final data = EthUtils.getTransactionFromSessionRequest(pRequest); + if (data == null) return; + + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + final transaction = await _approveTransaction( + data, + method: pRequest.method, + chainId: pRequest.chainId, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + ); + if (transaction is Transaction) { + try { + // Load the private key + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final pk = '0x${keys[0].privateKey}'; + final credentials = EthPrivateKey.fromHex(pk); + final chainId = chainSupported.chainId.split(':').last; + + final signedTx = await ethClient.sendTransaction( + credentials, + transaction, + chainId: int.parse(chainId), + ); + + response = response.copyWith(result: signedTx); + } on RPCError catch (e) { + debugPrint('[SampleWallet] ethSendTransaction error $e'); + response = response.copyWith( + error: JsonRpcError( + code: e.errorCode, + message: e.message, + ), + ); + } catch (e) { + debugPrint('[SampleWallet] ethSendTransaction error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } else { + response = response.copyWith(error: transaction as JsonRpcError); + } + + _handleResponseForTopic(topic, response); + } + + Future switchChain(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] switchChain request: $topic $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + try { + final params = (parameters as List).first as Map; + final hexChainId = params['chainId'].toString().replaceFirst('0x', ''); + final chainId = int.parse(hexChainId, radix: 16); + await _walletKit.emitSessionEvent( + topic: topic, + chainId: 'eip155:$chainId', + event: SessionEventParams( + name: 'chainChanged', + data: chainId, + ), + ); + response = response.copyWith(result: true); + } on ReownSignError catch (e) { + debugPrint('[SampleWallet] switchChain error $e'); + response = response.copyWith( + error: JsonRpcError( + code: e.code, + message: e.message, + ), + ); + } catch (e) { + debugPrint('[SampleWallet] switchChain error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + _handleResponseForTopic(topic, response); + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = _walletKit.sessions.get(topic); + + try { + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } + + // Future addChain(String topic, dynamic parameters) async { + // final pRequest = _walletKit.pendingRequests.getAll().last; + // await _walletKit.respondSessionRequest( + // topic: topic, + // response: JsonRpcResponse( + // id: pRequest.id, + // jsonrpc: '2.0', + // result: true, + // ), + // ); + // CommonMethods.goBackToDapp(topic, true); + // } + + Future _approveTransaction( + Map tJson, { + String? title, + String? method, + String? chainId, + VerifyContext? verifyContext, + required String transportType, + }) async { + Transaction transaction = tJson.toTransaction(); + + final gasPrice = await ethClient.getGasPrice(); + try { + final gasLimit = await ethClient.estimateGas( + sender: transaction.from, + to: transaction.to, + value: transaction.value, + data: transaction.data, + gasPrice: gasPrice, + ); + + transaction = transaction.copyWith( + gasPrice: gasPrice, + maxGas: gasLimit.toInt(), + ); + } on RPCError catch (e) { + return JsonRpcError(code: e.errorCode, message: e.message); + } + + final gweiGasPrice = (transaction.gasPrice?.getInWei ?? BigInt.zero) / + BigInt.from(1000000000); + + const encoder = JsonEncoder.withIndent(' '); + final trx = encoder.convert(tJson); + + if (await MethodsUtils.requestApproval( + trx, + title: title, + method: method, + chainId: chainId, + transportType: transportType, + verifyContext: verifyContext, + extraModels: [ + WCConnectionModel( + title: 'Gas price', + elements: ['${gweiGasPrice.toStringAsFixed(2)} GWEI'], + ), + ], + )) { + return transaction; + } + + return const JsonRpcError(code: 5002, message: 'User rejected method'); + } + + void _onSessionRequest(SessionRequestEvent? args) async { + if (args != null && args.chainId == chainSupported.chainId) { + debugPrint('[SampleWallet] _onSessionRequest ${args.toString()}'); + final handler = sessionRequestHandlers[args.method]; + if (handler != null) { + await handler(args.topic, args.params); + } + } + } + + bool isValidSignature( + String hexSignature, + String message, + String hexAddress, + ) { + try { + debugPrint( + '[SampleWallet] isValidSignature: $hexSignature, $message, $hexAddress'); + final recoveredAddress = EthSigUtil.recoverPersonalSignature( + signature: hexSignature, + message: utf8.encode(message), + ); + debugPrint('[SampleWallet] recoveredAddress: $recoveredAddress'); + + final recoveredAddress2 = EthSigUtil.recoverSignature( + signature: hexSignature, + message: utf8.encode(message), + ); + debugPrint('[SampleWallet] recoveredAddress2: $recoveredAddress2'); + + final isValid = recoveredAddress == hexAddress; + return isValid; + } catch (e) { + return false; + } + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/chain_services/kadena_service.dart b/packages/reown_walletkit/example/lib/dependencies/chain_services/kadena_service.dart new file mode 100644 index 0000000..f964fca --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/kadena_service.dart @@ -0,0 +1,300 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_data.dart'; +import 'package:reown_walletkit_wallet/models/chain_metadata.dart'; +import 'package:reown_walletkit_wallet/utils/methods_utils.dart'; +import 'package:reown_walletkit_wallet/widgets/kadena_request_widget/kadena_request_widget.dart'; + +import 'package:kadena_dart_sdk/kadena_dart_sdk.dart'; + +class KadenaService { + final _bottomSheetService = GetIt.I(); + final _walletKit = GetIt.I().walletKit; + + final ChainMetadata chainSupported; + late final SigningApi kadenaClient; + + Map get kadenaRequestHandlers => { + 'kadena_getAccounts_v1': kadenaGetAccountsV1, + 'kadena_sign_v1': kadenaSignV1, + 'kadena_quicksign_v1': kadenaQuicksignV1, + }; + + KadenaService({required this.chainSupported}) { + kadenaClient = SigningApi(); + + _walletKit.registerEventEmitter( + chainId: chainSupported.chainId, + event: 'kadena_transaction_updated', + ); + + for (var handler in kadenaRequestHandlers.entries) { + _walletKit.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + + _walletKit.onSessionRequest.subscribe(_onSessionRequest); + } + + Future kadenaGetAccountsV1(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] kadenaGetAccountsV1 request: $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + try { + final accountRequest = AccountRequest.fromJson(parameters); + final getAccountsRequest = GetAccountsRequest(accounts: [accountRequest]); + // Get the keys for the kadena chain + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final kadenaAccounts = []; + + // Loop through the contracts of the request if it exists and add all accounts + for (var account in getAccountsRequest.accounts) { + for (var contract in (account.contracts ?? [])) { + kadenaAccounts.add( + KadenaAccount( + name: 'k:${keys[0].publicKey}', + contract: contract, + chains: ['1'], + ), + ); + } + } + + response = response.copyWith( + result: GetAccountsResponse( + accounts: [ + AccountResponse( + account: '${chainSupported.chainId}${keys[0].publicKey}', + publicKey: keys[0].publicKey, + kadenaAccounts: kadenaAccounts, + ), + ], + ).toJson(), + ); + } catch (e) { + debugPrint('[SampleWallet] kadenaGetAccountsV1 error: $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future kadenaSignV1(String topic, dynamic parameters) async { + debugPrint( + '[SampleWallet] kadenaSignV1 request: ${jsonEncode(parameters)}'); + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + try { + final chain = ChainData.kadenaChains.firstWhere( + (c) => c.chainId == chainSupported.chainId, + ); + final uri = Uri.parse(chain.rpc.first); + final params = parameters as Map; + params.putIfAbsent('networkId', () => uri.host); + + final signRequest = kadenaClient.parseSignRequest(request: params); + + // Get the keys for the kadena chain + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final payload = kadenaClient.constructPactCommandPayload( + request: signRequest, + signingPubKey: keys[0].publicKey, + ); + + // Show the sign widget + final List? approved = await _bottomSheetService.queueBottomSheet( + widget: KadenaRequestWidget(payloads: [payload]), + ); + + // If the user approved, sign the request + if ((approved ?? []).isNotEmpty) { + final signature = kadenaClient.sign( + payload: payload, + keyPair: KadenaSignKeyPair( + privateKey: keys[0].privateKey, + publicKey: keys[0].publicKey, + ), + ); + + response = response.copyWith( + result: signature.toJson(), + ); + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } catch (e) { + debugPrint('[SampleWallet] kadenaSignV1 error: $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + + _handleResponseForTopic(topic, response); + } + + Future kadenaQuicksignV1(String topic, dynamic parameters) async { + debugPrint( + '[SampleWallet] kadenaQuicksignV1 request: ${jsonEncode(parameters)}'); + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + try { + final quicksignRequest = kadenaClient.parseQuicksignRequest( + request: parameters, + ); + + // Get the keys for the kadena chain + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + // Show the sign widget + final List? approved = await _bottomSheetService.queueBottomSheet( + widget: KadenaRequestWidget( + payloads: [ + ...(quicksignRequest.commandSigDatas + .map((e) => PactCommandPayload.fromJson(jsonDecode(e.cmd))) + .toList()), + ], + ), + ); + + if ((approved ?? []).isNotEmpty) { + final List signatures = []; + + // Loop through the requests and sign each one that is true + for (int i = 0; i < approved!.length; i++) { + final bool isApproved = approved[i]; + final CommandSigData request = quicksignRequest.commandSigDatas[i]; + late QuicksignResponse signature; + if (isApproved) { + signature = kadenaClient.quicksignSingleCommand( + commandSigData: request, + keyPairs: [ + KadenaSignKeyPair( + privateKey: keys[0].privateKey, + publicKey: keys[0].publicKey, + ) + ], + ); + } else { + signature = QuicksignResponse( + commandSigData: request, + outcome: QuicksignOutcome( + result: QuicksignOutcome.failure, + msg: 'User rejected sign', + ), + ); + } + + signatures.add(signature); + } + + final result = QuicksignResult(responses: signatures); + + response = response.copyWith( + result: result.toJson(), + ); + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } catch (e) { + debugPrint('[SampleWallet] kadenaSignV1 error: $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + _handleResponseForTopic(topic, response); + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = _walletKit.sessions.get(topic); + + try { + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } + + void _onSessionRequest(SessionRequestEvent? args) async { + if (args != null && args.chainId == chainSupported.chainId) { + debugPrint('[SampleWallet] _onSessionRequest ${args.toString()}'); + final handler = kadenaRequestHandlers[args.method]; + if (handler != null) { + await handler(args.topic, args.params); + } + } + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/chain_services/polkadot_service.dart b/packages/reown_walletkit/example/lib/dependencies/chain_services/polkadot_service.dart new file mode 100644 index 0000000..1c9723e --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/polkadot_service.dart @@ -0,0 +1,237 @@ +import 'dart:convert'; +import 'package:convert/convert.dart'; + +import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.dart'; +import 'package:polkadart/scale_codec.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_metadata.dart'; + +import 'package:polkadart/polkadart.dart'; +import 'package:polkadart_keyring/polkadart_keyring.dart'; +import 'package:reown_walletkit_wallet/utils/methods_utils.dart'; + +class PolkadotService { + final _walletKit = GetIt.I().walletKit; + + final ChainMetadata chainSupported; + late final Keyring keyring; + late final Provider provider; + + Map get polkadotRequestHandlers => + { + 'polkadot_signMessage': polkadotSignMessage, + 'polkadot_signTransaction': polkadotSignTransaction, + }; + + PolkadotService({required this.chainSupported}) { + keyring = Keyring(); + provider = Provider.fromUri(Uri.parse(chainSupported.rpc.first)); + + for (var handler in polkadotRequestHandlers.entries) { + _walletKit.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + + _walletKit.onSessionRequest.subscribe(_onSessionRequest); + } + + Future polkadotSignMessage(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] polkadotSignMessage: $parameters'); + const method = 'polkadot_signMessage'; + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + try { + final params = parameters as Map; + final message = params['message'].toString(); + debugPrint('[SampleWallet] polkadotSignMessage message: $message'); + + // code + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + final keyPair = await keyring.fromUri(keys[0].privateKey); + // adjust the default ss58Format for Polkadot + keyPair.ss58Format = 0; + // adjust the default ss58Format for Kusama + // keyPair.ss58Format = 2; + + if (await MethodsUtils.requestApproval( + message, + title: method, + transportType: pRequest.transportType.name, + )) { + final encodedMessage = utf8.encode(message); + final signature = keyPair.sign(encodedMessage); + + final isVerified = keyPair.verify(encodedMessage, signature); + debugPrint('[$runtimeType] isVerified $isVerified'); + + final hexSignature = hex.encode(signature); + response = response.copyWith( + result: { + 'signature': '0x$hexSignature', + }, + ); + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } catch (e) { + debugPrint('[SampleWallet] polkadotSignMessage error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future polkadotSignTransaction(String topic, dynamic parameters) async { + debugPrint( + '[SampleWallet] polkadotSignTransaction: ${jsonEncode(parameters)}'); + const method = 'polkadot_signTransaction'; + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse( + id: pRequest.id, + jsonrpc: '2.0', + ); + + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + final trxPayload = parameters['transactionPayload'] as Map; + + const encoder = JsonEncoder.withIndent(' '); + final message = encoder.convert(trxPayload); + if (await MethodsUtils.requestApproval( + message, + title: method, + transportType: pRequest.transportType.name, + )) { + try { + final keyPair = await keyring.fromUri(keys[0].privateKey); + // adjust the default ss58Format for Polkadot + keyPair.ss58Format = 0; + // adjust the default ss58Format for Kusama + // keyPair.ss58Format = 2; + + // Get info necessary to build an extrinsic + final provider = Provider.fromUri(Uri.parse(chainSupported.rpc.first)); + final stateApi = StateApi(provider); + final customMetadata = await stateApi.getMetadata(); + final registry = customMetadata.chainInfo.scaleCodec.registry; + + final payload = trxPayload.toSigningPayload(registry); + final payloadBytes = payload.encode(registry); + final signature = keyPair.sign(payloadBytes); + + final isVerified = keyPair.verify(payloadBytes, signature); + debugPrint('[$runtimeType] isVerified $isVerified'); + + final hexSignature = hex.encode(signature); + response = response.copyWith( + result: { + 'signature': '0x$hexSignature', + }, + ); + } catch (e) { + debugPrint('[SampleWallet] polkadotSignTransaction error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + _handleResponseForTopic(topic, response); + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = _walletKit.sessions.get(topic); + + try { + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } + + void _onSessionRequest(SessionRequestEvent? args) async { + if (args != null && args.chainId == chainSupported.chainId) { + debugPrint('[SampleWallet] _onSessionRequest ${args.toString()}'); + final handler = polkadotRequestHandlers[args.method]; + if (handler != null) { + await handler(args.topic, args.params); + } + } + } +} + +extension on Map { + SigningPayload toSigningPayload(Registry registry) { + final signedExtensions = registry.getSignedExtensionTypes(); + final requestSignedExtensions = this['signedExtensions'] as List; + final mapEntries = requestSignedExtensions.map((e) { + return MapEntry(e, signedExtensions[e]); + }).toList(); + + final method = this['method'].toString(); + final decoded = hex.decode(method.substring(2)); + final decodedMethod = utf8.decode(decoded); + return SigningPayload( + method: utf8.encode(decodedMethod), + specVersion: int.parse(this['specVersion']), + transactionVersion: int.parse(this['transactionVersion']), + genesisHash: this['genesisHash'].toString(), + blockHash: this['blockHash'].toString(), + blockNumber: int.parse(this['blockNumber']), + eraPeriod: int.parse(this['era']), + nonce: int.parse(this['nonce']), + tip: int.parse(this['tip']), + customSignedExtensions: Map.fromEntries(mapEntries), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/chain_services/solana_service.dart b/packages/reown_walletkit/example/lib/dependencies/chain_services/solana_service.dart new file mode 100644 index 0000000..6b68adf --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/solana_service.dart @@ -0,0 +1,257 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:solana/solana.dart' as solana; +import 'package:solana/encoder.dart' as solana_encoder; +import 'package:bs58/bs58.dart'; + +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_metadata.dart'; +import 'package:reown_walletkit_wallet/utils/methods_utils.dart'; + +class SolanaService { + Map get solanaRequestHandlers => { + 'solana_signMessage': solanaSignMessage, + 'solana_signTransaction': solanaSignTransaction, + }; + + final _walletKit = GetIt.I().walletKit; + final ChainMetadata chainSupported; + + SolanaService({required this.chainSupported}) { + for (var handler in solanaRequestHandlers.entries) { + _walletKit.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + } + + Future solanaSignMessage(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] solanaSignMessage request: $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final message = params['message'].toString(); + + final keyPair = await _getKeyPair(); + + // it's being sent encoded from dapp + final base58Decoded = base58.decode(message); + final decodedMessage = utf8.decode(base58Decoded); + if (await MethodsUtils.requestApproval( + decodedMessage, + method: pRequest.method, + chainId: pRequest.chainId, + address: keyPair.address, + transportType: pRequest.transportType.name, + )) { + final signature = await keyPair.sign(base58Decoded.toList()); + + response = response.copyWith( + result: { + 'signature': signature.toBase58(), + }, + ); + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + // + } catch (e) { + debugPrint('[SampleWallet] polkadotSignMessage error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + + _handleResponseForTopic(topic, response); + } + + Future solanaSignTransaction(String topic, dynamic parameters) async { + debugPrint( + '[SampleWallet] solanaSignTransaction: ${jsonEncode(parameters)}'); + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params); + + final keyPair = await _getKeyPair(); + + if (await MethodsUtils.requestApproval( + // Show Approval Modal + beautifiedTrx, + method: pRequest.method, + chainId: pRequest.chainId, + address: keyPair.address, + transportType: pRequest.transportType.name, + )) { + // Sign the transaction. + // if params contains `transaction` key we should parse that one and disregard the rest + if (params.containsKey('transaction')) { + final transaction = params['transaction'] as String; + final transactionBytes = base64.decode(transaction); + final signedTx = solana_encoder.SignedTx.fromBytes( + transactionBytes, + ); + + // Sign the transaction. + final signature = await keyPair.sign( + signedTx.compiledMessage.toByteArray(), + ); + + response = response.copyWith( + result: { + 'signature': signature.toBase58(), + }, + ); + } else { + // else we parse the other key/values, see https://docs.walletconnect.com/advanced/multichain/rpc-reference/solana-rpc#solana_signtransaction + final feePayer = params['feePayer'].toString(); + final recentBlockHash = params['recentBlockhash'].toString(); + final instructionsList = params['instructions'] as List; + + final instructions = instructionsList.map((json) { + return (json as Map).toInstruction(); + }).toList(); + + final message = solana.Message(instructions: instructions); + final compiledMessage = message.compile( + recentBlockhash: recentBlockHash, + feePayer: solana.Ed25519HDPublicKey.fromBase58(feePayer), + ); + + // Sign the transaction. + final signature = await keyPair.sign( + compiledMessage.toByteArray(), + ); + + response = response.copyWith( + result: { + 'signature': signature.toBase58(), + }, + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } catch (e, s) { + debugPrint('[SampleWallet] solanaSignTransaction error $e, $s'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + + _handleResponseForTopic(topic, response); + } + + Future _getKeyPair() async { + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + final secKeyBytes = keys[0].privateKey.parse32Bytes(); + return await solana.Ed25519HDKeyPair.fromPrivateKeyBytes( + privateKey: secKeyBytes, + ); + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = _walletKit.sessions.get(topic); + + try { + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } +} + +extension on String { + // SigningKey used by solana package requires a 32 bytes key + Uint8List parse32Bytes() { + try { + final List secBytes = split(',').map((e) => int.parse(e)).toList(); + return Uint8List.fromList(secBytes.sublist(0, 32)); + } catch (e) { + final secKeyBytes = base58.decode(this); + return Uint8List.fromList(secKeyBytes.sublist(0, 32)); + } + } +} + +extension on Map { + solana_encoder.Instruction toInstruction() { + final programId = this['programId'] as String; + final programKey = + solana.Ed25519HDPublicKey(base58.decode(programId).toList()); + + final data = (this['data'] as List).map((e) => e as int).toList(); + final data58 = base58.encode(Uint8List.fromList(data)); + final dataBytes = solana_encoder.ByteArray.fromBase58(data58); + + final keys = this['keys'] as List; + return solana_encoder.Instruction( + programId: programKey, + data: dataBytes, + accounts: keys.map((k) { + final kParams = (k as Map); + return solana_encoder.AccountMeta( + pubKey: solana.Ed25519HDPublicKey.fromBase58(kParams['pubkey']), + isWriteable: kParams['isWritable'] as bool, + isSigner: kParams['isSigner'] as bool, + ); + }).toList(), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/chain_services/solana_service_2.dart b/packages/reown_walletkit/example/lib/dependencies/chain_services/solana_service_2.dart new file mode 100644 index 0000000..77cabf9 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/solana_service_2.dart @@ -0,0 +1,257 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:solana_web3/solana_web3.dart' as solana; +// ignore: implementation_imports +import 'package:solana_web3/src/crypto/nacl.dart' as nacl; +import 'package:bs58/bs58.dart'; + +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_metadata.dart'; +import 'package:reown_walletkit_wallet/utils/methods_utils.dart'; + +/// +/// Uses solana_web3: ^0.1.3 +/// +class SolanaService2 { + Map get solanaRequestHandlers => { + 'solana_signMessage': solanaSignMessage, + 'solana_signTransaction': solanaSignTransaction, + }; + + final _walletKit = GetIt.I().walletKit; + final ChainMetadata chainSupported; + + SolanaService2({required this.chainSupported}) { + for (var handler in solanaRequestHandlers.entries) { + _walletKit.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + } + + Future solanaSignMessage(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] solanaSignMessage request: $parameters'); + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final message = params['message'].toString(); + + final keyPair = await _getKeyPair(); + + // it's being sent encoded from dapp + final base58Decoded = base58.decode(message); + final decodedMessage = utf8.decode(base58Decoded); + if (await MethodsUtils.requestApproval( + decodedMessage, + method: pRequest.method, + chainId: pRequest.chainId, + address: keyPair.pubkey.toBase58(), + transportType: pRequest.transportType.name, + )) { + final signature = await nacl.sign.detached( + base58Decoded, + keyPair.seckey, + ); + + response = response.copyWith( + result: { + 'signature': signature.toBase58(), + }, + ); + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + // + } catch (e) { + debugPrint('[SampleWallet] polkadotSignMessage error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + + _handleResponseForTopic(topic, response); + } + + Future solanaSignTransaction(String topic, dynamic parameters) async { + debugPrint( + '[SampleWallet] solanaSignTransaction: ${jsonEncode(parameters)}'); + final pRequest = _walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params); + + final keyPair = await _getKeyPair(); + + if (await MethodsUtils.requestApproval( + // Show Approval Modal + beautifiedTrx, + method: pRequest.method, + chainId: pRequest.chainId, + address: keyPair.pubkey.toBase58(), + transportType: pRequest.transportType.name, + )) { + // Sign the transaction. + // if params contains `transaction` key we should parse that one and disregard the rest, see https://docs.walletconnect.com/advanced/multichain/rpc-reference/solana-rpc#solana_signtransaction + if (params.containsKey('transaction')) { + final encodedTx = params['transaction'] as String; + final decodedTx = solana.Transaction.fromBase64(encodedTx); + + // Sign the transaction. + decodedTx.sign([keyPair]); + + response = response.copyWith( + result: { + 'signature': decodedTx.signatures.first.toBase58(), + }, + ); + } else { + // else we parse the other key/values, see https://docs.walletconnect.com/advanced/multichain/rpc-reference/solana-rpc#solana_signtransaction + final feePayer = params['feePayer'].toString(); + final recentBlockHash = params['recentBlockhash'].toString(); + final instructionsList = params['instructions'] as List; + + final instructions = instructionsList.map((json) { + return (json as Map).toInstruction(); + }).toList(); + + final decodedTx = solana.Transaction.v0( + payer: solana.Pubkey.fromBase58(feePayer), + instructions: instructions, + recentBlockhash: recentBlockHash, + ); + + // Sign the transaction. + decodedTx.sign([keyPair]); + + response = response.copyWith( + result: { + 'signature': decodedTx.signatures.first.toBase58(), + }, + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + } catch (e, s) { + debugPrint('[SampleWallet] solanaSignTransaction error $e, $s'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError( + code: error.code, + message: error.message, + ), + ); + } + + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + + _handleResponseForTopic(topic, response); + } + + Future _getKeyPair() async { + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + try { + final secKeyBytes = keys[0].privateKey.parse32Bytes(); + return solana.Keypair.fromSeedSync(secKeyBytes); + } catch (e) { + final secKeyBytes = base58.decode(keys[0].privateKey); + // final bytes = Uint8List.fromList(secKeyBytes.sublist(0, 32)); + return solana.Keypair.fromSeckeySync(secKeyBytes); + } + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = _walletKit.sessions.get(topic); + + try { + await _walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } +} + +extension on Map { + solana.TransactionInstruction toInstruction() { + final programId = this['programId'] as String; + + final data = (this['data'] as String); + final dataBytes = base64.decode(data); + + final keys = this['keys'] as List; + return solana.TransactionInstruction( + programId: solana.Pubkey.fromBase58(programId), + data: dataBytes, + keys: keys.map((k) { + final kParams = (k as Map); + return solana.AccountMeta( + solana.Pubkey.fromBase58(kParams['pubkey']), + isSigner: kParams['isSigner'] as bool, + isWritable: kParams['isWritable'] as bool, + ); + }).toList(), + ); + } +} + +extension on String { + // SigningKey used by solana package requires a 32 bytes key + Uint8List parse32Bytes() { + final List secBytes = split(',').map((e) => int.parse(e)).toList(); + return Uint8List.fromList(secBytes.sublist(0, 32)); + } +} + +extension on Uint8List { + String toBase58() => base58.encode(this); +} diff --git a/packages/reown_walletkit/example/lib/dependencies/deep_link_handler.dart b/packages/reown_walletkit/example/lib/dependencies/deep_link_handler.dart new file mode 100644 index 0000000..dde014a --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/deep_link_handler.dart @@ -0,0 +1,72 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; + +class DeepLinkHandler { + static const _methodChannel = MethodChannel( + 'com.walletconnect.flutterwallet/methods', + ); + static const _eventChannel = EventChannel( + 'com.walletconnect.flutterwallet/events', + ); + static final waiting = ValueNotifier(false); + + static void initListener() { + if (kIsWeb) return; + _eventChannel.receiveBroadcastStream().listen( + _onLink, + onError: _onError, + ); + } + + static void checkInitialLink() async { + if (kIsWeb) return; + try { + _methodChannel.invokeMethod('initialLink'); + } catch (e) { + debugPrint('[SampleWallet] [DeepLinkHandler] checkInitialLink $e'); + } + } + + static IReownWalletKit get _walletKit => + GetIt.I().walletKit; + static Uri get nativeUri => + Uri.parse(_walletKit.metadata.redirect?.native ?? ''); + static Uri get universalUri => + Uri.parse(_walletKit.metadata.redirect?.universal ?? ''); + static String get host => universalUri.host; + + static void _onLink(Object? event) async { + final ev = ReownCoreUtils.getSearchParamFromURL('$event', 'wc_ev'); + if (ev.isNotEmpty) { + debugPrint('[SampleWallet] is linkMode $event'); + await _walletKit.dispatchEnvelope('$event'); + } else { + final decodedUri = Uri.parse(Uri.decodeFull(event.toString())); + if (decodedUri.isScheme('wc')) { + debugPrint('[SampleWallet] is legacy uri $decodedUri'); + waiting.value = true; + await _walletKit.pair(uri: decodedUri); + } else { + final uriParam = ReownCoreUtils.getSearchParamFromURL( + '$decodedUri', + 'uri', + ); + if (decodedUri.isScheme(nativeUri.scheme) && uriParam.isNotEmpty) { + debugPrint('[SampleWallet] is custom uri $decodedUri'); + waiting.value = true; + final pairingUri = decodedUri.query.replaceFirst('uri=', ''); + await _walletKit.pair(uri: Uri.parse(pairingUri)); + } + } + } + } + + static void _onError(Object error) { + waiting.value = false; + debugPrint('[SampleWallet] [DeepLinkHandler] _onError $error'); + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/i_walletkit_service.dart b/packages/reown_walletkit/example/lib/dependencies/i_walletkit_service.dart new file mode 100644 index 0000000..0ca3682 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/i_walletkit_service.dart @@ -0,0 +1,15 @@ +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +// class UpdateEvent extends EventArgs { +// final bool loading; + +// UpdateEvent({required this.loading}); +// } + +abstract class IWalletKitService extends Disposable { + Future create(); + Future init(); + + ReownWalletKit get walletKit; +} diff --git a/packages/reown_walletkit/example/lib/dependencies/key_service/chain_key.dart b/packages/reown_walletkit/example/lib/dependencies/key_service/chain_key.dart new file mode 100644 index 0000000..1c64d9d --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/key_service/chain_key.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; + +class ChainKey { + final List chains; + final String privateKey; + final String publicKey; + final String address; + + ChainKey({ + required this.chains, + required this.privateKey, + required this.publicKey, + required this.address, + }); + + String get namespace { + if (chains.isNotEmpty) { + return chains.first.split(':').first; + } + return ''; + } + + Map toJson() => { + 'chains': chains, + 'privateKey': privateKey, + 'publicKey': privateKey, + 'address': address, + }; + + factory ChainKey.fromJson(Map json) { + return ChainKey( + chains: (json['chains'] as List).map((e) => '$e').toList(), + privateKey: json['privateKey'], + publicKey: json['publicKey'], + address: json['address'], + ); + } + + @override + String toString() => jsonEncode(toJson()); +} diff --git a/packages/reown_walletkit/example/lib/dependencies/key_service/i_key_service.dart b/packages/reown_walletkit/example/lib/dependencies/key_service/i_key_service.dart new file mode 100644 index 0000000..06f8943 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/key_service/i_key_service.dart @@ -0,0 +1,27 @@ +import 'package:reown_walletkit_wallet/dependencies/key_service/chain_key.dart'; + +abstract class IKeyService { + Future clearAll(); + + /// Returns a list of all the keys. + Future> loadKeys(); + + /// Returns a list of all the chain ids. + List getChains(); + + /// Returns a list of all the keys for a given chain id. + /// If the chain is not found, returns an empty list. + /// - [chain]: The chain to get the keys for. + List getKeysForChain(String value); + + /// Returns a list of all the accounts in namespace:chainId:address format. + List getAllAccounts(); + + Future createAddressFromSeed(); + + Future loadDefaultWallet(); + + Future getMnemonic(); + + Future restoreWalletFromSeed({required String mnemonic}); +} diff --git a/packages/reown_walletkit/example/lib/dependencies/key_service/key_service.dart b/packages/reown_walletkit/example/lib/dependencies/key_service/key_service.dart new file mode 100644 index 0000000..9bff88b --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/key_service/key_service.dart @@ -0,0 +1,202 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; +import 'package:flutter/foundation.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:reown_walletkit_wallet/dependencies/key_service/chain_key.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_data.dart'; +import 'package:reown_walletkit_wallet/dependencies/bip39/bip39_base.dart' + as bip39; +import 'package:reown_walletkit_wallet/dependencies/bip32/bip32_base.dart' + as bip32; +import 'package:reown_walletkit_wallet/utils/dart_defines.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class KeyService extends IKeyService { + List _keys = []; + + @override + Future clearAll() async { + final prefs = await SharedPreferences.getInstance(); + final keys = prefs.getKeys(); + for (var key in keys) { + if (key.startsWith('w3w_')) { + await prefs.remove(key); + } + } + } + + @override + Future> loadKeys() async { + // ⚠️ WARNING: SharedPreferences is not the best way to store your keys! This is just for example purposes! + final prefs = await SharedPreferences.getInstance(); + try { + final savedKeys = prefs.getStringList('w3w_chain_keys')!; + final chainKeys = savedKeys.map((e) => ChainKey.fromJson(jsonDecode(e))); + _keys = List.from(chainKeys.toList()); + // + final extraKeys = await _extraChainKeys(); + _keys.addAll(extraKeys); + } catch (_) {} + + debugPrint('[$runtimeType] _keys $_keys'); + return _keys; + } + + @override + List getChains() { + final List chainIds = []; + for (final ChainKey key in _keys) { + chainIds.addAll(key.chains); + } + return chainIds; + } + + @override + List getKeysForChain(String value) { + String namespace = value; + if (value.contains(':')) { + namespace = NamespaceUtils.getNamespaceFromChain(value); + } + return _keys.where((e) => e.namespace == namespace).toList(); + } + + @override + List getAllAccounts() { + final List accounts = []; + for (final ChainKey key in _keys) { + for (final String chain in key.chains) { + accounts.add('$chain:${key.address}'); + } + } + return accounts; + } + + @override + Future getMnemonic() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('w3w_mnemonic') ?? ''; + } + + // ** bip39/bip32 - EIP155 ** + + @override + Future loadDefaultWallet() async { + const mnemonic = + 'spoil video deputy round immense setup wasp secret maze slight bag what'; + await restoreWalletFromSeed(mnemonic: mnemonic); + } + + @override + Future createAddressFromSeed() async { + final prefs = await SharedPreferences.getInstance(); + final mnemonic = prefs.getString('w3w_mnemonic')!; + + final chainKeys = getKeysForChain('eip155'); + final index = chainKeys.length; + + final keyPair = _keyPairFromMnemonic(mnemonic, index: index); + final chainKey = _eip155ChainKey(keyPair); + + _keys.add(chainKey); + + await _saveKeys(); + } + + @override + Future restoreWalletFromSeed({required String mnemonic}) async { + // ⚠️ WARNING: SharedPreferences is not the best way to store your keys! This is just for example purposes! + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('w3w_chain_keys'); + await prefs.setString('w3w_mnemonic', mnemonic); + + final keyPair = _keyPairFromMnemonic(mnemonic); + final chainKey = _eip155ChainKey(keyPair); + + _keys = List.from([chainKey]); + + await _saveKeys(); + } + + Future _saveKeys() async { + final prefs = await SharedPreferences.getInstance(); + // Store only eip155 keys + final chainKeys = _keys + .where((k) => k.namespace == 'eip155') + .map((e) => jsonEncode(e.toJson())) + .toList(); + await prefs.setStringList('w3w_chain_keys', chainKeys); + } + + CryptoKeyPair _keyPairFromMnemonic(String mnemonic, {int index = 0}) { + final isValidMnemonic = bip39.validateMnemonic(mnemonic); + if (!isValidMnemonic) { + throw 'Invalid mnemonic'; + } + + final seed = bip39.mnemonicToSeed(mnemonic); + final root = bip32.BIP32.fromSeed(seed); + + final child = root.derivePath("m/44'/60'/0'/0/$index"); + final private = hex.encode(child.privateKey as List); + final public = hex.encode(child.publicKey); + return CryptoKeyPair(private, public); + } + + ChainKey _eip155ChainKey(CryptoKeyPair keyPair) { + final private = EthPrivateKey.fromHex(keyPair.privateKey); + final address = private.address.hex; + final evmChainKey = ChainKey( + chains: ChainData.eip155Chains.map((e) => e.chainId).toList(), + privateKey: keyPair.privateKey, + publicKey: keyPair.publicKey, + address: address, + ); + debugPrint('[SampleWallet] evmChainKey ${evmChainKey.toString()}'); + return evmChainKey; + } + + // ** extra derivations ** + + Future> _extraChainKeys() async { + // HARDCODED VALUES + final kadenaChainKey = _kadenaChainKey(); + final polkadotChainKey = _polkadotChainKey(); + final solanaChainKeys = _solanaChainKey(); + // + return [ + kadenaChainKey, + polkadotChainKey, + solanaChainKeys, + ]; + } + + ChainKey _kadenaChainKey() { + return ChainKey( + chains: ChainData.kadenaChains.map((e) => e.chainId).toList(), + privateKey: DartDefines.kadenaSecretKey, + publicKey: DartDefines.kadenaAddress, + address: DartDefines.kadenaAddress, + ); + } + + ChainKey _polkadotChainKey() { + return ChainKey( + chains: ChainData.polkadotChains.map((e) => e.chainId).toList(), + privateKey: DartDefines.polkadotMnemonic, + publicKey: '', + address: DartDefines.polkadotAddress, + ); + } + + ChainKey _solanaChainKey() { + return ChainKey( + chains: ChainData.solanaChains.map((e) => e.chainId).toList(), + privateKey: DartDefines.solanaSecretKey, + publicKey: DartDefines.solanaAddress, + address: DartDefines.solanaAddress, + ); + } +} diff --git a/packages/reown_walletkit/example/lib/dependencies/walletkit_service.dart b/packages/reown_walletkit/example/lib/dependencies/walletkit_service.dart new file mode 100644 index 0000000..ff7e717 --- /dev/null +++ b/packages/reown_walletkit/example/lib/dependencies/walletkit_service.dart @@ -0,0 +1,389 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer'; + +import 'package:eth_sig_util/util/utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/chain_services/evm_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/deep_link_handler.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/chain_key.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_data.dart'; +import 'package:reown_walletkit_wallet/utils/dart_defines.dart'; +import 'package:reown_walletkit_wallet/utils/eth_utils.dart'; +import 'package:reown_walletkit_wallet/utils/methods_utils.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_request/wc_connection_request_widget.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_request_widget.dart/wc_request_widget.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_request_widget.dart/wc_session_auth_request_widget.dart'; + +class WalletKitService extends IWalletKitService { + final _bottomSheetHandler = GetIt.I(); + ReownWalletKit? _walletKit; + + String get _flavor { + String flavor = '-${const String.fromEnvironment('FLUTTER_APP_FLAVOR')}'; + return flavor.replaceAll('-production', ''); + } + + String _universalLink() { + Uri link = Uri.parse('https://lab.web3modal.com/flutter_walletkit'); + if (_flavor.isNotEmpty) { + return link + .replace(path: '${link.path}_internal') + .replace(host: 'dev.${link.host}') + .toString(); + } + return link.toString(); + } + + Redirect _constructRedirect() { + return Redirect( + native: 'wcflutterwallet$_flavor://', + // universal: _universalLink(), + // // enable linkMode on Wallet so Dapps can use relay-less connection + // // universal: value must be set on cloud config as well + // linkMode: true, + ); + } + + @override + Future create() async { + // Create the ReownWalletKit instance + _walletKit = ReownWalletKit( + core: ReownCore( + projectId: DartDefines.projectId, + logLevel: LogLevel.error, + ), + metadata: PairingMetadata( + name: 'WalletKit Flutter Sample', + description: 'Reown\'s sample wallet with Flutter', + url: _universalLink(), + icons: [ + 'https://docs.walletconnect.com/assets/images/web3walletLogo-54d3b546146931ceaf47a3500868a73a.png' + ], + redirect: _constructRedirect(), + ), + ); + + _walletKit!.core.addLogListener(_logListener); + + // Setup our listeners + debugPrint('[SampleWallet] create'); + _walletKit!.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid); + _walletKit!.core.pairing.onPairingCreate.subscribe(_onPairingCreate); + _walletKit!.core.relayClient.onRelayClientError.subscribe( + _onRelayClientError, + ); + _walletKit!.core.relayClient.onRelayClientMessage.subscribe( + _onRelayClientMessage, + ); + + _walletKit!.onSessionProposal.subscribe(_onSessionProposal); + _walletKit!.onSessionProposalError.subscribe(_onSessionProposalError); + _walletKit!.onSessionConnect.subscribe(_onSessionConnect); + _walletKit!.onSessionAuthRequest.subscribe(_onSessionAuthRequest); + + // Setup our accounts + List chainKeys = await GetIt.I().loadKeys(); + if (chainKeys.isEmpty) { + await GetIt.I().loadDefaultWallet(); + chainKeys = await GetIt.I().loadKeys(); + } + for (final chainKey in chainKeys) { + for (final chainId in chainKey.chains) { + if (chainId.startsWith('kadena')) { + final account = '$chainId:k**${chainKey.address}'; + debugPrint('[SampleWallet] registerAccount $account'); + _walletKit!.registerAccount( + chainId: chainId, + accountAddress: 'k**${chainKey.address}', + ); + } else { + final account = '$chainId:${chainKey.address}'; + debugPrint('[SampleWallet] registerAccount $account'); + _walletKit!.registerAccount( + chainId: chainId, + accountAddress: chainKey.address, + ); + } + } + } + } + + @override + Future init() async { + // Await the initialization of the ReownWalletKit instance + await _walletKit!.init(); + await _emitEvent(); + } + + Future _emitEvent() async { + final isOnline = _walletKit!.core.connectivity.isOnline.value; + if (!isOnline) { + await Future.delayed(const Duration(milliseconds: 500)); + _emitEvent(); + return; + } + + final sessions = _walletKit!.sessions.getAll(); + for (var session in sessions) { + try { + final events = NamespaceUtils.getNamespacesEventsForChain( + chainId: 'eip155:1', + namespaces: session.namespaces, + ); + if (events.contains('accountsChanged')) { + final chainKeys = GetIt.I().getKeysForChain('eip155'); + _walletKit!.emitSessionEvent( + topic: session.topic, + chainId: 'eip155:1', + event: SessionEventParams( + name: 'accountsChanged', + data: [chainKeys.first.address], + ), + ); + } + } catch (_) {} + } + } + + void _logListener(LogEvent event) { + if (event.level == Level.debug) { + // TODO send to mixpanel + log('${event.message}'); + } else { + debugPrint('${event.message}'); + } + } + + @override + FutureOr onDispose() { + _walletKit!.core.removeLogListener(_logListener); + _walletKit!.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid); + _walletKit!.core.pairing.onPairingCreate.unsubscribe(_onPairingCreate); + _walletKit!.core.relayClient.onRelayClientError.unsubscribe( + _onRelayClientError, + ); + _walletKit!.core.relayClient.onRelayClientMessage.unsubscribe( + _onRelayClientMessage, + ); + + _walletKit!.onSessionProposal.unsubscribe(_onSessionProposal); + _walletKit!.onSessionProposalError.unsubscribe(_onSessionProposalError); + _walletKit!.onSessionConnect.unsubscribe(_onSessionConnect); + _walletKit!.onSessionAuthRequest.unsubscribe(_onSessionAuthRequest); + } + + @override + ReownWalletKit get walletKit => _walletKit!; + + List get _loaderMethods => [ + MethodConstants.WC_SESSION_PROPOSE, + MethodConstants.WC_SESSION_REQUEST, + MethodConstants.WC_SESSION_AUTHENTICATE, + ]; + + void _onRelayClientMessage(MessageEvent? event) async { + if (event != null) { + final jsonObject = await EthUtils.decodeMessageEvent(event); + debugPrint('[SampleWallet] _onRelayClientMessage $jsonObject'); + if (jsonObject is JsonRpcRequest) { + DeepLinkHandler.waiting.value = _loaderMethods.contains( + jsonObject.method, + ); + } + } + } + + void _onSessionProposal(SessionProposalEvent? args) async { + debugPrint('[SampleWallet] _onSessionProposal ${jsonEncode(args?.params)}'); + if (args != null) { + final proposer = args.params.proposer; + final result = (await _bottomSheetHandler.queueBottomSheet( + widget: WCRequestWidget( + verifyContext: args.verifyContext, + child: WCConnectionRequestWidget( + proposalData: args.params, + verifyContext: args.verifyContext, + requester: proposer, + ), + ), + )) ?? + WCBottomSheetResult.reject; + + if (result != WCBottomSheetResult.reject) { + // generatedNamespaces is constructed based on registered methods handlers + // so if you want to handle requests using onSessionRequest event then you would need to manually add that method in the approved namespaces + try { + final session = await _walletKit!.approveSession( + id: args.id, + namespaces: NamespaceUtils.regenerateNamespacesWithChains( + args.params.generatedNamespaces!, + ), + sessionProperties: args.params.sessionProperties, + ); + MethodsUtils.handleRedirect( + session.topic, + session.session!.peer.metadata.redirect, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + '', + proposer.metadata.redirect, + error.message, + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError(); + await _walletKit!.rejectSession(id: args.id, reason: error); + await _walletKit!.core.pairing.disconnect( + topic: args.params.pairingTopic, + ); + MethodsUtils.handleRedirect( + '', + proposer.metadata.redirect, + error.message, + ); + } + } + } + + void _onSessionProposalError(SessionProposalErrorEvent? args) async { + debugPrint('[SampleWallet] _onSessionProposalError $args'); + DeepLinkHandler.waiting.value = false; + if (args != null) { + String errorMessage = args.error.message; + if (args.error.code == 5100) { + errorMessage = + errorMessage.replaceFirst('Requested:', '\n\nRequested:'); + errorMessage = + errorMessage.replaceFirst('Supported:', '\n\nSupported:'); + } + MethodsUtils.goBackModal( + title: 'Error', + message: errorMessage, + success: false, + ); + } + } + + void _onSessionConnect(SessionConnect? args) { + if (args != null) { + final session = jsonEncode(args.session.toJson()); + debugPrint('[SampleWallet] _onSessionConnect $session'); + } + } + + void _onRelayClientError(ErrorEvent? args) { + debugPrint('[SampleWallet] _onRelayClientError ${args?.error}'); + } + + void _onPairingInvalid(PairingInvalidEvent? args) { + debugPrint('[SampleWallet] _onPairingInvalid $args'); + } + + void _onPairingCreate(PairingEvent? args) { + debugPrint('[SampleWallet] _onPairingCreate $args'); + } + + void _onSessionAuthRequest(SessionAuthRequest? args) async { + if (args != null) { + final SessionAuthPayload authPayload = args.authPayload; + final jsonPyaload = jsonEncode(authPayload.toJson()); + debugPrint('[SampleWallet] _onSessionAuthRequest $jsonPyaload'); + final supportedChains = ChainData.eip155Chains.map((e) => e.chainId); + final supportedMethods = SupportedEVMMethods.values.map((e) => e.name); + final newAuthPayload = AuthSignature.populateAuthPayload( + authPayload: authPayload, + chains: supportedChains.toList(), + methods: supportedMethods.toList(), + ); + final cacaoRequestPayload = CacaoRequestPayload.fromSessionAuthPayload( + newAuthPayload, + ); + final List> formattedMessages = []; + for (var chain in newAuthPayload.chains) { + final chainKeys = GetIt.I().getKeysForChain(chain); + final iss = 'did:pkh:$chain:${chainKeys.first.address}'; + final message = _walletKit!.formatAuthMessage( + iss: iss, + cacaoPayload: cacaoRequestPayload, + ); + formattedMessages.add({iss: message}); + } + + final WCBottomSheetResult rs = + (await _bottomSheetHandler.queueBottomSheet( + widget: WCSessionAuthRequestWidget( + child: WCConnectionRequestWidget( + sessionAuthPayload: newAuthPayload, + verifyContext: args.verifyContext, + requester: args.requester, + ), + ), + )) ?? + WCBottomSheetResult.reject; + + if (rs != WCBottomSheetResult.reject) { + final chainKeys = GetIt.I().getKeysForChain('eip155'); + final privateKey = '0x${chainKeys.first.privateKey}'; + final credentials = EthPrivateKey.fromHex(privateKey); + // + final messageToSign = formattedMessages.length; + final count = (rs == WCBottomSheetResult.one) ? 1 : messageToSign; + // + final List cacaos = []; + for (var i = 0; i < count; i++) { + final iss = formattedMessages[i].keys.first; + final message = formattedMessages[i].values.first; + final signature = credentials.signPersonalMessageToUint8List( + Uint8List.fromList(message.codeUnits), + ); + final hexSignature = bytesToHex(signature, include0x: true); + cacaos.add( + AuthSignature.buildAuthObject( + requestPayload: cacaoRequestPayload, + signature: CacaoSignature( + t: CacaoSignature.EIP191, + s: hexSignature, + ), + iss: iss, + ), + ); + } + // + try { + final session = await _walletKit!.approveSessionAuthenticate( + id: args.id, + auths: cacaos, + ); + MethodsUtils.handleRedirect( + session.topic, + session.session?.peer.metadata.redirect, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + args.topic, + args.requester.metadata.redirect, + error.message, + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED_AUTH); + await _walletKit!.rejectSessionAuthenticate( + id: args.id, + reason: error.toSignError(), + ); + MethodsUtils.handleRedirect( + args.topic, + args.requester.metadata.redirect, + error.message, + ); + } + } + } +} diff --git a/packages/reown_walletkit/example/lib/main.dart b/packages/reown_walletkit/example/lib/main.dart new file mode 100644 index 0000000..adf9c2f --- /dev/null +++ b/packages/reown_walletkit/example/lib/main.dart @@ -0,0 +1,249 @@ +import 'package:get_it/get_it.dart'; +import 'package:get_it_mixin/get_it_mixin.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/bottom_sheet_listener.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/chain_services/cosmos_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/chain_services/evm_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/chain_services/kadena_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/chain_services/polkadot_service.dart'; +// ignore: unused_import +import 'package:reown_walletkit_wallet/dependencies/chain_services/solana_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/chain_services/solana_service_2.dart'; +import 'package:reown_walletkit_wallet/dependencies/deep_link_handler.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/key_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/walletkit_service.dart'; +import 'package:reown_walletkit_wallet/models/chain_data.dart'; +import 'package:reown_walletkit_wallet/models/page_data.dart'; +import 'package:reown_walletkit_wallet/pages/apps_page.dart'; +import 'package:reown_walletkit_wallet/pages/settings_page.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/utils/string_constants.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + DeepLinkHandler.initListener(); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: StringConstants.appTitle, + home: MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget with GetItStatefulWidgetMixin { + MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State with GetItStateMixin { + bool _initializing = true; + + List _pageDatas = []; + int _selectedIndex = 0; + + @override + void initState() { + super.initState(); + initialize(); + } + + Future initialize() async { + GetIt.I.registerSingleton(BottomSheetService()); + GetIt.I.registerSingleton(KeyService()); + + final walletKitService = WalletKitService(); + await walletKitService.create(); + GetIt.I.registerSingleton(walletKitService); + + // Support EVM Chains + for (final chainData in ChainData.eip155Chains) { + GetIt.I.registerSingleton( + EVMService(chainSupported: chainData), + instanceName: chainData.chainId, + ); + } + + // Support Kadena Chains + for (final chainData in ChainData.kadenaChains) { + GetIt.I.registerSingleton( + KadenaService(chainSupported: chainData), + instanceName: chainData.chainId, + ); + } + + // Support Polkadot Chains + for (final chainData in ChainData.polkadotChains) { + GetIt.I.registerSingleton( + PolkadotService(chainSupported: chainData), + instanceName: chainData.chainId, + ); + } + + // Support Solana Chains + // Change SolanaService2 to SolanaService to switch between solana_web3: ^0.1.3 to solana: ^0.30.4 + for (final chainData in ChainData.solanaChains) { + GetIt.I.registerSingleton( + SolanaService2(chainSupported: chainData), + instanceName: chainData.chainId, + ); + } + + // Support Cosmos Chains + for (final chainData in ChainData.cosmosChains) { + GetIt.I.registerSingleton( + CosmosService(chainSupported: chainData), + instanceName: chainData.chainId, + ); + } + + await walletKitService.init(); + + walletKitService.walletKit.core.relayClient.onRelayClientConnect.subscribe( + _setState, + ); + walletKitService.walletKit.core.relayClient.onRelayClientDisconnect + .subscribe( + _setState, + ); + + setState(() { + _pageDatas = [ + PageData( + page: AppsPage(), + title: StringConstants.connectPageTitle, + icon: Icons.swap_vert_circle_outlined, + ), + // PageData( + // page: const Center( + // child: Text( + // 'Inbox (Not Implemented)', + // style: StyleConstants.bodyText, + // ), + // ), + // title: 'Inbox', + // icon: Icons.inbox_rounded, + // ), + PageData( + page: const SettingsPage(), + title: 'Settings', + icon: Icons.settings_outlined, + ), + ]; + + _initializing = false; + }); + } + + void _setState(dynamic args) => setState(() {}); + + @override + Widget build(BuildContext context) { + if (_initializing) { + return const Material( + child: Center( + child: CircularProgressIndicator( + color: StyleConstants.primaryColor, + ), + ), + ); + } + + final List navRail = []; + if (MediaQuery.of(context).size.width >= Constants.smallScreen) { + navRail.add(_buildNavigationRail()); + } + navRail.add( + Expanded( + child: _pageDatas[_selectedIndex].page, + ), + ); + + final walletKit = GetIt.I().walletKit; + return Scaffold( + appBar: AppBar( + title: Text( + _pageDatas[_selectedIndex].title, + style: const TextStyle(color: Colors.black), + ), + actions: [ + const Text('Relay '), + CircleAvatar( + radius: 6.0, + backgroundColor: walletKit.core.relayClient.isConnected + ? Colors.green + : Colors.red, + ), + const SizedBox(width: 16.0), + ], + ), + body: BottomSheetListener( + child: Row( + mainAxisSize: MainAxisSize.max, + children: navRail, + ), + ), + bottomNavigationBar: + MediaQuery.of(context).size.width < Constants.smallScreen + ? _buildBottomNavBar() + : null, + ); + } + + Widget _buildBottomNavBar() { + return BottomNavigationBar( + currentIndex: _selectedIndex, + unselectedItemColor: Colors.grey, + selectedItemColor: Colors.black, + // called when one tab is selected + onTap: (int index) { + setState(() { + _selectedIndex = index; + }); + }, + // bottom tab items + items: _pageDatas + .map( + (e) => BottomNavigationBarItem( + icon: Icon(e.icon), + label: e.title, + ), + ) + .toList(), + ); + } + + Widget _buildNavigationRail() { + return NavigationRail( + // backgroundColor: StyleConstants.backgroundColor, + selectedIndex: _selectedIndex, + onDestinationSelected: (int index) { + setState(() { + _selectedIndex = index; + }); + }, + labelType: NavigationRailLabelType.selected, + destinations: _pageDatas + .map( + (e) => NavigationRailDestination( + icon: Icon(e.icon), + label: Text(e.title), + ), + ) + .toList(), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/models/accounts.dart b/packages/reown_walletkit/example/lib/models/accounts.dart new file mode 100644 index 0000000..309725e --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/accounts.dart @@ -0,0 +1,76 @@ +import 'package:flutter/foundation.dart'; + +class AccountDetails { + final String address; + final String chain; + + const AccountDetails({ + required this.address, + required this.chain, + }); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AccountDetails && + other.address == address && + other.chain == chain; + } + + @override + int get hashCode => address.hashCode ^ chain.hashCode; +} + +class Account { + final int id; + final String name; + final String mnemonic; + final String privateKey; + final List details; + + const Account({ + required this.id, + required this.name, + required this.mnemonic, + required this.privateKey, + required this.details, + }); + + Account copyWith({ + int? id, + String? name, + String? mnemonic, + String? privateKey, + List? details, + }) { + return Account( + id: id ?? this.id, + name: name ?? this.name, + mnemonic: mnemonic ?? this.mnemonic, + privateKey: privateKey ?? this.privateKey, + details: details ?? this.details, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Account && + other.id == id && + other.name == name && + other.mnemonic == mnemonic && + other.privateKey == privateKey && + listEquals(other.details, details); + } + + @override + int get hashCode { + return id.hashCode ^ + name.hashCode ^ + mnemonic.hashCode ^ + privateKey.hashCode ^ + details.hashCode; + } +} diff --git a/packages/reown_walletkit/example/lib/models/chain_data.dart b/packages/reown_walletkit/example/lib/models/chain_data.dart new file mode 100644 index 0000000..e85a2e7 --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/chain_data.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/models/chain_metadata.dart'; + +class ChainData { + static final List eip155Chains = [ + ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:1', + name: 'Ethereum', + logo: '/chain-logos/eip155-1.png', + color: Colors.blue.shade300, + rpc: ['https://eth.drpc.org'], + ), + ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:137', + name: 'Polygon', + logo: '/chain-logos/eip155-137.png', + color: Colors.purple.shade300, + rpc: ['https://polygon-rpc.com/'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:42161', + name: 'Arbitrum', + logo: '/chain-logos/eip155-42161.png', + color: Colors.blue, + rpc: ['https://arbitrum.blockpi.network/v1/rpc/public'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:10', + name: 'OP Mainnet', + logo: '/chain-logos/eip155-10.png', + color: Colors.red, + rpc: ['https://mainnet.optimism.io/'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:43114', + name: 'Avalanche', + logo: '/chain-logos/eip155-43114.png', + color: Colors.orange, + rpc: ['https://api.avax.network/ext/bc/C/rpc'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:56', + name: 'BNB Smart Chain Mainnet', + logo: '/chain-logos/eip155-56.png', + color: Colors.orange, + rpc: ['https://bsc-dataseed1.bnbchain.org'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:42220', + name: 'Celo', + logo: '/chain-logos/eip155-42220.png', + color: Colors.green, + rpc: ['https://forno.celo.org/'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:100', + name: 'Gnosis', + logo: '/chain-logos/eip155-100.png', + color: Colors.greenAccent, + rpc: ['https://rpc.gnosischain.com/'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:324', + name: 'zkSync', + logo: '/chain-logos/eip155-324.png', + color: Colors.black, + rpc: ['https://mainnet.era.zksync.io'], + ), + ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:11155111', + name: 'Sepolia', + logo: '/chain-logos/eip155-1.png', + color: Colors.blue.shade300, + isTestnet: true, + rpc: ['https://ethereum-sepolia.publicnode.com'], + ), + ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:80001', + name: 'Polygon Mumbai', + logo: '/chain-logos/eip155-137.png', + color: Colors.purple.shade300, + isTestnet: true, + rpc: ['https://matic-mumbai.chainstacklabs.com'], + ), + ]; + + static final List solanaChains = [ + const ChainMetadata( + type: ChainType.solana, + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana Mainnet', + logo: '/chain-logos/solana.png', + color: Color.fromARGB(255, 247, 0, 255), + rpc: ['https://api.mainnet-beta.solana.com'], + ), + const ChainMetadata( + type: ChainType.solana, + chainId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + name: 'Solana Devnet', + logo: '/chain-logos/solana.png', + color: Color.fromARGB(255, 247, 0, 255), + rpc: ['https://api.devnet.solana.com'], + ), + const ChainMetadata( + type: ChainType.solana, + chainId: 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', + name: 'Solana Testnet', + logo: '/chain-logos/solana.png', + color: Colors.black, + isTestnet: true, + rpc: ['https://api.testnet.solana.com'], + ), + ]; + + static final List cosmosChains = [ + // TODO TO BE SUPPORTED + const ChainMetadata( + type: ChainType.cosmos, + chainId: 'cosmos:cosmoshub-4', + name: 'Cosmos Mainnet', + logo: '/chain-logos/cosmos.png', + color: Colors.purple, + rpc: [ + 'https://cosmos-rpc.polkachu.com:443', + 'https://rpc-cosmoshub-ia.cosmosia.notional.ventures:443', + 'https://rpc.cosmos.network:443', + ], + ), + ]; + + static final List kadenaChains = [ + const ChainMetadata( + type: ChainType.kadena, + chainId: 'kadena:mainnet01', + name: 'Kadena Mainnet', + logo: '/chain-logos/kadena.png', + color: Colors.green, + rpc: [ + 'https://api.chainweb.com', + ], + ), + const ChainMetadata( + type: ChainType.kadena, + chainId: 'kadena:testnet04', + name: 'Kadena Testnet', + logo: '/chain-logos/kadena.png', + color: Colors.green, + isTestnet: true, + rpc: [ + 'https://api.chainweb.com', + ], + ), + ]; + + static final List polkadotChains = [ + const ChainMetadata( + type: ChainType.polkadot, + chainId: 'polkadot:91b171bb158e2d3848fa23a9f1c25182', + name: 'Polkadot Mainnet', + logo: '/chain-logos/polkadot.png', + color: Color.fromARGB(255, 174, 57, 220), + rpc: [ + 'wss://rpc.polkadot.io', + // 'wss://rpc.matrix.canary.enjin.io' + ], + ), + const ChainMetadata( + type: ChainType.polkadot, + chainId: 'polkadot:e143f23803ac50e8f6f8e62695d1ce9e', + name: 'Polkadot Testnet (Westend)', + logo: '/chain-logos/polkadot.png', + color: Color.fromARGB(255, 174, 57, 220), + isTestnet: true, + rpc: [ + 'wss://westend-rpc.polkadot.io', + ], + ), + ]; +} diff --git a/packages/reown_walletkit/example/lib/models/chain_metadata.dart b/packages/reown_walletkit/example/lib/models/chain_metadata.dart new file mode 100644 index 0000000..38aee3d --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/chain_metadata.dart @@ -0,0 +1,51 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +enum ChainType { + eip155, + solana, + cosmos, + kadena, + polkadot, +} + +class ChainMetadata { + final String chainId; + final String name; + final String logo; + final bool isTestnet; + final Color color; + final ChainType type; + final List rpc; + + const ChainMetadata({ + required this.chainId, + required this.name, + required this.logo, + this.isTestnet = false, + required this.color, + required this.type, + required this.rpc, + }); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ChainMetadata && + other.chainId == chainId && + other.name == name && + other.logo == logo && + other.isTestnet == isTestnet && + listEquals(other.rpc, rpc); + } + + @override + int get hashCode { + return chainId.hashCode ^ + name.hashCode ^ + logo.hashCode ^ + rpc.hashCode ^ + isTestnet.hashCode; + } +} diff --git a/packages/reown_walletkit/example/lib/models/eth/ethereum_sign_message.dart b/packages/reown_walletkit/example/lib/models/eth/ethereum_sign_message.dart new file mode 100644 index 0000000..f5dda50 --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/eth/ethereum_sign_message.dart @@ -0,0 +1,19 @@ +enum WCSignType { + message, + personalMessage, + typedMessageV2, + typedMessageV3, + typedMessageV4, +} + +class EthereumSignMessage { + final String data; + final String address; + final WCSignType type; + + const EthereumSignMessage({ + required this.data, + required this.address, + required this.type, + }); +} diff --git a/packages/reown_walletkit/example/lib/models/eth/ethereum_transaction.dart b/packages/reown_walletkit/example/lib/models/eth/ethereum_transaction.dart new file mode 100644 index 0000000..9b3e27b --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/eth/ethereum_transaction.dart @@ -0,0 +1,40 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'ethereum_transaction.g.dart'; + +@JsonSerializable(includeIfNull: false) +class EthereumTransaction { + final String from; + final String to; + final String value; + final String? nonce; + final String? gasPrice; + final String? maxFeePerGas; + final String? maxPriorityFeePerGas; + final String? gas; + final String? gasLimit; + final String? data; + + EthereumTransaction({ + required this.from, + required this.to, + required this.value, + this.nonce, + this.gasPrice, + this.maxFeePerGas, + this.maxPriorityFeePerGas, + this.gas, + this.gasLimit, + this.data, + }); + + factory EthereumTransaction.fromJson(Map json) => + _$EthereumTransactionFromJson(json); + + Map toJson() => _$EthereumTransactionToJson(this); + + @override + String toString() { + return 'WCEthereumTransaction(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)'; + } +} diff --git a/packages/reown_walletkit/example/lib/models/eth/ethereum_transaction.g.dart b/packages/reown_walletkit/example/lib/models/eth/ethereum_transaction.g.dart new file mode 100644 index 0000000..bcf198d --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/eth/ethereum_transaction.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ethereum_transaction.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +EthereumTransaction _$EthereumTransactionFromJson(Map json) => + EthereumTransaction( + from: json['from'] as String, + to: json['to'] as String, + value: json['value'] as String, + nonce: json['nonce'] as String?, + gasPrice: json['gasPrice'] as String?, + maxFeePerGas: json['maxFeePerGas'] as String?, + maxPriorityFeePerGas: json['maxPriorityFeePerGas'] as String?, + gas: json['gas'] as String?, + gasLimit: json['gasLimit'] as String?, + data: json['data'] as String?, + ); + +Map _$EthereumTransactionToJson(EthereumTransaction instance) { + final val = { + 'from': instance.from, + 'to': instance.to, + 'value': instance.value, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('nonce', instance.nonce); + writeNotNull('gasPrice', instance.gasPrice); + writeNotNull('maxFeePerGas', instance.maxFeePerGas); + writeNotNull('maxPriorityFeePerGas', instance.maxPriorityFeePerGas); + writeNotNull('gas', instance.gas); + writeNotNull('gasLimit', instance.gasLimit); + writeNotNull('data', instance.data); + return val; +} diff --git a/packages/reown_walletkit/example/lib/models/page_data.dart b/packages/reown_walletkit/example/lib/models/page_data.dart new file mode 100644 index 0000000..1a9f4e4 --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/page_data.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class PageData { + Widget page; + String title; + IconData icon; + + PageData({ + required this.page, + required this.title, + required this.icon, + }); +} diff --git a/packages/reown_walletkit/example/lib/models/solana/solana_sign_transaction.dart b/packages/reown_walletkit/example/lib/models/solana/solana_sign_transaction.dart new file mode 100644 index 0000000..294aded --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/solana/solana_sign_transaction.dart @@ -0,0 +1,72 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'solana_sign_transaction.g.dart'; + +@JsonSerializable(includeIfNull: false) +class SolanaSignTransaction { + final String feePayer; + final String recentBlockhash; + final List instructions; + + SolanaSignTransaction({ + required this.feePayer, + required this.recentBlockhash, + required this.instructions, + }); + + factory SolanaSignTransaction.fromJson(Map json) => + _$SolanaSignTransactionFromJson(json); + + Map toJson() => _$SolanaSignTransactionToJson(this); + + @override + String toString() { + return 'SolanaSignTransaction(feePayer: $feePayer, recentBlockhash: $recentBlockhash, instructions: $instructions)'; + } +} + +@JsonSerializable(includeIfNull: false) +class SolanaInstruction { + final String programId; + final List keys; + final String data; + + SolanaInstruction({ + required this.programId, + required this.keys, + required this.data, + }); + + factory SolanaInstruction.fromJson(Map json) => + _$SolanaInstructionFromJson(json); + + Map toJson() => _$SolanaInstructionToJson(this); + + @override + String toString() { + return 'SolanaInstruction(programId: $programId, keys: $keys, data: $data)'; + } +} + +@JsonSerializable(includeIfNull: false) +class SolanaKeyMetadata { + final String pubkey; + final bool isSigner; + final bool isWritable; + + SolanaKeyMetadata({ + required this.pubkey, + required this.isSigner, + required this.isWritable, + }); + + factory SolanaKeyMetadata.fromJson(Map json) => + _$SolanaKeyMetadataFromJson(json); + + Map toJson() => _$SolanaKeyMetadataToJson(this); + + @override + String toString() { + return 'SolanaKeyMetadata(pubkey: $pubkey, isSigner: $isSigner, isWritable: $isWritable)'; + } +} diff --git a/packages/reown_walletkit/example/lib/models/solana/solana_sign_transaction.g.dart b/packages/reown_walletkit/example/lib/models/solana/solana_sign_transaction.g.dart new file mode 100644 index 0000000..ada6a96 --- /dev/null +++ b/packages/reown_walletkit/example/lib/models/solana/solana_sign_transaction.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'solana_sign_transaction.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SolanaSignTransaction _$SolanaSignTransactionFromJson( + Map json) => + SolanaSignTransaction( + feePayer: json['feePayer'] as String, + recentBlockhash: json['recentBlockhash'] as String, + instructions: (json['instructions'] as List) + .map((e) => SolanaInstruction.fromJson(e as Map)) + .toList(), + ); + +Map _$SolanaSignTransactionToJson( + SolanaSignTransaction instance) => + { + 'feePayer': instance.feePayer, + 'recentBlockhash': instance.recentBlockhash, + 'instructions': instance.instructions, + }; + +SolanaInstruction _$SolanaInstructionFromJson(Map json) => + SolanaInstruction( + programId: json['programId'] as String, + keys: (json['keys'] as List) + .map((e) => SolanaKeyMetadata.fromJson(e as Map)) + .toList(), + data: json['data'] as String, + ); + +Map _$SolanaInstructionToJson(SolanaInstruction instance) => + { + 'programId': instance.programId, + 'keys': instance.keys, + 'data': instance.data, + }; + +SolanaKeyMetadata _$SolanaKeyMetadataFromJson(Map json) => + SolanaKeyMetadata( + pubkey: json['pubkey'] as String, + isSigner: json['isSigner'] as bool, + isWritable: json['isWritable'] as bool, + ); + +Map _$SolanaKeyMetadataToJson(SolanaKeyMetadata instance) => + { + 'pubkey': instance.pubkey, + 'isSigner': instance.isSigner, + 'isWritable': instance.isWritable, + }; diff --git a/packages/reown_walletkit/example/lib/pages/app_detail_page.dart b/packages/reown_walletkit/example/lib/pages/app_detail_page.dart new file mode 100644 index 0000000..a653bed --- /dev/null +++ b/packages/reown_walletkit/example/lib/pages/app_detail_page.dart @@ -0,0 +1,218 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/utils/methods_utils.dart'; +import 'package:reown_walletkit_wallet/utils/namespace_model_builder.dart'; +import 'package:reown_walletkit_wallet/widgets/custom_button.dart'; + +class AppDetailPage extends StatefulWidget { + final PairingInfo pairing; + + const AppDetailPage({ + super.key, + required this.pairing, + }); + + @override + AppDetailPageState createState() => AppDetailPageState(); +} + +class AppDetailPageState extends State { + late ReownWalletKit _walletKit; + + @override + void initState() { + super.initState(); + _walletKit = GetIt.I().walletKit; + _walletKit.onSessionDelete.subscribe(_onSessionDelete); + _walletKit.onSessionExpire.subscribe(_onSessionDelete); + } + + @override + void dispose() { + _walletKit.onSessionDelete.unsubscribe(_onSessionDelete); + _walletKit.onSessionExpire.unsubscribe(_onSessionDelete); + super.dispose(); + } + + void _onSessionDelete(dynamic args) { + setState(() { + _walletKit = GetIt.I().walletKit; + }); + } + + @override + Widget build(BuildContext context) { + final metadata = widget.pairing.peerMetadata; + DateTime dateTime = + DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000); + int year = dateTime.year; + int month = dateTime.month; + int day = dateTime.day; + + String expiryDate = + '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; + + final sessions = _walletKit.sessions + .getAll() + .where((element) => element.pairingTopic == widget.pairing.topic) + .toList(); + + List sessionWidgets = []; + for (final SessionData session in sessions) { + final namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces( + session.topic, + session.namespaces, + context, + ); + // Loop through and add the namespace widgets, but put 20 pixels between each one + for (int i = 0; i < namespaceWidget.length; i++) { + sessionWidgets.add(namespaceWidget[i]); + if (i != namespaceWidget.length - 1) { + sessionWidgets.add(const SizedBox(height: 20.0)); + } + } + sessionWidgets.add(const SizedBox.square(dimension: 10.0)); + sessionWidgets.add( + Row( + children: [ + CustomButton( + type: CustomButtonType.normal, + onTap: () async { + try { + await _walletKit.disconnectSession( + topic: session.topic, + reason: Errors.getSdkError(Errors.USER_DISCONNECTED) + .toSignError(), + ); + setState(() {}); + } catch (e) { + debugPrint('[SampleWallet] ${e.toString()}'); + } + }, + child: const Center( + child: Text( + 'Disconnect Session', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ); + } + return Scaffold( + appBar: AppBar( + title: Text(metadata?.name ?? 'Unknown'), + actions: [ + Visibility( + visible: metadata?.redirect?.native != null, + child: IconButton( + icon: const Icon( + Icons.open_in_new_rounded, + ), + onPressed: () { + MethodsUtils.openApp( + sessions.first.topic, + sessions.first.peer.metadata.redirect, + ); + }, + ), + ) + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.only( + left: StyleConstants.linear8, + top: StyleConstants.linear8, + right: StyleConstants.linear8, + bottom: StyleConstants.linear32, + ), + child: Column( + children: [ + Visibility( + visible: metadata != null, + child: Row( + children: [ + CircleAvatar( + radius: 40.0, + backgroundImage: ((metadata?.icons ?? []).isNotEmpty + ? NetworkImage(metadata!.icons[0]) + : const AssetImage( + 'assets/images/default_icon.png')) + as ImageProvider, + ), + const SizedBox(width: 10.0), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(metadata?.url ?? ''), + Text('Expires on: $expiryDate'), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 20.0), + Visibility( + visible: metadata != null, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: sessionWidgets, + ), + ), + const SizedBox(height: 20.0), + Row( + children: [ + CustomButton( + type: CustomButtonType.invalid, + onTap: () async { + try { + await _walletKit.core.pairing.disconnect( + topic: widget.pairing.topic, + ); + final topicSession = sessions.where( + (s) => s.pairingTopic == widget.pairing.topic, + ); + for (var session in topicSession) { + await _walletKit.disconnectSession( + topic: session.topic, + reason: Errors.getSdkError(Errors.USER_DISCONNECTED) + .toSignError(), + ); + } + _back(); + } catch (e) { + debugPrint('[SampleWallet] ${e.toString()}'); + } + }, + child: const Center( + child: Text( + 'Delete Pairing', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } + + void _back() { + Navigator.of(context).pop(); + } +} diff --git a/packages/reown_walletkit/example/lib/pages/apps_page.dart b/packages/reown_walletkit/example/lib/pages/apps_page.dart new file mode 100644 index 0000000..dd297af --- /dev/null +++ b/packages/reown_walletkit/example/lib/pages/apps_page.dart @@ -0,0 +1,272 @@ +import 'dart:async'; + +import 'package:fl_toast/fl_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:get_it_mixin/get_it_mixin.dart'; +import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/deep_link_handler.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/pages/app_detail_page.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/utils/eth_utils.dart'; +import 'package:reown_walletkit_wallet/utils/string_constants.dart'; +import 'package:reown_walletkit_wallet/widgets/pairing_item.dart'; +import 'package:reown_walletkit_wallet/widgets/uri_input_popup.dart'; + +class AppsPage extends StatefulWidget with GetItStatefulWidgetMixin { + AppsPage({Key? key}) : super(key: key); + + @override + AppsPageState createState() => AppsPageState(); +} + +class AppsPageState extends State with GetItStateMixin { + List _pairings = []; + late IWalletKitService _walletKitService; + late IReownWalletKit _walletKit; + + @override + void initState() { + super.initState(); + _walletKitService = GetIt.I(); + _walletKit = _walletKitService.walletKit; + _pairings = _walletKit.pairings.getAll(); + _pairings = _pairings.where((p) => p.active).toList(); + // + _registerListeners(); + // TODO _walletKit.core.echo.register(firebaseAccessToken); + DeepLinkHandler.checkInitialLink(); + } + + void _registerListeners() { + _walletKit.core.relayClient.onRelayClientMessage.subscribe( + _onRelayClientMessage, + ); + _walletKit.pairings.onSync.subscribe(_refreshState); + _walletKit.pairings.onUpdate.subscribe(_refreshState); + _walletKit.onSessionConnect.subscribe(_refreshState); + _walletKit.onSessionDelete.subscribe(_refreshState); + } + + void _unregisterListeners() { + _walletKit.onSessionDelete.unsubscribe(_refreshState); + _walletKit.onSessionConnect.unsubscribe(_refreshState); + _walletKit.pairings.onSync.unsubscribe(_refreshState); + _walletKit.pairings.onUpdate.unsubscribe(_refreshState); + _walletKit.core.relayClient.onRelayClientMessage.unsubscribe( + _onRelayClientMessage, + ); + } + + @override + void dispose() { + _unregisterListeners(); + super.dispose(); + } + + void _refreshState(dynamic event) async { + setState(() {}); + } + + void _onRelayClientMessage(MessageEvent? event) async { + _refreshState(event); + if (event != null) { + final jsonObject = await EthUtils.decodeMessageEvent(event); + if (!mounted) return; + if (jsonObject is JsonRpcRequest && + jsonObject.method == MethodConstants.WC_SESSION_PING) { + showPlatformToast( + duration: const Duration(seconds: 1), + child: Container( + padding: const EdgeInsets.all(StyleConstants.linear8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + StyleConstants.linear16, + ), + ), + child: Text(jsonObject.method, maxLines: 1), + ), + context: context, + ); + } + } + } + + @override + Widget build(BuildContext context) { + _pairings = _walletKit.pairings.getAll(); + _pairings = _pairings.where((p) => p.active).toList(); + return Stack( + children: [ + _pairings.isEmpty ? _buildNoPairingMessage() : _buildPairingList(), + Positioned( + bottom: StyleConstants.magic20, + right: StyleConstants.magic20, + left: StyleConstants.magic20, + child: Row( + children: [ + const SizedBox(width: StyleConstants.magic20), + _buildIconButton(Icons.copy, _onCopyQrCode), + const SizedBox(width: StyleConstants.magic20), + _buildIconButton(Icons.qr_code_rounded, _onScanQrCode), + ], + ), + ), + ValueListenableBuilder( + valueListenable: DeepLinkHandler.waiting, + builder: (context, value, _) { + return Visibility( + visible: value, + child: Center( + child: Container( + decoration: const BoxDecoration( + color: Colors.black38, + borderRadius: BorderRadius.all(Radius.circular(50.0)), + ), + padding: const EdgeInsets.all(12.0), + child: const CircularProgressIndicator( + color: Colors.white, + ), + ), + ), + ); + }, + ), + ], + ); + } + + Widget _buildNoPairingMessage() { + return const Center( + child: Text( + StringConstants.noApps, + textAlign: TextAlign.center, + style: StyleConstants.bodyText, + ), + ); + } + + Widget _buildPairingList() { + final pairingItems = _pairings + .map( + (PairingInfo pairing) => PairingItem( + key: ValueKey(pairing.topic), + pairing: pairing, + onTap: () => _onListItemTap(pairing), + ), + ) + .toList(); + + return ListView.builder( + itemCount: pairingItems.length, + itemBuilder: (BuildContext context, int index) { + return pairingItems[index]; + }, + ); + } + + Widget _buildIconButton(IconData icon, void Function()? onPressed) { + return Container( + decoration: BoxDecoration( + color: StyleConstants.primaryColor, + borderRadius: BorderRadius.circular( + StyleConstants.linear48, + ), + ), + child: IconButton( + icon: Icon( + icon, + color: StyleConstants.titleTextColor, + ), + iconSize: StyleConstants.linear24, + onPressed: onPressed, + ), + ); + } + + Future _onCopyQrCode() async { + final uri = await GetIt.I().queueBottomSheet( + widget: UriInputPopup(), + ); + if (uri is String) { + _onFoundUri(uri); + } + } + + Future _onScanQrCode() async { + try { + QrBarCodeScannerDialog().getScannedQrBarCode( + context: context, + onCode: (value) { + if (!mounted) return; + _onFoundUri(value); + }, + ); + } catch (e) { + debugPrint(e.toString()); + } + } + + Future _onFoundUri(String? uri) async { + if ((uri ?? '').isEmpty) return; + try { + DeepLinkHandler.waiting.value = true; + await _walletKit.pair(uri: Uri.parse(uri!)); + } on ReownSignError catch (e) { + _showErrorDialog('${e.code}: ${e.message}\n$uri'); + } on TimeoutException catch (_) { + _showErrorDialog('Time out error. Check your connection.'); + } + } + + void _showErrorDialog(String message) { + DeepLinkHandler.waiting.value = false; + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text( + 'Error', + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + content: Text( + message, + style: const TextStyle( + color: Colors.black, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text( + 'Close', + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ) + ], + ); + }); + } + + void _onListItemTap(PairingInfo pairing) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AppDetailPage( + pairing: pairing, + ), + ), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/pages/settings_page.dart b/packages/reown_walletkit/example/lib/pages/settings_page.dart new file mode 100644 index 0000000..77444d1 --- /dev/null +++ b/packages/reown_walletkit/example/lib/pages/settings_page.dart @@ -0,0 +1,802 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:fl_toast/fl_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get_it/get_it.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/widgets/custom_button.dart'; +import 'package:reown_walletkit_wallet/widgets/recover_from_seed.dart'; + +class SettingsPage extends StatefulWidget { + const SettingsPage({super.key}); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + @override + Widget build(BuildContext context) { + final keysService = GetIt.I(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _EVMAccounts( + onCreateAddress: () async { + await keysService.createAddressFromSeed(); + await keysService.loadKeys(); + Navigator.of(context).pop(); + setState(() {}); + }, + onAccountChanged: (address) async { + final walletKit = GetIt.I().walletKit; + final sessions = walletKit.sessions.getAll(); + for (var session in sessions) { + await walletKit.emitSessionEvent( + topic: session.topic, + chainId: 'eip155:1', + event: SessionEventParams( + name: 'accountsChanged', + data: [address], + ), + ); + } + setState(() {}); + }, + ), + // + const SizedBox(height: 20.0), + const Divider(height: 1.0), + _SolanaAccounts(), + const SizedBox(height: 20.0), + const Divider(height: 1.0), + _PolkadotAccounts(), + const SizedBox(height: 20.0), + const Divider(height: 1.0), + _KadenaAccounts(), + const SizedBox(height: 20.0), + const Divider(height: 1.0), + _DeviceData(), + const SizedBox(height: 20.0), + const Divider(height: 1.0), + _Metadata(), + const SizedBox(height: 20.0), + const Divider(height: 1.0), + _Buttons( + onRestoreFromSeed: () async { + final mnemonic = + await GetIt.I().queueBottomSheet( + widget: RecoverFromSeed(), + ); + if (mnemonic is String) { + await keysService.restoreWalletFromSeed( + mnemonic: mnemonic, + ); + await keysService.loadKeys(); + await showDialog( + context: context, + builder: (BuildContext context) { + return const AlertDialog( + content: Text('Wallet from seed restored'), + ); + }, + ); + setState(() {}); + } + }, + onRestoreDefault: () async { + await keysService.clearAll(); + await keysService.loadDefaultWallet(); + await keysService.loadKeys(); + await showDialog( + context: context, + builder: (BuildContext context) { + return const AlertDialog( + content: Text('Default wallet restored'), + ); + }, + ); + setState(() {}); + }, + ), + // + ], + ), + ), + ), + ], + ); + } +} + +class _Metadata extends StatelessWidget { + @override + Widget build(BuildContext context) { + final walletKit = GetIt.I().walletKit; + final nativeLink = walletKit.metadata.redirect?.native; + final universalLink = walletKit.metadata.redirect?.universal; + final linkMode = walletKit.metadata.redirect?.linkMode; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox.square(dimension: 20.0), + _DataContainer( + title: 'Redirect', + data: + 'Native: $nativeLink\nUniversal: $universalLink\nLink Mode: $linkMode', + ), + ], + ), + ); + } +} + +class _EVMAccounts extends StatefulWidget { + final VoidCallback onCreateAddress; + final Function(String) onAccountChanged; + const _EVMAccounts({ + required this.onCreateAddress, + required this.onAccountChanged, + }); + + @override + State<_EVMAccounts> createState() => _EVMAccountsState(); +} + +class _EVMAccountsState extends State<_EVMAccounts> { + int _currentPage = 0; + late final PageController _pageController; + + @override + void initState() { + super.initState(); + _pageController = PageController(); + } + + @override + Widget build(BuildContext context) { + final keysService = GetIt.I(); + final chainKeys = keysService.getKeysForChain('eip155'); + debugPrint('[$runtimeType] chainKeys ${chainKeys.length}'); + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + children: [ + const SizedBox.square(dimension: 8.0), + Expanded( + child: Text( + 'EVM Accounts (${_currentPage + 1}/${chainKeys.length})', + style: const TextStyle( + color: Colors.black, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + IconButton( + onPressed: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Create new account'), + content: const Text( + 'This will create a new address out from the same seed phrase', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: widget.onCreateAddress, + child: const Text('Proceed'), + ), + ], + ); + }, + ); + }, + icon: const Icon(Icons.add_box_rounded), + padding: const EdgeInsets.all(0.0), + visualDensity: VisualDensity.compact, + ), + IconButton( + onPressed: (_currentPage == 0) + ? null + : () { + _pageController.jumpToPage(_currentPage - 1); + }, + icon: const Icon(Icons.arrow_back), + padding: const EdgeInsets.all(0.0), + visualDensity: VisualDensity.compact, + ), + IconButton( + onPressed: (_currentPage == chainKeys.length - 1) + ? null + : () { + _pageController.jumpToPage(_currentPage + 1); + }, + icon: const Icon(Icons.arrow_forward), + padding: const EdgeInsets.all(0.0), + visualDensity: VisualDensity.compact, + ), + ], + ), + ), + SizedBox( + height: 300.0, + child: PageView.builder( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + onPageChanged: (value) async { + setState(() => _currentPage = value); + final chainKey = chainKeys[_currentPage]; + widget.onAccountChanged(chainKey.address); + }, + itemBuilder: (BuildContext context, int index) { + final chainKey = chainKeys[index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + children: [ + const SizedBox(height: 12.0), + _DataContainer( + title: 'CAIP-10', + data: 'eip155:1:${chainKey.address}', + height: 84.0, + ), + const SizedBox(height: 12.0), + _DataContainer( + title: 'Public key', + data: chainKey.publicKey, + height: 84.0, + ), + const SizedBox(height: 12.0), + _DataContainer( + title: 'Private key', + data: chainKey.privateKey, + blurred: true, + height: 84.0, + ), + const SizedBox(height: 12.0), + ], + ), + ); + }, + itemCount: chainKeys.length, + ), + ), + SizedBox( + height: 16.0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + // ignore: sdk_version_since + children: chainKeys.indexed + .map( + (e) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 2.0), + child: CircleAvatar( + radius: e.$1 == _currentPage ? 4.0 : 3.0, + backgroundColor: + e.$1 == _currentPage ? Colors.black : Colors.black38, + ), + ), + ) + .toList(), + ), + ), + const SizedBox(height: 20.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: FutureBuilder( + future: keysService.getMnemonic(), + builder: (context, snapshot) { + return _DataContainer( + title: 'Seed phrase', + data: snapshot.data ?? '', + blurred: true, + ); + }, + ), + ), + ], + ); + } +} + +class _SolanaAccounts extends StatelessWidget { + @override + Widget build(BuildContext context) { + final keysService = GetIt.I(); + final chainKeys = keysService.getKeysForChain('solana'); + if (chainKeys.isEmpty) return const SizedBox.shrink(); + return Column( + children: [ + const Padding( + padding: EdgeInsets.all(12.0), + child: Row( + children: [ + SizedBox.square(dimension: 8.0), + Expanded( + child: Text( + 'Solana Account', + style: TextStyle( + color: Colors.black, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + children: [ + _DataContainer( + title: 'Address', + data: chainKeys.first.address, + ), + const SizedBox(height: 12.0), + _DataContainer( + title: 'Secret key', + data: chainKeys.first.privateKey, + blurred: true, + ), + ], + ), + ), + ], + ); + } +} + +class _PolkadotAccounts extends StatelessWidget { + @override + Widget build(BuildContext context) { + final keysService = GetIt.I(); + final chainKeys = keysService.getKeysForChain('polkadot'); + if (chainKeys.isEmpty) return const SizedBox.shrink(); + return Column( + children: [ + const Padding( + padding: EdgeInsets.all(12.0), + child: Row( + children: [ + SizedBox.square(dimension: 8.0), + Expanded( + child: Text( + 'Polkadot Account', + style: TextStyle( + color: Colors.black, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + children: [ + _DataContainer( + title: 'Address', + data: chainKeys.first.address, + ), + const SizedBox(height: 12.0), + _DataContainer( + title: 'Mnemonic', + data: chainKeys.first.privateKey, + blurred: true, + ), + ], + ), + ), + ], + ); + } +} + +class _KadenaAccounts extends StatelessWidget { + @override + Widget build(BuildContext context) { + final keysService = GetIt.I(); + final chainKeys = keysService.getKeysForChain('kadena'); + if (chainKeys.isEmpty) return const SizedBox.shrink(); + return Column( + children: [ + const Padding( + padding: EdgeInsets.all(12.0), + child: Row( + children: [ + SizedBox.square(dimension: 8.0), + Expanded( + child: Text( + 'Kadena Account', + style: TextStyle( + color: Colors.black, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + children: [ + _DataContainer( + title: 'Address', + data: chainKeys.first.address, + ), + const SizedBox(height: 12.0), + _DataContainer( + title: 'Secret key', + data: chainKeys.first.privateKey, + blurred: true, + ), + ], + ), + ) + ], + ); + } +} + +class _DeviceData extends StatelessWidget { + @override + Widget build(BuildContext context) { + final walletKit = GetIt.I().walletKit; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(left: 8.0, bottom: 8.0, top: 12.0), + child: Text( + 'Device', + style: TextStyle( + color: Colors.black, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + FutureBuilder( + future: walletKit.core.crypto.getClientId(), + builder: (context, snapshot) { + return _DataContainer( + title: 'Client ID', + data: snapshot.data ?? '', + ); + }, + ), + const SizedBox(height: 12.0), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox.shrink(); + } + final v = snapshot.data!.version; + final b = snapshot.data!.buildNumber; + const f = String.fromEnvironment('FLUTTER_APP_FLAVOR'); + return _DataContainer( + title: 'App version', + data: '$v-$f ($b) - SDK v$packageVersion', + ); + }, + ), + ], + ), + ); + } +} + +class _Buttons extends StatelessWidget { + final VoidCallback onRestoreFromSeed; + final VoidCallback onRestoreDefault; + const _Buttons({ + required this.onRestoreFromSeed, + required this.onRestoreDefault, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + children: [ + const SizedBox(height: 8.0), + Row( + children: [ + CustomButton( + type: CustomButtonType.normal, + onTap: onRestoreFromSeed, + child: const Center( + child: Text( + 'Restore wallet from seed', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + const SizedBox(height: 12.0), + Row( + children: [ + CustomButton( + type: CustomButtonType.invalid, + onTap: onRestoreDefault, + child: const Center( + child: Text( + 'Restore default wallet', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} + +class _DataContainer extends StatefulWidget { + const _DataContainer({ + required this.title, + required this.data, + this.blurred = false, + this.height, + }); + final String title; + final String data; + final bool blurred; + final double? height; + + @override + State<_DataContainer> createState() => __DataContainerState(); +} + +class __DataContainerState extends State<_DataContainer> { + late bool blurred; + + @override + void initState() { + super.initState(); + blurred = widget.blurred; + } + + @override + Widget build(BuildContext context) { + final blurValue = blurred ? 5.0 : 0.0; + return GestureDetector( + onTap: () => Clipboard.setData(ClipboardData(text: widget.data)).then( + (_) => showPlatformToast( + child: Text('${widget.title} copied'), + context: context, + ), + ), + onLongPress: () => setState(() { + blurred = false; + }), + onLongPressUp: () => setState(() { + blurred = widget.blurred; + }), + child: Container( + height: widget.height, + decoration: BoxDecoration( + color: StyleConstants.lightGray, + borderRadius: BorderRadius.circular( + StyleConstants.linear16, + ), + ), + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + widget.title, + style: const TextStyle( + color: Colors.black, + fontSize: 14.0, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8.0), + const Icon(Icons.copy, size: 14.0), + ], + ), + ImageFiltered( + imageFilter: ImageFilter.blur( + sigmaX: blurValue, + sigmaY: blurValue, + tileMode: TileMode.decal, + ), + child: Text( + widget.data, + style: const TextStyle( + color: Colors.black87, + fontSize: 13.0, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ), + ); + } +} + +class SizeReportingWidget extends StatefulWidget { + final Widget child; + final ValueChanged onSizeChange; + + const SizeReportingWidget({ + Key? key, + required this.child, + required this.onSizeChange, + }) : super(key: key); + + @override + State createState() => _SizeReportingWidgetState(); +} + +class _SizeReportingWidgetState extends State { + Size? _oldSize; + + @override + Widget build(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) => _notifySize()); + return widget.child; + } + + void _notifySize() { + if (!mounted) { + return; + } + final size = context.size; + if (_oldSize != size && size != null) { + _oldSize = size; + widget.onSizeChange(size); + } + } +} + +class ExpandablePageView extends StatefulWidget { + final List children; + final PageController? controller; + final Function(int)? onPageChanged; + + const ExpandablePageView({ + Key? key, + required this.children, + this.controller, + this.onPageChanged, + }) : super(key: key); + + @override + State createState() => _ExpandablePageViewState(); +} + +class _ExpandablePageViewState extends State + with TickerProviderStateMixin { + late PageController _pageController; + late List _heights; + int _currentPage = 0; + + double get _currentHeight => _heights[_currentPage]; + + @override + void initState() { + super.initState(); + _heights = widget.children.map((e) => 0.0).toList(); + _pageController = widget.controller ?? PageController() + ..addListener(() { + final newPage = _pageController.page?.round() ?? 0; + if (_currentPage != newPage) { + setState(() => _currentPage = newPage); + } + }); + } + + @override + void didUpdateWidget(covariant ExpandablePageView oldWidget) { + super.didUpdateWidget(oldWidget); + final diff = widget.children.length - oldWidget.children.length; + if (diff > 0) { + for (var i = 0; i < diff; i++) { + final lastHeight = _heights.last; + _heights.add(lastHeight); + } + } + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint('${_heights[0]} $_currentHeight'); + return TweenAnimationBuilder( + curve: Curves.easeInOutCubic, + duration: const Duration(milliseconds: 50), + tween: Tween( + begin: max(_heights[0], 200.0), + end: max(_currentHeight, 200.0), + ), + builder: (context, value, child) => SizedBox( + height: value, + child: child, + ), + child: PageView( + physics: const NeverScrollableScrollPhysics(), + controller: _pageController, + onPageChanged: widget.onPageChanged, + children: _sizeReportingChildren + .asMap() // + .map((index, child) => MapEntry(index, child)) + .values + .toList(), + ), + ); + } + + List get _sizeReportingChildren => widget.children + .asMap() // + .map( + (index, child) => MapEntry( + index, + OverflowBox( + //needed, so that parent won't impose its constraints on the children, thus skewing the measurement results. + minHeight: 0, + maxHeight: double.infinity, + alignment: Alignment.topCenter, + child: SizeReportingWidget( + onSizeChange: (size) => + setState(() => _heights[index] = size.height), + child: Align(child: child), + ), + ), + ), + ) + .values + .toList(); +} diff --git a/packages/reown_walletkit/example/lib/utils/constants.dart b/packages/reown_walletkit/example/lib/utils/constants.dart new file mode 100644 index 0000000..9585905 --- /dev/null +++ b/packages/reown_walletkit/example/lib/utils/constants.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; + +class Constants { + static const smallScreen = 640; + + static const String domain = 'reown.com'; + static const String aud = 'https://$domain/login'; +} + +class StyleConstants { + static const Color backgroundColor = Colors.black; + static const Color primaryColor = Color(0xFF3396FF); + + static const Color darkGray = Color(0xFF141414); + static const Color lightGray = Color.fromARGB(255, 227, 227, 227); + + static const Color clear = Color.fromARGB(0, 0, 0, 0); + static const Color layerColor0 = Color(0xFF000000); + static const Color layerColor1 = Color.fromARGB(255, 18, 18, 19); + static const Color layerColor1NoAlpha = Color(0xFF141415); + static const Color layerColor2 = Color.fromARGB(255, 65, 65, 71); + static const Color layerBubbleColor2 = Color(0xFF798686); + static const Color layerTextColor2 = Color(0xFF141414); + static const Color layerColor3 = Color.fromARGB(255, 39, 42, 42); + static const Color layerTextColor3 = Color(0xFF9EA9A9); + static const Color layerColor4 = Color(0xFF153B47); + static const Color layerTextColor4 = Color(0xFF1AC6FF); + + static const Color titleTextColor = Color(0xFFFFFFFF); + + static const Color successColor = Color(0xFF2BEE6C); + static const Color errorColor = Color(0xFFF25A67); + + // Linear + static const double linear8 = 8; + static const double linear16 = 16; + static const double linear24 = 24; + static const double linear32 = 32; + static const double linear48 = 48; + static const double linear56 = 56; + static const double linear72 = 72; + static const double linear80 = 80; + + // Magic Number + static const double magic10 = 10; + static const double magic14 = 14; + static const double magic20 = 20; + static const double magic40 = 40; + static const double magic64 = 64; + + // Width + static const double maxWidth = 400; + + // Text styles + static const TextStyle titleText = TextStyle( + color: Colors.grey, + fontSize: magic40, + fontWeight: FontWeight.w600, + ); + static const TextStyle subtitleText = TextStyle( + color: Colors.white, + fontSize: linear24, + fontWeight: FontWeight.w600, + ); + static const TextStyle buttonText = TextStyle( + color: Colors.white, + fontSize: linear16, + fontWeight: FontWeight.w600, + ); + static const TextStyle bodyTextBold = TextStyle( + color: Colors.grey, + fontSize: magic14, + fontWeight: FontWeight.w900, + ); + static const TextStyle bodyText = TextStyle( + color: Colors.grey, + fontSize: magic14, + fontWeight: FontWeight.w400, + ); + static const TextStyle bodyLightGray = TextStyle( + color: lightGray, + fontSize: magic14, + ); + static const TextStyle layerTextStyle2 = TextStyle( + color: layerTextColor2, + fontSize: magic14, + fontWeight: FontWeight.w600, + ); + static const TextStyle layerTextStyle3 = TextStyle( + color: Colors.black, + fontSize: magic14, + fontWeight: FontWeight.w600, + ); + static const TextStyle layerTextStyle4 = TextStyle( + color: layerTextColor4, + fontSize: magic14, + fontWeight: FontWeight.w600, + ); + + // Bubbles + static const EdgeInsets bubblePadding = EdgeInsets.symmetric( + vertical: linear8, + horizontal: linear8, + ); +} diff --git a/packages/reown_walletkit/example/lib/utils/dart_defines.dart b/packages/reown_walletkit/example/lib/utils/dart_defines.dart new file mode 100644 index 0000000..aa08209 --- /dev/null +++ b/packages/reown_walletkit/example/lib/utils/dart_defines.dart @@ -0,0 +1,35 @@ +class DartDefines { + static const projectId = String.fromEnvironment('PROJECT_ID'); + // HARDCODED TEST KEYS + // KADENA + static const kadenaSecretKey = String.fromEnvironment( + 'KADENA_SECRET_KEY', + defaultValue: + '6576379e67666438c8a8e637b101d343153fed0f96c1cfa07aa45e4ccb8e5a4f', + ); + static const kadenaAddress = String.fromEnvironment( + 'KADENA_ADDRESS', + defaultValue: + '3a527a1af7713cde04a4ce8b6c95b3806b7582f2423d740fc16eaa5b7a235d42', + ); + // SOLANA + static const solanaSecretKey = String.fromEnvironment( + 'SOLANA_SECRET_KEY', + defaultValue: + '131,20,187,127,47,75,38,64,75,207,232,11,76,181,166,166,222,165,180,209,240,41,18,94,141,56,106,154,178,179,142,70,202,6,166,179,56,40,83,93,128,50,92,94,242,45,153,178,30,4,157,189,157,159,152,246,221,138,106,213,155,193,247,233', + ); + static const solanaAddress = String.fromEnvironment( + 'SOLANA_ADDRESS', + defaultValue: 'EbdEmCpKGvEwfwV4ACmVYHFRkwvXdogJhMZeEekDFVVJ', + ); + // POLKADOT + static const polkadotMnemonic = String.fromEnvironment( + 'POLKADOT_MNEMONIC', + defaultValue: + 'shove trumpet draw priority either tonight million worry dust vivid twelve solid', + ); + static const polkadotAddress = String.fromEnvironment( + 'POLKADOT_ADDRESS', + defaultValue: '7UUBfttjb1P1m4KSCxC5UTS8xui6EszQsaJNQ6qztLLnNTz', + ); +} diff --git a/packages/reown_walletkit/example/lib/utils/eth_utils.dart b/packages/reown_walletkit/example/lib/utils/eth_utils.dart new file mode 100644 index 0000000..17a524e --- /dev/null +++ b/packages/reown_walletkit/example/lib/utils/eth_utils.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; +import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; + +class EthUtils { + static final addressRegEx = RegExp( + r'^0x[a-fA-F0-9]{40}$', + caseSensitive: false, + ); + + static String getUtf8Message(String maybeHex) { + if (maybeHex.startsWith('0x')) { + final List decoded = hex.decode( + maybeHex.substring(2), + ); + return utf8.decode(decoded); + } + + return maybeHex; + } + + static String? getAddressFromSessionRequest(SessionRequest request) { + try { + final paramsList = List.from((request.params as List)); + if (request.method == 'personal_sign') { + // for `personal_sign` first value in params has to be always the message + paramsList.removeAt(0); + } + + return paramsList.firstWhere((p) { + try { + EthereumAddress.fromHex(p); + return true; + } catch (e) { + return false; + } + }); + } catch (e) { + debugPrint(e.toString()); + return null; + } + } + + static dynamic getDataFromSessionRequest(SessionRequest request) { + try { + final paramsList = List.from((request.params as List)); + if (request.method == 'personal_sign') { + return paramsList.first; + } + return paramsList.firstWhere((p) { + final address = getAddressFromSessionRequest(request); + return p != address; + }); + } catch (e) { + debugPrint(e.toString()); + return null; + } + } + + static Map? getTransactionFromSessionRequest( + SessionRequest request, + ) { + try { + final param = (request.params as List).first; + return param as Map; + } catch (e) { + debugPrint(e.toString()); + return null; + } + } + + static Future decodeMessageEvent(MessageEvent event) async { + final walletKit = GetIt.I().walletKit; + final payloadString = await walletKit.core.crypto.decode( + event.topic, + event.message, + ); + if (payloadString == null) return null; + + final data = jsonDecode(payloadString) as Map; + if (data.containsKey('method')) { + return JsonRpcRequest.fromJson(data); + } else { + return JsonRpcResponse.fromJson(data); + } + } +} diff --git a/packages/reown_walletkit/example/lib/utils/methods_utils.dart b/packages/reown_walletkit/example/lib/utils/methods_utils.dart new file mode 100644 index 0000000..d05fa37 --- /dev/null +++ b/packages/reown_walletkit/example/lib/utils/methods_utils.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/deep_link_handler.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_model.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_widget.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_request_widget.dart/wc_request_widget.dart'; + +class MethodsUtils { + static final walletKit = GetIt.I().walletKit; + + static Future requestApproval( + String text, { + String? title, + String? method, + String? chainId, + String? address, + required String transportType, + List extraModels = const [], + VerifyContext? verifyContext, + }) async { + final bottomSheetService = GetIt.I(); + final WCBottomSheetResult rs = (await bottomSheetService.queueBottomSheet( + widget: WCRequestWidget( + verifyContext: verifyContext, + child: WCConnectionWidget( + title: title ?? 'Approve Request', + info: [ + WCConnectionModel( + title: 'Method: $method\n' + 'Transport Type: ${transportType.toUpperCase()}\n' + 'Chain ID: $chainId\n' + 'Address: $address\n\n' + 'Message:', + elements: [ + text, + ], + ), + ...extraModels, + ], + ), + ), + )) ?? + WCBottomSheetResult.reject; + + return rs != WCBottomSheetResult.reject; + } + + static void handleRedirect( + String topic, + Redirect? redirect, [ + String? error, + ]) { + debugPrint( + '[SampleWallet] handleRedirect topic: $topic, redirect: $redirect, error: $error'); + openApp(topic, redirect, onFail: (e) { + goBackModal( + title: 'Error', + message: error, + success: false, + ); + }); + } + + static void openApp( + String topic, + Redirect? redirect, { + int delay = 100, + Function(ReownSignError? error)? onFail, + }) async { + await Future.delayed(Duration(milliseconds: delay)); + DeepLinkHandler.waiting.value = false; + try { + await walletKit.redirectToDapp( + topic: topic, + redirect: redirect, + ); + } on ReownSignError catch (e) { + onFail?.call(e); + } + } + + static void goBackModal({ + String? title, + String? message, + bool success = true, + }) async { + DeepLinkHandler.waiting.value = false; + await GetIt.I().queueBottomSheet( + closeAfter: success ? 3 : 0, + widget: Container( + color: Colors.white, + height: 210.0, + width: double.infinity, + padding: const EdgeInsets.all(20.0), + child: Column( + children: [ + Icon( + success ? Icons.check_circle_sharp : Icons.error_outline_sharp, + color: success ? Colors.green[100] : Colors.red[100], + size: 80.0, + ), + Text( + title ?? 'Connected', + style: StyleConstants.subtitleText.copyWith( + color: Colors.black, + fontSize: 18.0, + ), + ), + Text(message ?? 'You can go back to your dApp now'), + ], + ), + ), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/utils/namespace_model_builder.dart b/packages/reown_walletkit/example/lib/utils/namespace_model_builder.dart new file mode 100644 index 0000000..8a1e723 --- /dev/null +++ b/packages/reown_walletkit/example/lib/utils/namespace_model_builder.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/utils/string_constants.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_model.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_widget.dart'; + +class ConnectionWidgetBuilder { + static List buildFromRequiredNamespaces( + Map generatedNamespaces, + ) { + final List views = []; + for (final key in generatedNamespaces.keys) { + final namespaces = generatedNamespaces[key]!; + final chains = NamespaceUtils.getChainsFromAccounts(namespaces.accounts); + final List models = []; + // If the chains property is present, add the chain data to the models + models.add( + WCConnectionModel( + title: StringConstants.chains, + elements: chains, + ), + ); + models.add( + WCConnectionModel( + title: StringConstants.methods, + elements: namespaces.methods, + ), + ); + if (namespaces.events.isNotEmpty) { + models.add( + WCConnectionModel( + title: StringConstants.events, + elements: namespaces.events, + ), + ); + } + + views.add( + WCConnectionWidget( + title: key, + info: models, + ), + ); + } + + return views; + } + + static List buildFromNamespaces( + String topic, + Map namespaces, + BuildContext context, + ) { + final List views = []; + for (final key in namespaces.keys) { + final ns = namespaces[key]!; + final List models = []; + // If the chains property is present, add the chain data to the models + models.add( + WCConnectionModel( + title: StringConstants.accounts, + elements: ns.accounts, + ), + ); + models.add( + WCConnectionModel( + title: StringConstants.methods, + elements: ns.methods, + ), + ); + + if (ns.events.isNotEmpty) { + models.add( + WCConnectionModel( + title: StringConstants.events, + elements: ns.events, + ), + ); + } + + views.add( + WCConnectionWidget( + title: key, + info: models, + ), + ); + } + + return views; + } +} diff --git a/packages/reown_walletkit/example/lib/utils/string_constants.dart b/packages/reown_walletkit/example/lib/utils/string_constants.dart new file mode 100644 index 0000000..0d50c1c --- /dev/null +++ b/packages/reown_walletkit/example/lib/utils/string_constants.dart @@ -0,0 +1,66 @@ +class StringConstants { + // General + static const String cancel = 'Cancel'; + static const String close = 'Close'; + static const String ok = 'OK'; + static const String delete = 'Delete'; + static const String welcome = 'Welcome'; + static const String welcomeMessage = + 'We made this Example Wallet App to help developers integrate the Reown\'s WalletKit SDK and provide an amazing experience to their users.'; + static const String getStarted = 'Get Started'; + static const String wouldLikeToConnect = 'would like to connect'; + static const String message = 'Message'; + static const String invalidUri = 'Invalid URI'; + + // Apps Pages + static const String apps = 'Apps'; + static const String noApps = + 'Apps you connect with will appear here.\nTo connect, scan a QR code or paste the code that\'s displayed on the app.'; + + // QR Code Scanning + static const String scanPairing = 'Scan the code'; + + // Uri Input Popup + static const String enterUri = 'Enter a WalletConnect URI'; + static const String enterUriMessage = + 'To get the URI press the "copy to clipboard" button in the wallet connection interfaces.'; + static const String textFieldPlaceholder = 'wc://a13aef...'; + + // Session Proposal + static const String chains = 'Chains'; + static const String accounts = 'Accounts'; + static const String methods = 'Methods'; + static const String events = 'Events'; + + // Request + static const String approve = 'Approve'; + static const String reject = 'Reject'; + + // Main Page + static const String appTitle = 'Reown WalletKit Flutter'; + static const String connectPageTitle = 'Connections'; + static const String pairingsPageTitle = 'Pairings'; + static const String sessionsPageTitle = 'Sessions'; + static const String authPageTitle = 'Auth'; + static const String settingsPageTitle = 'Settings'; + + // Connect Page + static const String selectChains = 'Select chains:'; + static const String testnetsOnly = 'Testnets only?'; + static const String scanQrCode = 'Scan QR Code'; + static const String urlCopiedToClipboard = 'URL copied to clipboard'; + static const String connect = 'Connect'; + static const String connectionEstablished = 'Session established'; + static const String connectionFailed = 'Session setup failed'; + static const String authSucceeded = 'Authentication Successful'; + static const String authFailed = 'Authentication Failed'; + + // Pairings Page + static const String pairings = 'Pairings'; + static const String deletePairing = 'Delete Pairing?'; + + // Sessions Page + static const String sessions = 'Sessions'; + static const String noSessionSelected = 'No session selected'; + static const String sessionTopic = 'Session Topic: '; +} diff --git a/packages/reown_walletkit/example/lib/widgets/custom_button.dart b/packages/reown_walletkit/example/lib/widgets/custom_button.dart new file mode 100644 index 0000000..3d13919 --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/custom_button.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; + +enum CustomButtonType { normal, valid, invalid } + +class CustomButton extends StatelessWidget { + final Widget child; + final CustomButtonType? type; + final VoidCallback onTap; + + const CustomButton({ + super.key, + required this.child, + required this.onTap, + this.type, + }); + + Color _getBackgroundColor(CustomButtonType? type) { + switch (type) { + case CustomButtonType.normal: + return Colors.blue; + case CustomButtonType.valid: + return StyleConstants.successColor; + case CustomButtonType.invalid: + return StyleConstants.errorColor; + default: + return Colors.black54; + } + } + + @override + Widget build(BuildContext context) { + return Expanded( + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(16), + child: Container( + decoration: BoxDecoration( + color: _getBackgroundColor(type), + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 12, + ), + child: child, + ), + ), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/kadena_request_widget/kadena_request_widget.dart b/packages/reown_walletkit/example/lib/widgets/kadena_request_widget/kadena_request_widget.dart new file mode 100644 index 0000000..fc09559 --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/kadena_request_widget/kadena_request_widget.dart @@ -0,0 +1,114 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:kadena_dart_sdk/kadena_dart_sdk.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_model.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_widget.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_request_widget.dart/wc_request_widget.dart'; + +/// A widget that takes a list of PactCommandPayloads, and allows the user +/// to sign each one individually. If there is only one item in the list, +/// it doesn't show the number of transactions to be signed. Otherwise, it +/// shows the number of transactions to be signed at the top. +/// +/// This widget is used by the KadenaService to sign any kind of request. +/// +/// This widget is generally displayed using the [BottomSheetService]. +/// It returns a list of booleans. Each boolean represents whether the +/// user approved the transaction at the same index in the list of +/// PactCommandPayloads. +/// +/// For each PactCommandPayload to be signed, the widget itself +/// displays the code of the PactCommandPayload and the data. +/// It also shows each [Capability] that is included in the payload. +class KadenaRequestWidget extends StatefulWidget { + const KadenaRequestWidget({ + super.key, + required this.payloads, + }); + + final List payloads; + + @override + KadenaRequestWidgetState createState() => KadenaRequestWidgetState(); +} + +class KadenaRequestWidgetState extends State { + int _currentIndex = 0; + final List _responses = []; + + @override + Widget build(BuildContext context) { + final List capsList = []; + + if (widget.payloads[_currentIndex].signers.isNotEmpty && + widget.payloads[_currentIndex].signers.first.clist != null) { + capsList.addAll( + widget.payloads[_currentIndex].signers.first.clist! + .map( + (e) => WCConnectionModel( + title: e.name, + elements: e.args + .map( + (e) => e.toString(), + ) + .toList(), + ), + ) + .toList(), + ); + } + + final List signCounter = []; + if (widget.payloads.length > 1) { + signCounter.add( + Text( + '${_currentIndex + 1} of ${widget.payloads.length}', + style: StyleConstants.subtitleText, + ), + ); + signCounter.add( + const SizedBox( + height: StyleConstants.magic20, + ), + ); + } + + return WCRequestWidget( + onAccept: () { + _responses.add(true); + _incrementIndex(); + }, + onReject: () { + _responses.add(false); + _incrementIndex(); + }, + child: Column( + children: [ + ...signCounter, + WCConnectionWidget( + title: 'Sign Transaction', + info: [ + WCConnectionModel( + title: 'Pact Command', + text: jsonEncode(widget.payloads[_currentIndex]), + ), + ...capsList, + ], + ), + ], + ), + ); + } + + void _incrementIndex() { + if (_currentIndex < widget.payloads.length - 1) { + setState(() { + _currentIndex++; + }); + } else { + Navigator.of(context).pop(_responses); + } + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/pairing_item.dart b/packages/reown_walletkit/example/lib/widgets/pairing_item.dart new file mode 100644 index 0000000..d68576b --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/pairing_item.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; + +class PairingItem extends StatelessWidget { + const PairingItem({ + super.key, + required this.pairing, + required this.onTap, + }); + + final PairingInfo pairing; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + PairingMetadata? metadata = pairing.peerMetadata; + if (metadata == null) { + return ListTile( + title: const Text('Unknown'), + subtitle: const Text('No metadata available'), + onTap: onTap, + ); + } + final sessions = GetIt.I() + .walletKit + .sessions + .getAll() + .where((element) => element.pairingTopic == pairing.topic) + .toList(); + + return ListTile( + leading: CircleAvatar( + backgroundImage: (metadata.icons.isNotEmpty + ? NetworkImage(metadata.icons[0]) + : const AssetImage('assets/images/default_icon.png')) + as ImageProvider, + ), + title: Text( + metadata.name, + style: const TextStyle(color: Colors.black), + ), + subtitle: Text( + sessions.isEmpty + // ? DeepLinkHandler.waiting.value + // ? 'Settling session. Wait...' + // : 'No active sessions' + ? 'No active sessions' + : 'Active sessions: ${sessions.length}', + style: TextStyle( + color: sessions.isEmpty + // ? DeepLinkHandler.waiting.value + // ? Colors.green + // : Colors.black + ? Colors.black + : Colors.blueAccent, + fontSize: 13.0, + fontWeight: sessions.isEmpty + // ? DeepLinkHandler.waiting.value + // ? FontWeight.bold + // : FontWeight.normal + ? FontWeight.normal + : FontWeight.bold, + ), + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 20.0, + color: Colors.black, + ), + onTap: onTap, + ); + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/recover_from_seed.dart b/packages/reown_walletkit/example/lib/widgets/recover_from_seed.dart new file mode 100644 index 0000000..5def219 --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/recover_from_seed.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; + +class RecoverFromSeed extends StatelessWidget { + RecoverFromSeed({ + Key? key, + }) : super(key: key); + + final controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + final unfocusedBorder = OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey.shade300, width: 1.0), + borderRadius: BorderRadius.circular(12.0), + ); + final focusedBorder = unfocusedBorder.copyWith( + borderSide: const BorderSide(color: Colors.blue, width: 1.0), + ); + return Container( + color: Colors.white, + height: 282.0, + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Column( + children: [ + Text( + 'Insert Seed Phrase', + style: StyleConstants.subtitleText.copyWith( + color: Colors.black, + fontSize: 18.0, + ), + ), + const SizedBox(height: StyleConstants.magic10), + SizedBox( + height: 90.0, + // padding: const EdgeInsets.all(3.0), + child: TextFormField( + controller: controller, + maxLines: 4, + textAlignVertical: TextAlignVertical.center, + cursorColor: Colors.blue, + enableSuggestions: false, + autocorrect: false, + cursorHeight: 16.0, + decoration: InputDecoration( + isDense: true, + hintText: 'your seed phrase here', + hintStyle: const TextStyle(color: Colors.grey), + border: unfocusedBorder, + errorBorder: unfocusedBorder, + enabledBorder: unfocusedBorder, + disabledBorder: unfocusedBorder, + focusedBorder: focusedBorder, + filled: true, + fillColor: Colors.grey[200], + contentPadding: const EdgeInsets.all(8.0), + ), + ), + ), + const SizedBox(height: StyleConstants.magic10), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Navigator.of(context).pop(controller.text), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.blue), + foregroundColor: MaterialStateProperty.all(Colors.white), + ), + child: const Text('Recover'), + ), + ), + const SizedBox(height: StyleConstants.magic10), + SizedBox( + width: double.infinity, + child: TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: const Text( + 'Cancel', + style: TextStyle(color: Colors.grey), + ), + ), + ), + ], + ), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/uri_input_popup.dart b/packages/reown_walletkit/example/lib/widgets/uri_input_popup.dart new file mode 100644 index 0000000..f076bf5 --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/uri_input_popup.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/utils/string_constants.dart'; + +class UriInputPopup extends StatelessWidget { + UriInputPopup({ + Key? key, + }) : super(key: key); + + final controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + final unfocusedBorder = OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey.shade300, width: 1.0), + borderRadius: BorderRadius.circular(12.0), + ); + final focusedBorder = unfocusedBorder.copyWith( + borderSide: const BorderSide(color: Colors.blue, width: 1.0), + ); + return Container( + color: Colors.white, + height: 280.0, + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Column( + children: [ + Text( + StringConstants.enterUri, + style: StyleConstants.subtitleText.copyWith( + color: Colors.black, + fontSize: 18.0, + ), + ), + const Text( + StringConstants.enterUriMessage, + style: StyleConstants.bodyText, + textAlign: TextAlign.center, + ), + const SizedBox(height: StyleConstants.magic10), + SizedBox( + height: 46.0, + // padding: const EdgeInsets.all(3.0), + child: TextFormField( + // focusNode: _focusNode, + controller: controller, + // onChanged: (value) { + // // _debouncer.run(() => widget.onTextChanged(value)); + // }, + textAlignVertical: TextAlignVertical.center, + cursorColor: Colors.blue, + enableSuggestions: false, + autocorrect: false, + cursorHeight: 16.0, + decoration: InputDecoration( + isDense: true, + hintText: 'wc://as87d6...', + hintStyle: const TextStyle(color: Colors.grey), + border: unfocusedBorder, + errorBorder: unfocusedBorder, + enabledBorder: unfocusedBorder, + disabledBorder: unfocusedBorder, + focusedBorder: focusedBorder, + filled: true, + fillColor: Colors.grey[200], + contentPadding: const EdgeInsets.all(8.0), + ), + ), + ), + const SizedBox(height: StyleConstants.magic10), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Navigator.of(context).pop(controller.text), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.blue), + foregroundColor: MaterialStateProperty.all(Colors.white), + ), + child: const Text('Connect'), + ), + ), + const SizedBox(height: StyleConstants.magic10), + SizedBox( + width: double.infinity, + child: TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: const Text( + 'Cancel', + style: TextStyle(color: Colors.grey), + ), + ), + ), + ], + ), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/wc_connection_request/wc_connection_request_widget.dart b/packages/reown_walletkit/example/lib/widgets/wc_connection_request/wc_connection_request_widget.dart new file mode 100644 index 0000000..cf80ffe --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/wc_connection_request/wc_connection_request_widget.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/i_walletkit_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/key_service/i_key_service.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/utils/namespace_model_builder.dart'; +import 'package:reown_walletkit_wallet/utils/string_constants.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_widget.dart'; + +import '../wc_connection_widget/wc_connection_model.dart'; + +class WCConnectionRequestWidget extends StatelessWidget { + const WCConnectionRequestWidget({ + Key? key, + // this.authPayloadParams, + this.sessionAuthPayload, + this.proposalData, + this.requester, + this.verifyContext, + }) : super(key: key); + + // final AuthPayloadParams? authPayloadParams; + final SessionAuthPayload? sessionAuthPayload; + final ProposalData? proposalData; + final ConnectionMetadata? requester; + final VerifyContext? verifyContext; + + @override + Widget build(BuildContext context) { + if (requester == null) { + return const Text('ERROR'); + } + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(StyleConstants.linear8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: StyleConstants.linear8), + Text( + '${requester!.metadata.name} ${StringConstants.wouldLikeToConnect}', + style: StyleConstants.subtitleText.copyWith( + fontSize: 18, + color: Colors.black, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: StyleConstants.linear8), + (sessionAuthPayload != null) + ? _buildSessionAuthRequestView() + : _buildSessionProposalView(context), + ], + ), + ); + } + + Widget _buildSessionAuthRequestView() { + final walletKit = GetIt.I().walletKit; + // + final cacaoPayload = CacaoRequestPayload.fromSessionAuthPayload( + sessionAuthPayload!, + ); + // + final List messagesModels = []; + for (var chain in sessionAuthPayload!.chains) { + final chainKeys = GetIt.I().getKeysForChain(chain); + final iss = 'did:pkh:$chain:${chainKeys.first.address}'; + final message = walletKit.formatAuthMessage( + iss: iss, + cacaoPayload: cacaoPayload, + ); + messagesModels.add( + WCConnectionModel( + title: 'Message ${messagesModels.length + 1}', + elements: [ + message, + ], + ), + ); + } + // + return WCConnectionWidget( + title: '${messagesModels.length} Messages', + info: messagesModels, + ); + } + + Widget _buildSessionProposalView(BuildContext context) { + // Create the connection models using the required and optional namespaces provided by the proposal data + // The key is the title and the list of values is the data + final views = ConnectionWidgetBuilder.buildFromRequiredNamespaces( + proposalData!.generatedNamespaces!, + ); + + return Column( + children: views, + ); + } +} + +class VerifyContextWidget extends StatelessWidget { + const VerifyContextWidget({ + super.key, + required this.verifyContext, + }); + final VerifyContext? verifyContext; + + @override + Widget build(BuildContext context) { + if (verifyContext == null) { + return const SizedBox.shrink(); + } + + if (verifyContext!.validation.scam) { + return VerifyBanner( + color: StyleConstants.errorColor, + origin: verifyContext!.origin, + title: 'Security risk', + text: 'This domain is flagged as unsafe by multiple security providers.' + ' Leave immediately to protect your assets.', + ); + } + if (verifyContext!.validation.invalid) { + return VerifyBanner( + color: StyleConstants.errorColor, + origin: verifyContext!.origin, + title: 'Domain mismatch', + text: + 'This website has a domain that does not match the sender of this request.' + ' Approving may lead to loss of funds.', + ); + } + if (verifyContext!.validation.valid) { + return VerifyHeader( + iconColor: StyleConstants.successColor, + title: verifyContext!.origin, + ); + } + return VerifyBanner( + color: Colors.orange, + origin: verifyContext!.origin, + title: 'Cannot verify', + text: 'This domain cannot be verified. ' + 'Check the request carefully before approving.', + ); + } +} + +class VerifyHeader extends StatelessWidget { + const VerifyHeader({ + super.key, + required this.iconColor, + required this.title, + }); + final Color iconColor; + final String title; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.shield_outlined, + color: iconColor, + ), + const SizedBox(width: StyleConstants.linear8), + Text( + title, + style: TextStyle( + color: iconColor, + fontWeight: FontWeight.bold, + ), + ), + ], + ); + } +} + +class VerifyBanner extends StatelessWidget { + const VerifyBanner({ + super.key, + required this.origin, + required this.title, + required this.text, + required this.color, + }); + final String origin, title, text; + final Color color; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + origin, + style: const TextStyle( + color: Colors.black54, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox.square(dimension: 8.0), + Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: color.withOpacity(0.2), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + ), + child: Column( + children: [ + VerifyHeader( + iconColor: color, + title: title, + ), + const SizedBox(height: 4.0), + Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + color: color, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_model.dart b/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_model.dart new file mode 100644 index 0000000..f4cbb57 --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_model.dart @@ -0,0 +1,18 @@ +class WCConnectionModel { + final String? title; + final String? text; + final List? elements; + final Map? elementActions; + + WCConnectionModel({ + this.title, + this.text, + this.elements, + this.elementActions, + }); + + @override + String toString() { + return 'WCConnectionModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)'; + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_widget.dart b/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_widget.dart new file mode 100644 index 0000000..b089ee8 --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_widget.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_widget_info.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_model.dart'; + +class WCConnectionWidget extends StatelessWidget { + const WCConnectionWidget({ + super.key, + required this.title, + required this.info, + }); + + final String title; + final List info; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: StyleConstants.lightGray, + borderRadius: BorderRadius.circular( + StyleConstants.linear16, + ), + ), + padding: const EdgeInsets.all( + StyleConstants.linear8, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTitle(title), + const SizedBox(height: StyleConstants.linear8), + ...info.map( + (e) => WCConnectionWidgetInfo( + model: e, + ), + ), + ], + ), + ); + } + + Widget _buildTitle(String text) { + return Container( + decoration: BoxDecoration( + color: Colors.black12, + borderRadius: BorderRadius.circular( + StyleConstants.linear16, + ), + ), + padding: StyleConstants.bubblePadding, + child: Text( + text, + style: StyleConstants.layerTextStyle2, + ), + ); + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_widget_info.dart b/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_widget_info.dart new file mode 100644 index 0000000..5f19bc2 --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/wc_connection_widget/wc_connection_widget_info.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_widget/wc_connection_model.dart'; + +class WCConnectionWidgetInfo extends StatelessWidget { + const WCConnectionWidgetInfo({ + Key? key, + required this.model, + }) : super(key: key); + + final WCConnectionModel model; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: Colors.black12, + borderRadius: BorderRadius.circular( + StyleConstants.linear16, + ), + ), + padding: const EdgeInsets.all( + StyleConstants.linear8, + ), + margin: const EdgeInsetsDirectional.only( + top: StyleConstants.linear8, + ), + child: model.elements != null ? _buildList() : _buildText(), + ); + } + + Widget _buildList() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (model.title != null) + Text( + model.title!, + style: StyleConstants.layerTextStyle3, + ), + if (model.title != null) const SizedBox(height: StyleConstants.linear8), + Wrap( + spacing: 4, + runSpacing: 4, + direction: Axis.horizontal, + children: model.elements!.map((e) => _buildElement(e)).toList(), + ), + ], + ); + } + + Widget _buildElement(String text) { + return ElevatedButton( + onPressed: + model.elementActions != null ? model.elementActions![text] : null, + style: ButtonStyle( + elevation: model.elementActions != null + ? MaterialStateProperty.all(4.0) + : MaterialStateProperty.all(0.0), + padding: MaterialStateProperty.all(const EdgeInsets.all(0.0)), + visualDensity: VisualDensity.compact, + backgroundColor: MaterialStateProperty.all( + StyleConstants.layerColor4, + ), + overlayColor: MaterialStateProperty.all(Colors.white), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.circular(StyleConstants.linear16), + ); + }, + ), + ), + child: Container( + padding: const EdgeInsets.all( + StyleConstants.linear8, + ), + child: Text( + text, + style: StyleConstants.layerTextStyle4, + ), + ), + ); + } + + Widget _buildText() { + return Text( + model.text!, + style: StyleConstants.layerTextStyle3, + ); + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/wc_request_widget.dart/wc_request_widget.dart b/packages/reown_walletkit/example/lib/widgets/wc_request_widget.dart/wc_request_widget.dart new file mode 100644 index 0000000..9426ad9 --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/wc_request_widget.dart/wc_request_widget.dart @@ -0,0 +1,70 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/utils/string_constants.dart'; +import 'package:reown_walletkit_wallet/widgets/custom_button.dart'; +import 'package:reown_walletkit_wallet/widgets/wc_connection_request/wc_connection_request_widget.dart'; + +class WCRequestWidget extends StatelessWidget { + const WCRequestWidget({ + super.key, + required this.child, + this.verifyContext, + this.onAccept, + this.onReject, + }); + + final Widget child; + final VerifyContext? verifyContext; + final VoidCallback? onAccept; + final VoidCallback? onReject; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + VerifyContextWidget( + verifyContext: verifyContext, + ), + const SizedBox(height: StyleConstants.linear8), + Flexible( + child: SingleChildScrollView( + child: child, + ), + ), + const SizedBox(height: StyleConstants.linear16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CustomButton( + onTap: onReject ?? + () => Navigator.of(context).pop(WCBottomSheetResult.reject), + type: CustomButtonType.invalid, + child: const Text( + StringConstants.reject, + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + const SizedBox( + width: StyleConstants.linear16, + ), + CustomButton( + onTap: onAccept ?? + () => Navigator.of(context).pop(WCBottomSheetResult.one), + type: CustomButtonType.valid, + child: const Text( + StringConstants.approve, + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + ); + } +} diff --git a/packages/reown_walletkit/example/lib/widgets/wc_request_widget.dart/wc_session_auth_request_widget.dart b/packages/reown_walletkit/example/lib/widgets/wc_request_widget.dart/wc_session_auth_request_widget.dart new file mode 100644 index 0000000..2ab94ac --- /dev/null +++ b/packages/reown_walletkit/example/lib/widgets/wc_request_widget.dart/wc_session_auth_request_widget.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:reown_walletkit_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:reown_walletkit_wallet/utils/constants.dart'; +import 'package:reown_walletkit_wallet/widgets/custom_button.dart'; + +class WCSessionAuthRequestWidget extends StatelessWidget { + const WCSessionAuthRequestWidget({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: SingleChildScrollView( + child: child, + ), + ), + const SizedBox(height: StyleConstants.linear16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CustomButton( + onTap: () => + Navigator.of(context).pop(WCBottomSheetResult.reject), + type: CustomButtonType.invalid, + child: const Text( + 'Cancel', + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + const SizedBox(width: StyleConstants.linear8), + CustomButton( + onTap: () => Navigator.of(context).pop(WCBottomSheetResult.one), + type: CustomButtonType.normal, + child: const Text( + 'Sign One', + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + const SizedBox(width: StyleConstants.linear8), + CustomButton( + onTap: () => Navigator.of(context).pop(WCBottomSheetResult.all), + type: CustomButtonType.valid, + child: const Text( + 'Sign All', + style: StyleConstants.buttonText, + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + ); + } +} diff --git a/packages/reown_walletkit/example/linux/.gitignore b/packages/reown_walletkit/example/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/reown_walletkit/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/reown_walletkit/example/linux/CMakeLists.txt b/packages/reown_walletkit/example/linux/CMakeLists.txt new file mode 100644 index 0000000..e3ec211 --- /dev/null +++ b/packages/reown_walletkit/example/linux/CMakeLists.txt @@ -0,0 +1,138 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "wallet") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.wallet") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/reown_walletkit/example/linux/flutter/CMakeLists.txt b/packages/reown_walletkit/example/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/packages/reown_walletkit/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/reown_walletkit/example/linux/flutter/generated_plugin_registrant.cc b/packages/reown_walletkit/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/packages/reown_walletkit/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/packages/reown_walletkit/example/linux/flutter/generated_plugin_registrant.h b/packages/reown_walletkit/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/packages/reown_walletkit/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/reown_walletkit/example/linux/flutter/generated_plugins.cmake b/packages/reown_walletkit/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f16b4c3 --- /dev/null +++ b/packages/reown_walletkit/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/reown_walletkit/example/linux/main.cc b/packages/reown_walletkit/example/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/packages/reown_walletkit/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/reown_walletkit/example/linux/my_application.cc b/packages/reown_walletkit/example/linux/my_application.cc new file mode 100644 index 0000000..76e8510 --- /dev/null +++ b/packages/reown_walletkit/example/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "wallet"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "wallet"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/packages/reown_walletkit/example/linux/my_application.h b/packages/reown_walletkit/example/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/packages/reown_walletkit/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/reown_walletkit/example/macos/.gitignore b/packages/reown_walletkit/example/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/packages/reown_walletkit/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/reown_walletkit/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/reown_walletkit/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/packages/reown_walletkit/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/reown_walletkit/example/macos/Flutter/Flutter-Release.xcconfig b/packages/reown_walletkit/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/reown_walletkit/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/reown_walletkit/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..a4e08b1 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,18 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import connectivity_plus +import package_info_plus +import shared_preferences_foundation +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/packages/reown_walletkit/example/macos/Podfile b/packages/reown_walletkit/example/macos/Podfile new file mode 100644 index 0000000..049abe2 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Podfile @@ -0,0 +1,40 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/reown_walletkit/example/macos/Podfile.lock b/packages/reown_walletkit/example/macos/Podfile.lock new file mode 100644 index 0000000..1f66755 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Podfile.lock @@ -0,0 +1,35 @@ +PODS: + - FlutterMacOS (1.0.0) + - package_info_plus (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 + +COCOAPODS: 1.15.2 diff --git a/packages/reown_walletkit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/reown_walletkit/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4156de8 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,565 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = wallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* wallet.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* wallet.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/reown_walletkit/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_walletkit/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_walletkit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/reown_walletkit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..dc2bf40 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_walletkit/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/reown_walletkit/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/reown_walletkit/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reown_walletkit/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/reown_walletkit/example/macos/Runner/AppDelegate.swift b/packages/reown_walletkit/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/packages/reown_walletkit/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/reown_walletkit/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/reown_walletkit/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_walletkit/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/reown_walletkit/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..c13afeb --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = WalletKit Flutter + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.wallet + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. diff --git a/packages/reown_walletkit/example/macos/Runner/Configs/Debug.xcconfig b/packages/reown_walletkit/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/reown_walletkit/example/macos/Runner/Configs/Release.xcconfig b/packages/reown_walletkit/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/reown_walletkit/example/macos/Runner/Configs/Warnings.xcconfig b/packages/reown_walletkit/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/reown_walletkit/example/macos/Runner/DebugProfile.entitlements b/packages/reown_walletkit/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..91e6008 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.device.camera + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/packages/reown_walletkit/example/macos/Runner/Info.plist b/packages/reown_walletkit/example/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/reown_walletkit/example/macos/Runner/MainFlutterWindow.swift b/packages/reown_walletkit/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/reown_walletkit/example/macos/Runner/Release.entitlements b/packages/reown_walletkit/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/packages/reown_walletkit/example/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/packages/reown_walletkit/example/netlify.toml b/packages/reown_walletkit/example/netlify.toml new file mode 100644 index 0000000..8263e63 --- /dev/null +++ b/packages/reown_walletkit/example/netlify.toml @@ -0,0 +1,11 @@ +[[plugins]] + + package = "netlify-plugin-flutter" + + [plugins.inputs] + channel = "stable" + +[build] + +command = "flutter build web --release" +publish = "build/web" \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/.gitignore b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/.gitignore new file mode 100644 index 0000000..474a12e --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/.gitignore @@ -0,0 +1,58 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/LICENSE b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/LICENSE new file mode 100644 index 0000000..989e2c5 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/analysis_options.yaml b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/.gitignore b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/build.gradle b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/build.gradle new file mode 100644 index 0000000..3d5ba6d --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/build.gradle @@ -0,0 +1,47 @@ +group 'com.northladder.qr_bar_code_scanner_dialog' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace "com.northladder.qr_bar_code_scanner_dialog" + compileSdkVersion 31 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + minSdkVersion 20 + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradle/wrapper/gradle-wrapper.jar b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradle/wrapper/gradle-wrapper.properties b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ae04661 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradlew b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradlew new file mode 100755 index 0000000..a69d9cb --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradlew.bat b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradlew.bat new file mode 100644 index 0000000..53a6b23 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/settings.gradle b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/settings.gradle new file mode 100644 index 0000000..3ccf202 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'qr_bar_code_scanner_dialog' diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/src/main/AndroidManifest.xml b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1bdf510 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/src/main/kotlin/com/northladder/qr_bar_code_scanner_dialog/QrBarCodeScannerDialogPlugin.kt b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/src/main/kotlin/com/northladder/qr_bar_code_scanner_dialog/QrBarCodeScannerDialogPlugin.kt new file mode 100644 index 0000000..756b4e7 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/android/src/main/kotlin/com/northladder/qr_bar_code_scanner_dialog/QrBarCodeScannerDialogPlugin.kt @@ -0,0 +1,35 @@ +package com.northladder.qr_bar_code_scanner_dialog + +import androidx.annotation.NonNull + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +/** QrBarCodeScannerDialogPlugin */ +class QrBarCodeScannerDialogPlugin: FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel : MethodChannel + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "qr_bar_code_scanner_dialog") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/.gitignore b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/.gitignore new file mode 100644 index 0000000..0c88507 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/.gitignore @@ -0,0 +1,38 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Assets/.gitkeep b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/QrBarCodeScannerDialogPlugin.h b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/QrBarCodeScannerDialogPlugin.h new file mode 100644 index 0000000..92177c5 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/QrBarCodeScannerDialogPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface QrBarCodeScannerDialogPlugin : NSObject +@end diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/QrBarCodeScannerDialogPlugin.m b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/QrBarCodeScannerDialogPlugin.m new file mode 100644 index 0000000..6a44d71 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/QrBarCodeScannerDialogPlugin.m @@ -0,0 +1,15 @@ +#import "QrBarCodeScannerDialogPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "qr_bar_code_scanner_dialog-Swift.h" +#endif + +@implementation QrBarCodeScannerDialogPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftQrBarCodeScannerDialogPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/SwiftQrBarCodeScannerDialogPlugin.swift b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/SwiftQrBarCodeScannerDialogPlugin.swift new file mode 100644 index 0000000..5e8f75d --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/Classes/SwiftQrBarCodeScannerDialogPlugin.swift @@ -0,0 +1,14 @@ +import Flutter +import UIKit + +public class SwiftQrBarCodeScannerDialogPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "qr_bar_code_scanner_dialog", binaryMessenger: registrar.messenger()) + let instance = SwiftQrBarCodeScannerDialogPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + result("iOS " + UIDevice.current.systemVersion) + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/qr_bar_code_scanner_dialog.podspec b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/qr_bar_code_scanner_dialog.podspec new file mode 100644 index 0000000..bd15a30 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/ios/qr_bar_code_scanner_dialog.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint qr_bar_code_scanner_dialog.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'qr_bar_code_scanner_dialog' + s.version = '0.0.1' + s.summary = 'Plugin to scan Bar/QR code easly with Flutter.' + s.description = <<-DESC +Plugin to scan Bar/QR code easly with Flutter. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '9.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog.dart new file mode 100644 index 0000000..779b9eb --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog.dart @@ -0,0 +1,15 @@ +import 'qr_bar_code_scanner_dialog_platform_interface.dart'; + +import 'package:flutter/widgets.dart'; + +class QrBarCodeScannerDialog { + Future getPlatformVersion() { + return QrBarCodeScannerDialogPlatform.instance.getPlatformVersion(); + } + + void getScannedQrBarCode( + {BuildContext? context, required Function(String?) onCode}) { + QrBarCodeScannerDialogPlatform.instance + .scanBarOrQrCode(context: context, onScanSuccess: onCode); + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_method_channel.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_method_channel.dart new file mode 100644 index 0000000..3c28a02 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_method_channel.dart @@ -0,0 +1,136 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; + +import 'qr_bar_code_scanner_dialog_platform_interface.dart'; + +/// An implementation of [QrBarCodeScannerDialogPlatform] that uses method channels. +class MethodChannelQrBarCodeScannerDialog + extends QrBarCodeScannerDialogPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('qr_bar_code_scanner_dialog'); + + @override + Future getPlatformVersion() async { + final version = + await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } + + @override + void scanBarOrQrCode( + {BuildContext? context, required Function(String? code) onScanSuccess}) { + /// context is required to show alert in non-web platforms + assert(context != null); + + showDialog( + context: context!, + builder: (context) => Container( + alignment: Alignment.center, + child: Container( + height: 400, + width: 600, + margin: const EdgeInsets.all(20), + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: ScannerWidget(onScanSuccess: (code) { + if (code != null) { + Navigator.pop(context); + onScanSuccess(code); + } + }), + ), + )); + } +} + +class ScannerWidget extends StatefulWidget { + final void Function(String? code) onScanSuccess; + + const ScannerWidget({super.key, required this.onScanSuccess}); + + @override + createState() => _ScannerWidgetState(); +} + +class _ScannerWidgetState extends State { + QRViewController? controller; + GlobalKey qrKey = GlobalKey(debugLabel: 'scanner'); + + bool isScanned = false; + + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + controller?.pauseCamera(); + } else if (Platform.isIOS) { + controller?.resumeCamera(); + } + } + + @override + void dispose() { + /// dispose the controller + controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: _buildQrView(context), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text("Stop scanning"), + ), + ], + ); + } + + Widget _buildQrView(BuildContext context) { + double smallestDimension = min( + MediaQuery.of(context).size.width, MediaQuery.of(context).size.height); + + smallestDimension = min(smallestDimension, 550); + + return QRView( + key: qrKey, + onQRViewCreated: (controller) { + _onQRViewCreated(controller); + }, + overlay: QrScannerOverlayShape( + borderColor: Colors.black, + borderRadius: 10, + borderLength: 30, + borderWidth: 10, + cutOutSize: smallestDimension - 140), + ); + } + + void _onQRViewCreated(QRViewController controller) { + this.controller = controller; + controller.scannedDataStream.listen((Barcode scanData) async { + if (!isScanned) { + isScanned = true; + widget.onScanSuccess(scanData.code); + } + }); + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_platform_interface.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_platform_interface.dart new file mode 100644 index 0000000..e2b2c12 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_platform_interface.dart @@ -0,0 +1,36 @@ +import 'package:flutter/widgets.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'qr_bar_code_scanner_dialog_method_channel.dart'; + +abstract class QrBarCodeScannerDialogPlatform extends PlatformInterface { + /// Constructs a QrBarCodeScannerDialogPlatform. + QrBarCodeScannerDialogPlatform() : super(token: _token); + + static final Object _token = Object(); + + static QrBarCodeScannerDialogPlatform _instance = + MethodChannelQrBarCodeScannerDialog(); + + /// The default instance of [QrBarCodeScannerDialogPlatform] to use. + /// + /// Defaults to [MethodChannelQrBarCodeScannerDialog]. + static QrBarCodeScannerDialogPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [QrBarCodeScannerDialogPlatform] when + /// they register themselves. + static set instance(QrBarCodeScannerDialogPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } + + void scanBarOrQrCode( + {BuildContext? context, required Function(String?) onScanSuccess}) { + throw UnimplementedError('scanBarOrQrCodeWeb() has not been implemented.'); + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_web.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_web.dart new file mode 100644 index 0000000..5318dc9 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/lib/qr_bar_code_scanner_dialog_web.dart @@ -0,0 +1,161 @@ +// In order to *not* need this ignore, consider extracting the "web" version +// of your plugin as a separate package, instead of inlining it in the same +// package as the core of your plugin. +// ignore: avoid_web_libraries_in_flutter +import 'dart:html' as html + show window, Element, ScriptElement, StyleElement, querySelector, Text; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import 'qr_bar_code_scanner_dialog_platform_interface.dart'; +import 'dart:js' as js; + +const String _kQrBarCodeScannerModelDomId = '__qr_bar_code_scanner_web-model'; + +/// A web implementation of the QrBarCodeScannerDialogPlatform of the QrBarCodeScannerDialog plugin. +class QrBarCodeScannerDialogWeb extends QrBarCodeScannerDialogPlatform { + /// Constructs a QrBarCodeScannerDialogWeb + QrBarCodeScannerDialogWeb() { + _ensureInitialized(_kQrBarCodeScannerModelDomId); + } + + static void registerWith(Registrar registrar) { + QrBarCodeScannerDialogPlatform.instance = QrBarCodeScannerDialogWeb(); + } + + /// Initializes a DOM container where we can host input elements. + html.Element _ensureInitialized(String id) { + var target = html.querySelector('#$id'); + if (target == null) { + final html.Element targetElement = html.Element.div() + ..id = id + ..className = "modal"; + + final html.Element content = html.Element.div() + ..className = "modal-content"; + + final html.Element div = html.Element.div() + ..setAttribute("style", "container"); + + final html.Element reader = html.Element.div() + ..id = "qr-reader" + ..setAttribute("width", "400px"); + div.children.add(reader); + + content.children.add(div); + targetElement.children.add(content); + + final body = html.querySelector('body')!; + + body.children.add(targetElement); + + final script = html.ScriptElement() + ..src = "https://unpkg.com/html5-qrcode"; + body.children.add(script); + + final head = html.querySelector('head')!; + final style = html.StyleElement(); + + final styleContent = html.Text(""" + + /* The Modal (background) */ + .modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 1; /* Sit on top */ + padding-top: 100px; /* Location of the box */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ + } + + /* Modal Content */ + .modal-content { + margin: auto; + max-width: 600px; + border-radius: 10px; + } + + #qr-reader { + position: relative; + background: white; + margin: 25px; + border-radius: 10px; + border: none; + } + + #qr-reader__filescan_input, + #qr-reader__camera_permission_button { + background: #3b99e8; + border: none; + padding: 8px; + border-radius: 5px; + color: white; + cursor:pointer; + margin-bottom: 10px; + } + + """); + + final codeScript = html.ScriptElement(); + final scriptText = html.Text(r""" + + var html5QrcodeScanner; + + // Get the modal + var modal = document.getElementById("__qr_bar_code_scanner_web-model"); + + // When the user clicks anywhere outside of the modal, close it + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none"; + if(html5QrcodeScanner!=null) + html5QrcodeScanner.clear(); + } + } + + async function scanCode(message) { + + html5QrcodeScanner = new Html5QrcodeScanner("qr-reader", { fps: 20, qrbox: 250 }); + + modal.style.display = "block"; + html5QrcodeScanner.render((decodedText, decodedResult) => { + console.log(`Code scanned = ${decodedText}`, decodedResult); + message(`Code scanned = ${decodedText}`); + html5QrcodeScanner.clear(); + modal.style.display = "none"; + }); + + + } + + """); + codeScript.nodes.add(scriptText); + + style.nodes.add(styleContent); + head.children.add(style); + head.children.add(codeScript); + + target = targetElement; + } + return target; + } + + /// Returns a [String] containing the version of the platform. + @override + Future getPlatformVersion() async { + final version = html.window.navigator.userAgent; + return version; + } + + @override + void scanBarOrQrCode( + {BuildContext? context, required Function(String?) onScanSuccess}) { + js.context.callMethod("scanCode", [onScanSuccess]); + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/.gitignore b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/.gitignore new file mode 100644 index 0000000..474a12e --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/.gitignore @@ -0,0 +1,58 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +pubspec.lock +/build/ +coverage/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# fvm +.fvm/ + +**/secrets.properties +**/*.keystore + +# Run scripts +*.sh +*.env.secret diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/LICENSE b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/LICENSE new file mode 100644 index 0000000..7279e1f --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/LICENSE @@ -0,0 +1,9 @@ +Copyright 2018 Julius Canute + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/analysis_options.yaml b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/analysis_options.yaml new file mode 100644 index 0000000..a3be6b8 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/.gitignore b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/.gitignore new file mode 100644 index 0000000..d36c796 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/.gitignore @@ -0,0 +1,67 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/caches + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/build.gradle b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/build.gradle new file mode 100644 index 0000000..b660a44 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/build.gradle @@ -0,0 +1,64 @@ +group 'net.touchcapture.qr.flutterqr' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.9.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace "net.touchcapture.qr.flutterqr" + compileSdk 33 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + // minSdkVersion is determined by Native View. + minSdkVersion 20 + targetSdkVersion 33 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true + } + + kotlinOptions { + jvmTarget = '11' + } + + compileOptions { + // Flag to enable support for the new language APIs + coreLibraryDesugaringEnabled true + // Sets Java compatibility to Java 11 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + if (project.android.hasProperty('namespace')) { + namespace 'net.touchcapture.qr.flutterqr' + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.zxing:core:3.5.2' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/gradle.properties b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/gradle.properties new file mode 100644 index 0000000..08f2b5f --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableJetifier=true +android.useAndroidX=true diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/gradle/wrapper/gradle-wrapper.properties b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df9c58f --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jul 22 12:15:07 CEST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/settings.gradle b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/settings.gradle new file mode 100644 index 0000000..16c4243 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_qr' diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/AndroidManifest.xml b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..28f2e86 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/CustomFramingRectBarcodeView.kt b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/CustomFramingRectBarcodeView.kt new file mode 100644 index 0000000..3ad7431 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/CustomFramingRectBarcodeView.kt @@ -0,0 +1,45 @@ +package net.touchcapture.qr.flutterqr + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import com.journeyapps.barcodescanner.BarcodeView +import com.journeyapps.barcodescanner.Size + +class CustomFramingRectBarcodeView : BarcodeView { + private var bottomOffset = BOTTOM_OFFSET_NOT_SET_VALUE + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) + + override fun calculateFramingRect(container: Rect, surface: Rect): Rect { + val containerArea = Rect(container) + val intersects = + containerArea.intersect(surface) //adjusts the containerArea (code from super.calculateFramingRect) + val scanAreaRect = super.calculateFramingRect(container, surface) + if (bottomOffset != BOTTOM_OFFSET_NOT_SET_VALUE) { //if the setFramingRect function was called, then we shift the scan area by Y + val scanAreaRectWithOffset = Rect(scanAreaRect) + scanAreaRectWithOffset.bottom -= bottomOffset + scanAreaRectWithOffset.top -= bottomOffset + val belongsToContainer = scanAreaRectWithOffset.intersect(containerArea) + if (belongsToContainer) { + return scanAreaRectWithOffset + } + } + return scanAreaRect + } + + fun setFramingRect(rectWidth: Int, rectHeight: Int, bottomOffset: Int) { + this.bottomOffset = bottomOffset + framingRectSize = Size(rectWidth, rectHeight) + } + + companion object { + private const val BOTTOM_OFFSET_NOT_SET_VALUE = -1 + } +} \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt new file mode 100644 index 0000000..97977a4 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt @@ -0,0 +1,47 @@ +package net.touchcapture.qr.flutterqr + +import androidx.annotation.NonNull +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding + +class FlutterQrPlugin : FlutterPlugin, ActivityAware { + + /** Plugin registration embedding v2 */ + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + flutterPluginBinding.platformViewRegistry + .registerViewFactory( + VIEW_TYPE_ID, + QRViewFactory(flutterPluginBinding.binaryMessenger) + ) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + // Leave empty + // Nullifying QrShared.activity and QrShared.binding here will cause errors if plugin is detached by another plugin + } + + override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) { + QrShared.activity = activityPluginBinding.activity + QrShared.binding = activityPluginBinding + } + + override fun onDetachedFromActivityForConfigChanges() { + QrShared.activity = null + QrShared.binding = null + } + + override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) { + QrShared.activity = activityPluginBinding.activity + QrShared.binding = activityPluginBinding + } + + override fun onDetachedFromActivity() { + QrShared.activity = null + QrShared.binding = null + } + + companion object { + private const val VIEW_TYPE_ID = "net.touchcapture.qr.flutterqr/qrview" + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt new file mode 100644 index 0000000..4589f2f --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt @@ -0,0 +1,386 @@ +package net.touchcapture.qr.flutterqr + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.view.View +import androidx.core.content.ContextCompat +import com.google.zxing.BarcodeFormat +import com.google.zxing.ResultPoint +import com.journeyapps.barcodescanner.BarcodeCallback +import com.journeyapps.barcodescanner.BarcodeResult +import com.journeyapps.barcodescanner.DefaultDecoderFactory +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.PluginRegistry +import io.flutter.plugin.platform.PlatformView + +class QRView( + private val context: Context, + messenger: BinaryMessenger, + private val id: Int, + private val params: HashMap +) : PlatformView, MethodChannel.MethodCallHandler, PluginRegistry.RequestPermissionsResultListener { + + private val cameraRequestCode = QrShared.CAMERA_REQUEST_ID + this.id + + private val channel: MethodChannel = MethodChannel( + messenger, "net.touchcapture.qr.flutterqr/qrview_$id" + ) + private val cameraFacingBack = 0 + private val cameraFacingFront = 1 + + private var isRequestingPermission = false + private var isTorchOn = false + private var isPaused = false + private var barcodeView: CustomFramingRectBarcodeView? = null + private var unRegisterLifecycleCallback: UnRegisterLifecycleCallback? = null + + init { + QrShared.binding?.addRequestPermissionsResultListener(this) + + channel.setMethodCallHandler(this) + + unRegisterLifecycleCallback = QrShared.activity?.registerLifecycleCallbacks( + onPause = { + if (!isPaused && hasCameraPermission) barcodeView?.pause() + + }, + onResume = { + if (!hasCameraPermission && !isRequestingPermission) checkAndRequestPermission() + else if (!isPaused && hasCameraPermission) barcodeView?.resume() + } + ) + } + + override fun dispose() { + unRegisterLifecycleCallback?.invoke() + + QrShared.binding?.removeRequestPermissionsResultListener(this) + + barcodeView?.pause() + barcodeView = null + } + + override fun getView(): View = initBarCodeView() + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + @Suppress("UNCHECKED_CAST") + when (call.method) { + "startScan" -> startScan(call.arguments as? List, result) + + "stopScan" -> stopScan() + + "flipCamera" -> flipCamera(result) + + "toggleFlash" -> toggleFlash(result) + + "pauseCamera" -> pauseCamera(result) + + // Stopping camera is the same as pausing camera + "stopCamera" -> pauseCamera(result) + + "resumeCamera" -> resumeCamera(result) + + "requestPermissions" -> checkAndRequestPermission() + + "getCameraInfo" -> getCameraInfo(result) + + "getFlashInfo" -> getFlashInfo(result) + + "getSystemFeatures" -> getSystemFeatures(result) + + "changeScanArea" -> changeScanArea( + dpScanAreaWidth = requireNotNull(call.argument("scanAreaWidth")), + dpScanAreaHeight = requireNotNull(call.argument("scanAreaHeight")), + cutOutBottomOffset = requireNotNull(call.argument("cutOutBottomOffset")), + result = result, + ) + + "invertScan" -> setInvertScan( + isInvert = call.argument("isInvertScan") ?: false, + ) + + else -> result.notImplemented() + } + } + + private fun initBarCodeView(): CustomFramingRectBarcodeView { + var barcodeView = barcodeView + + if (barcodeView == null) { + barcodeView = CustomFramingRectBarcodeView(QrShared.activity).also { + this.barcodeView = it + } + + barcodeView.decoderFactory = DefaultDecoderFactory(null, null, null, 2) + + if (params[PARAMS_CAMERA_FACING] as Int == 1) { + barcodeView.cameraSettings?.requestedCameraId = cameraFacingFront + } + } else if (!isPaused) { + barcodeView.resume() + } + + return barcodeView + } + + // region Camera Info + + private fun getCameraInfo(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + result.success(barcodeView.cameraSettings.requestedCameraId) + } + + private fun getFlashInfo(result: MethodChannel.Result) { + if (barcodeView == null) return barCodeViewNotSet(result) + + result.success(isTorchOn) + } + + private fun hasFlash(): Boolean { + return hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) + } + + @SuppressLint("UnsupportedChromeOsCameraSystemFeature") + private fun hasBackCamera(): Boolean { + return hasSystemFeature(PackageManager.FEATURE_CAMERA) + } + + private fun hasFrontCamera(): Boolean { + return hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) + } + + private fun hasSystemFeature(feature: String): Boolean = + context.packageManager.hasSystemFeature(feature) + + private fun getSystemFeatures(result: MethodChannel.Result) { + try { + result.success( + mapOf( + "hasFrontCamera" to hasFrontCamera(), + "hasBackCamera" to hasBackCamera(), + "hasFlash" to hasFlash(), + "activeCamera" to barcodeView?.cameraSettings?.requestedCameraId + ) + ) + } catch (e: Exception) { + result.error("", e.message, null) + } + } + + // endregion + + // region Camera Controls + + private fun flipCamera(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + barcodeView.pause() + + val settings = barcodeView.cameraSettings + if (settings.requestedCameraId == cameraFacingFront) { + settings.requestedCameraId = cameraFacingBack + } else settings.requestedCameraId = cameraFacingFront + + barcodeView.resume() + + result.success(settings.requestedCameraId) + } + + private fun toggleFlash(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + if (hasFlash()) { + barcodeView.setTorch(!isTorchOn) + isTorchOn = !isTorchOn + result.success(isTorchOn) + } else { + result.error(ERROR_CODE_NOT_SET, ERROR_MESSAGE_FLASH_NOT_FOUND, null) + } + } + + private fun pauseCamera(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + if (barcodeView.isPreviewActive) { + isPaused = true + barcodeView.pause() + } + + result.success(true) + } + + private fun resumeCamera(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + if (!barcodeView.isPreviewActive) { + isPaused = false + barcodeView.resume() + } + + result.success(true) + } + + private fun startScan(arguments: List?, result: MethodChannel.Result) { + checkAndRequestPermission() + + val allowedBarcodeTypes = getAllowedBarcodeTypes(arguments, result) + + if (arguments == null) { + barcodeView?.decoderFactory = DefaultDecoderFactory(null, null, null, 2) + } else { + barcodeView?.decoderFactory = DefaultDecoderFactory(allowedBarcodeTypes, null, null, 2) + } + + barcodeView?.decodeContinuous( + object : BarcodeCallback { + override fun barcodeResult(result: BarcodeResult) { + if (allowedBarcodeTypes.isEmpty() || allowedBarcodeTypes.contains(result.barcodeFormat)) { + val code = mapOf( + "code" to result.text, + "type" to result.barcodeFormat.name, + "rawBytes" to result.rawBytes + ) + + channel.invokeMethod(CHANNEL_METHOD_ON_RECOGNIZE_QR, code) + } + } + + override fun possibleResultPoints(resultPoints: List) = Unit + } + ) + } + + private fun stopScan() { + barcodeView?.stopDecoding() + } + + private fun setInvertScan(isInvert: Boolean) { + val barcodeView = barcodeView ?: return + with(barcodeView) { + pause() + cameraSettings.isScanInverted = isInvert + resume() + } + } + + private fun changeScanArea( + dpScanAreaWidth: Double, + dpScanAreaHeight: Double, + cutOutBottomOffset: Double, + result: MethodChannel.Result + ) { + setScanAreaSize(dpScanAreaWidth, dpScanAreaHeight, cutOutBottomOffset) + + result.success(true) + } + + private fun setScanAreaSize( + dpScanAreaWidth: Double, + dpScanAreaHeight: Double, + dpCutOutBottomOffset: Double + ) { + barcodeView?.setFramingRect( + dpScanAreaWidth.convertDpToPixels(), + dpScanAreaHeight.convertDpToPixels(), + dpCutOutBottomOffset.convertDpToPixels(), + ) + } + + // endregion + + // region permissions + + private val hasCameraPermission: Boolean + get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || + ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ): Boolean { + if (requestCode != cameraRequestCode) return false + isRequestingPermission = false + + val permissionGranted = + grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED + + channel.invokeMethod(CHANNEL_METHOD_ON_PERMISSION_SET, permissionGranted) + + return permissionGranted + } + + + + private fun checkAndRequestPermission() { + if (hasCameraPermission) { + channel.invokeMethod(CHANNEL_METHOD_ON_PERMISSION_SET, true) + return + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isRequestingPermission) { + QrShared.activity?.requestPermissions( + arrayOf(Manifest.permission.CAMERA), + cameraRequestCode + ) + } + } + + // endregion + + // region barcode common + + private fun getAllowedBarcodeTypes( + arguments: List?, + result: MethodChannel.Result + ): List { + return try { + arguments?.map { + BarcodeFormat.values()[it] + }.orEmpty() + } catch (e: Exception) { + result.error("", e.message, null) + + emptyList() + } + } + + private fun barCodeViewNotSet(result: MethodChannel.Result) { + result.error( + ERROR_CODE_NOT_SET, + ERROR_MESSAGE_NOT_SET, + null + ) + } + + // endregion + + // region helpers + + private fun Double.convertDpToPixels() = + (this * context.resources.displayMetrics.density).toInt() + + // endregion + + companion object { + private const val CHANNEL_METHOD_ON_PERMISSION_SET = "onPermissionSet" + private const val CHANNEL_METHOD_ON_RECOGNIZE_QR = "onRecognizeQR" + + private const val PARAMS_CAMERA_FACING = "cameraFacing" + + private const val ERROR_CODE_NOT_SET = "404" + + private const val ERROR_MESSAGE_NOT_SET = "No barcode view found" + private const val ERROR_MESSAGE_FLASH_NOT_FOUND = "This device doesn't support flash" + } +} + diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt new file mode 100644 index 0000000..013c725 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt @@ -0,0 +1,30 @@ +package net.touchcapture.qr.flutterqr + +import android.content.Context +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + + +class QRViewFactory( + private val messenger: BinaryMessenger +) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + + override fun create( + context: Context?, + viewId: Int, + args: Any? + ): PlatformView { + + @Suppress("UNCHECKED_CAST") + val params = args as HashMap + + return QRView( + context = requireNotNull(context), + id = viewId, + messenger = messenger, + params = params + ) + } +} \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrActivityLifecycleCallbacks.kt b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrActivityLifecycleCallbacks.kt new file mode 100644 index 0000000..e22f711 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrActivityLifecycleCallbacks.kt @@ -0,0 +1,41 @@ +package net.touchcapture.qr.flutterqr + +import android.app.Activity +import android.app.Application +import android.os.Bundle + +class UnRegisterLifecycleCallback( + private val application: Application, + private val callback: Application.ActivityLifecycleCallbacks, +) { + operator fun invoke() = application.unregisterActivityLifecycleCallbacks(callback) +} + +fun Activity.registerLifecycleCallbacks( + onPause: (() -> Unit)? = null, + onResume: (() -> Unit)? = null, +): UnRegisterLifecycleCallback { + val callback = object : Application.ActivityLifecycleCallbacks { + override fun onActivityPaused(p0: Activity) { + if (p0 == this@registerLifecycleCallbacks) onPause?.invoke() + } + + override fun onActivityResumed(p0: Activity) { + if (p0 == this@registerLifecycleCallbacks) onResume?.invoke() + } + + override fun onActivityStarted(p0: Activity) = Unit + + override fun onActivityDestroyed(p0: Activity) = Unit + + override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) = Unit + + override fun onActivityStopped(p0: Activity) = Unit + + override fun onActivityCreated(p0: Activity, p1: Bundle?) = Unit + } + + application.registerActivityLifecycleCallbacks(callback) + + return UnRegisterLifecycleCallback(application, callback) +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrShared.kt b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrShared.kt new file mode 100644 index 0000000..6814640 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrShared.kt @@ -0,0 +1,15 @@ +package net.touchcapture.qr.flutterqr + +import android.annotation.SuppressLint +import android.app.Activity +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding + +@SuppressLint("StaticFieldLeak") +object QrShared { + const val CAMERA_REQUEST_ID = 513469796 + + var activity: Activity? = null + + var binding: ActivityPluginBinding? = null + +} \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/.gitignore b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/.gitignore new file mode 100644 index 0000000..fce96a3 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/.gitignore @@ -0,0 +1,78 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ \ No newline at end of file diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Assets/.gitkeep b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/FlutterQrPlugin.h b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/FlutterQrPlugin.h new file mode 100644 index 0000000..c3d077d --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/FlutterQrPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FlutterQrPlugin : NSObject +@end diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/FlutterQrPlugin.m b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/FlutterQrPlugin.m new file mode 100644 index 0000000..d52bedc --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/FlutterQrPlugin.m @@ -0,0 +1,13 @@ +#import "FlutterQrPlugin.h" + +#if __has_include() +#import +#else +#import "qr_code_scanner-Swift.h" +#endif + +@implementation FlutterQrPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftFlutterQrPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/QRView.swift b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/QRView.swift new file mode 100644 index 0000000..1af7581 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/QRView.swift @@ -0,0 +1,304 @@ +// +// QRView.swift +// flutter_qr +// +// Created by Julius Canute on 21/12/18. +// + +import Foundation +import MTBBarcodeScanner + +public class QRView:NSObject,FlutterPlatformView { + @IBOutlet var previewView: UIView! + var scanner: MTBBarcodeScanner? + var registrar: FlutterPluginRegistrar + var channel: FlutterMethodChannel + var cameraFacing: MTBCamera + + // Codabar, maxicode, rss14 & rssexpanded not supported. Replaced with qr. + // UPCa uses ean13 object. + var QRCodeTypes = [ + 0: AVMetadataObject.ObjectType.aztec, + 1: AVMetadataObject.ObjectType.qr, + 2: AVMetadataObject.ObjectType.code39, + 3: AVMetadataObject.ObjectType.code93, + 4: AVMetadataObject.ObjectType.code128, + 5: AVMetadataObject.ObjectType.dataMatrix, + 6: AVMetadataObject.ObjectType.ean8, + 7: AVMetadataObject.ObjectType.ean13, + 8: AVMetadataObject.ObjectType.interleaved2of5, + 9: AVMetadataObject.ObjectType.qr, + 10: AVMetadataObject.ObjectType.pdf417, + 11: AVMetadataObject.ObjectType.qr, + 12: AVMetadataObject.ObjectType.qr, + 13: AVMetadataObject.ObjectType.qr, + 14: AVMetadataObject.ObjectType.ean13, + 15: AVMetadataObject.ObjectType.upce + ] + + public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64, params: Dictionary){ + self.registrar = registrar + previewView = UIView(frame: frame) + cameraFacing = MTBCamera.init(rawValue: UInt(Int(params["cameraFacing"] as! Double))) ?? MTBCamera.back + channel = FlutterMethodChannel(name: "net.touchcapture.qr.flutterqr/qrview_\(id)", binaryMessenger: registrar.messenger()) + } + + deinit { + scanner?.stopScanning() + } + + public func view() -> UIView { + channel.setMethodCallHandler({ + [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + switch(call.method){ + case "setDimensions": + let arguments = call.arguments as! Dictionary + self?.setDimensions(result, + width: arguments["width"] ?? 0, + height: arguments["height"] ?? 0, + scanAreaWidth: arguments["scanAreaWidth"] ?? 0, + scanAreaHeight: arguments["scanAreaHeight"] ?? 0, + scanAreaOffset: arguments["scanAreaOffset"] ?? 0) + case "startScan": + self?.startScan(call.arguments as! Array, result) + case "flipCamera": + self?.flipCamera(result) + case "toggleFlash": + self?.toggleFlash(result) + case "pauseCamera": + self?.pauseCamera(result) + case "stopCamera": + self?.stopCamera(result) + case "resumeCamera": + self?.resumeCamera(result) + case "getCameraInfo": + self?.getCameraInfo(result) + case "getFlashInfo": + self?.getFlashInfo(result) + case "getSystemFeatures": + self?.getSystemFeatures(result) + default: + result(FlutterMethodNotImplemented) + return + } + }) + return previewView + } + + func setDimensions(_ result: @escaping FlutterResult, width: Double, height: Double, scanAreaWidth: Double, scanAreaHeight: Double, scanAreaOffset: Double) { + // Then set the size of the preview area. + previewView.frame = CGRect(x: 0, y: 0, width: width, height: height) + + // Then set the size of the scan area. + let midX = self.view().bounds.midX + let midY = self.view().bounds.midY + + if let sc: MTBBarcodeScanner = scanner { + // Set the size of the preview if preview is already created. + if let previewLayer = sc.previewLayer { + previewLayer.frame = self.previewView.bounds + } + } else { + // Create new preview. + scanner = MTBBarcodeScanner(previewView: previewView) + } + + // Set scanArea if provided. + if (scanAreaWidth != 0 && scanAreaHeight != 0) { + scanner?.didStartScanningBlock = { + self.scanner?.scanRect = CGRect(x: Double(midX) - (scanAreaWidth / 2), y: Double(midY) - (scanAreaHeight / 2), width: scanAreaWidth, height: scanAreaHeight) + + // Set offset if provided. + if (scanAreaOffset != 0) { + let reversedOffset = -scanAreaOffset + self.scanner?.scanRect = (self.scanner?.scanRect.offsetBy(dx: 0, dy: CGFloat(reversedOffset)))! + + } + } + } + return result(width) + + } + + func startScan(_ arguments: Array, _ result: @escaping FlutterResult) { + // Check for allowed barcodes + var allowedBarcodeTypes: Array = [] + arguments.forEach { arg in + allowedBarcodeTypes.append( QRCodeTypes[arg]!) + } + MTBBarcodeScanner.requestCameraPermission(success: { [weak self] permissionGranted in + guard let self = self else { return } + + self.channel.invokeMethod("onPermissionSet", arguments: permissionGranted) + + if permissionGranted { + do { + try self.scanner?.startScanning(with: self.cameraFacing, resultBlock: { [weak self] codes in + if let codes = codes { + for code in codes { + var typeString: String; + switch(code.type) { + case AVMetadataObject.ObjectType.aztec: + typeString = "AZTEC" + case AVMetadataObject.ObjectType.code39: + typeString = "CODE_39" + case AVMetadataObject.ObjectType.code93: + typeString = "CODE_93" + case AVMetadataObject.ObjectType.code128: + typeString = "CODE_128" + case AVMetadataObject.ObjectType.dataMatrix: + typeString = "DATA_MATRIX" + case AVMetadataObject.ObjectType.ean8: + typeString = "EAN_8" + case AVMetadataObject.ObjectType.ean13: + typeString = "EAN_13" + case AVMetadataObject.ObjectType.itf14, + AVMetadataObject.ObjectType.interleaved2of5: + typeString = "ITF" + case AVMetadataObject.ObjectType.pdf417: + typeString = "PDF_417" + case AVMetadataObject.ObjectType.qr: + typeString = "QR_CODE" + case AVMetadataObject.ObjectType.upce: + typeString = "UPC_E" + default: + return + } + let bytes = { () -> Data? in + if #available(iOS 11.0, *) { + switch (code.descriptor) { + case let qrDescriptor as CIQRCodeDescriptor: + return qrDescriptor.errorCorrectedPayload + case let aztecDescriptor as CIAztecCodeDescriptor: + return aztecDescriptor.errorCorrectedPayload + case let pdf417Descriptor as CIPDF417CodeDescriptor: + return pdf417Descriptor.errorCorrectedPayload + case let dataMatrixDescriptor as CIDataMatrixCodeDescriptor: + return dataMatrixDescriptor.errorCorrectedPayload + default: + return nil + } + } else { + return nil + } + }() + let result = { () -> [String : Any]? in + guard let stringValue = code.stringValue else { + guard let safeBytes = bytes else { + return nil + } + return ["type": typeString, "rawBytes": safeBytes] + } + guard let safeBytes = bytes else { + return ["code": stringValue, "type": typeString] + } + return ["code": stringValue, "type": typeString, "rawBytes": safeBytes] + }() + guard result != nil else { continue } + if allowedBarcodeTypes.count == 0 || allowedBarcodeTypes.contains(code.type) { + self?.channel.invokeMethod("onRecognizeQR", arguments: result) + } + + } + } + + }) + } catch { + let scanError = FlutterError(code: "unknown-error", message: "Unable to start scanning", details: error) + result(scanError) + } + } + }) + } + + func stopCamera(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + if sc.isScanning() { + sc.stopScanning() + } + } + } + + func getCameraInfo(_ result: @escaping FlutterResult) { + result(self.cameraFacing.rawValue) + } + + func flipCamera(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + if sc.hasOppositeCamera() { + sc.flipCamera() + self.cameraFacing = sc.camera + } + return result(sc.camera.rawValue) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func getFlashInfo(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + result(sc.torchMode.rawValue != 0) + } else { + let error = FlutterError(code: "cameraInformationError", message: "Could not get flash information", details: nil) + result(error) + } + } + + func toggleFlash(_ result: @escaping FlutterResult){ + if let sc: MTBBarcodeScanner = self.scanner { + if sc.hasTorch() { + sc.toggleTorch() + return result(sc.torchMode == MTBTorchMode(rawValue: 1)) + } + return result(FlutterError(code: "404", message: "This device doesn\'t support flash", details: nil)) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func pauseCamera(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + if sc.isScanning() { + sc.freezeCapture() + } + return result(true) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func resumeCamera(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + if !sc.isScanning() { + sc.unfreezeCapture() + } + return result(true) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func getSystemFeatures(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = scanner { + var hasBackCameraVar = false + var hasFrontCameraVar = false + let camera = sc.camera + + if(camera == MTBCamera(rawValue: 0)){ + hasBackCameraVar = true + if sc.hasOppositeCamera() { + hasFrontCameraVar = true + } + }else{ + hasFrontCameraVar = true + if sc.hasOppositeCamera() { + hasBackCameraVar = true + } + } + return result([ + "hasFrontCamera": hasFrontCameraVar, + "hasBackCamera": hasBackCameraVar, + "hasFlash": sc.hasTorch(), + "activeCamera": camera.rawValue + ]) + } + return result(FlutterError(code: "404", message: nil, details: nil)) + } + + } diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/QRViewFactory.swift b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/QRViewFactory.swift new file mode 100644 index 0000000..6f851f6 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/QRViewFactory.swift @@ -0,0 +1,27 @@ +// +// QRViewFactory.swift +// flutter_qr +// +// Created by Julius Canute on 21/12/18. +// + +import Foundation + +public class QRViewFactory: NSObject, FlutterPlatformViewFactory { + + var registrar: FlutterPluginRegistrar? + + public init(withRegistrar registrar: FlutterPluginRegistrar){ + super.init() + self.registrar = registrar + } + + public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { + let params = args as! Dictionary + return QRView(withFrame: frame, withRegistrar: registrar!,withId: viewId, params: params) + } + + public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { + return FlutterStandardMessageCodec(readerWriter: FlutterStandardReaderWriter()) + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/SwiftFlutterQrPlugin.swift b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/SwiftFlutterQrPlugin.swift new file mode 100644 index 0000000..f26aff6 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/Classes/SwiftFlutterQrPlugin.swift @@ -0,0 +1,22 @@ +import Flutter +import UIKit + +public class SwiftFlutterQrPlugin: NSObject, FlutterPlugin { + + var factory: QRViewFactory + public init(with registrar: FlutterPluginRegistrar) { + self.factory = QRViewFactory(withRegistrar: registrar) + registrar.register(factory, withId: "net.touchcapture.qr.flutterqr/qrview") + } + + public static func register(with registrar: FlutterPluginRegistrar) { + registrar.addApplicationDelegate(SwiftFlutterQrPlugin(with: registrar)) + } + + public func applicationDidEnterBackground(_ application: UIApplication) { + } + + public func applicationWillTerminate(_ application: UIApplication) { + } + +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/qr_code_scanner.podspec b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/qr_code_scanner.podspec new file mode 100644 index 0000000..f94e7cc --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/ios/qr_code_scanner.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'qr_code_scanner' + s.version = '0.2.0' + s.summary = 'QR Code Scanner for flutter.' + s.description = <<-DESC +A new Flutter project. + DESC + s.homepage = 'https://github.com/juliuscanute/qr_code_scanner' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'juliuscanute[*]touchcapture.net' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.dependency 'MTBBarcodeScanner' + s.ios.deployment_target = '8.0' + s.swift_version = '4.0' +end + diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/qr_code_scanner.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/qr_code_scanner.dart new file mode 100644 index 0000000..4220613 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/qr_code_scanner.dart @@ -0,0 +1,7 @@ +export 'src/qr_code_scanner.dart'; +export 'src/qr_scanner_overlay_shape.dart'; +export 'src/types/barcode.dart'; +export 'src/types/barcode_format.dart'; +export 'src/types/camera.dart'; +export 'src/types/camera_exception.dart'; +export 'src/types/features.dart'; diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/lifecycle_event_handler.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/lifecycle_event_handler.dart new file mode 100644 index 0000000..deb1aaa --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/lifecycle_event_handler.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; + +class LifecycleEventHandler extends WidgetsBindingObserver { + LifecycleEventHandler({ + required this.resumeCallBack, + }); + + late final AsyncCallback resumeCallBack; + + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async { + switch (state) { + case AppLifecycleState.resumed: + await resumeCallBack(); + break; + case AppLifecycleState.inactive: + case AppLifecycleState.paused: + case AppLifecycleState.detached: + case AppLifecycleState.hidden: + break; + } + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/qr_code_scanner.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/qr_code_scanner.dart new file mode 100644 index 0000000..d92c75d --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/qr_code_scanner.dart @@ -0,0 +1,375 @@ +// ignore_for_file: no_leading_underscores_for_local_identifiers + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'lifecycle_event_handler.dart'; +import 'qr_scanner_overlay_shape.dart'; +import 'types/barcode.dart'; +import 'types/barcode_format.dart'; +import 'types/camera.dart'; +import 'types/camera_exception.dart'; +import 'types/features.dart'; +import 'web/flutter_qr_stub.dart' +// ignore: uri_does_not_exist + if (dart.library.html) 'web/flutter_qr_web.dart'; + +typedef QRViewCreatedCallback = void Function(QRViewController); +typedef PermissionSetCallback = void Function(QRViewController, bool); + +/// The [QRView] is the view where the camera +/// and the barcode scanner gets displayed. +class QRView extends StatefulWidget { + const QRView({ + required Key key, + required this.onQRViewCreated, + this.overlay, + this.overlayMargin = EdgeInsets.zero, + this.cameraFacing = CameraFacing.back, + this.onPermissionSet, + this.formatsAllowed = const [], + }) : super(key: key); + + /// [onQRViewCreated] gets called when the view is created + final QRViewCreatedCallback onQRViewCreated; + + /// Use [overlay] to provide an overlay for the view. + /// This can be used to create a certain scan area. + final QrScannerOverlayShape? overlay; + + /// Use [overlayMargin] to provide a margin to [overlay] + final EdgeInsetsGeometry overlayMargin; + + /// Set which camera to use on startup. + /// + /// [cameraFacing] can either be CameraFacing.front or CameraFacing.back. + /// Defaults to CameraFacing.back + final CameraFacing cameraFacing; + + /// Calls the provided [onPermissionSet] callback when the permission is set. + final PermissionSetCallback? onPermissionSet; + + /// Use [formatsAllowed] to specify which formats needs to be scanned. + final List formatsAllowed; + + @override + State createState() => _QRViewState(); +} + +class _QRViewState extends State { + late MethodChannel _channel; + late LifecycleEventHandler _observer; + + @override + void initState() { + super.initState(); + _observer = LifecycleEventHandler(resumeCallBack: updateDimensions); + WidgetsBinding.instance.addObserver(_observer); + } + + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: onNotification, + child: SizeChangedLayoutNotifier( + child: (widget.overlay != null) + ? _getPlatformQrViewWithOverlay() + : _getPlatformQrView(), + ), + ); + } + + @override + void dispose() { + super.dispose(); + WidgetsBinding.instance.removeObserver(_observer); + } + + Future updateDimensions() async { + await QRViewController.updateDimensions( + widget.key as GlobalKey>, _channel, + overlay: widget.overlay); + } + + bool onNotification(notification) { + updateDimensions(); + return false; + } + + Widget _getPlatformQrViewWithOverlay() { + return Stack( + children: [ + _getPlatformQrView(), + Padding( + padding: widget.overlayMargin, + child: Container( + decoration: ShapeDecoration( + shape: widget.overlay!, + ), + ), + ) + ], + ); + } + + Widget _getPlatformQrView() { + Widget _platformQrView; + if (kIsWeb) { + _platformQrView = createWebQrView( + onPlatformViewCreated: widget.onQRViewCreated, + onPermissionSet: widget.onPermissionSet, + cameraFacing: widget.cameraFacing, + ); + } else { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + _platformQrView = AndroidView( + viewType: 'net.touchcapture.qr.flutterqr/qrview', + onPlatformViewCreated: _onPlatformViewCreated, + creationParams: + _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(), + creationParamsCodec: const StandardMessageCodec(), + ); + break; + case TargetPlatform.iOS: + _platformQrView = UiKitView( + viewType: 'net.touchcapture.qr.flutterqr/qrview', + onPlatformViewCreated: _onPlatformViewCreated, + creationParams: + _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(), + creationParamsCodec: const StandardMessageCodec(), + ); + break; + default: + throw UnsupportedError( + "Trying to use the default qrview implementation for $defaultTargetPlatform but there isn't a default one"); + } + } + return _platformQrView; + } + + void _onPlatformViewCreated(int id) { + _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id'); + + // Start scan after creation of the view + final controller = QRViewController._( + _channel, + widget.key as GlobalKey>?, + widget.onPermissionSet, + widget.cameraFacing) + .._startScan(widget.key as GlobalKey>, + widget.overlay, widget.formatsAllowed); + + // Initialize the controller for controlling the QRView + widget.onQRViewCreated(controller); + } +} + +class _QrCameraSettings { + _QrCameraSettings({ + this.cameraFacing = CameraFacing.unknown, + }); + + final CameraFacing cameraFacing; + + Map toMap() { + return { + 'cameraFacing': cameraFacing.index, + }; + } +} + +class QRViewController { + QRViewController._(MethodChannel channel, GlobalKey? qrKey, + PermissionSetCallback? onPermissionSet, CameraFacing cameraFacing) + : _channel = channel, + _cameraFacing = cameraFacing { + _channel.setMethodCallHandler((call) async { + switch (call.method) { + case 'onRecognizeQR': + if (call.arguments != null) { + final args = call.arguments as Map; + final code = args['code'] as String?; + final rawType = args['type'] as String; + // Raw bytes are only supported by Android. + final rawBytes = args['rawBytes'] as List?; + final format = BarcodeTypesExtension.fromString(rawType); + if (format != BarcodeFormat.unknown) { + final barcode = Barcode(code, format, rawBytes); + _scanUpdateController.sink.add(barcode); + } else { + throw Exception('Unexpected barcode type $rawType'); + } + } + break; + case 'onPermissionSet': + if (call.arguments != null && call.arguments is bool) { + _hasPermissions = call.arguments; + if (onPermissionSet != null) { + onPermissionSet(this, _hasPermissions); + } + } + break; + } + }); + } + + final MethodChannel _channel; + final CameraFacing _cameraFacing; + final StreamController _scanUpdateController = + StreamController(); + + Stream get scannedDataStream => _scanUpdateController.stream; + + bool _hasPermissions = false; + bool get hasPermissions => _hasPermissions; + + /// Starts the barcode scanner + Future _startScan(GlobalKey key, QrScannerOverlayShape? overlay, + List? barcodeFormats) async { + // We need to update the dimension before the scan is started. + try { + await QRViewController.updateDimensions(key, _channel, overlay: overlay); + return await _channel.invokeMethod( + 'startScan', barcodeFormats?.map((e) => e.asInt()).toList() ?? []); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets information about which camera is active. + Future getCameraInfo() async { + try { + var cameraFacing = await _channel.invokeMethod('getCameraInfo') as int; + if (cameraFacing == -1) return _cameraFacing; + return CameraFacing + .values[await _channel.invokeMethod('getCameraInfo') as int]; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Flips the camera between available modes + Future flipCamera() async { + try { + return CameraFacing + .values[await _channel.invokeMethod('flipCamera') as int]; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Get flashlight status + Future getFlashStatus() async { + try { + return await _channel.invokeMethod('getFlashInfo'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Toggles the flashlight between available modes + Future toggleFlash() async { + try { + await _channel.invokeMethod('toggleFlash') as bool?; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Pauses the camera and barcode scanning + Future pauseCamera() async { + try { + await _channel.invokeMethod('pauseCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Stops barcode scanning and the camera + Future stopCamera() async { + try { + await _channel.invokeMethod('stopCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Resumes barcode scanning + Future resumeCamera() async { + try { + await _channel.invokeMethod('resumeCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Returns which features are available on device. + Future getSystemFeatures() async { + try { + var features = + await _channel.invokeMapMethod('getSystemFeatures'); + if (features != null) { + return SystemFeatures.fromJson(features); + } + throw CameraException('Error', 'Could not get system features'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Stops the camera and disposes the barcode stream. + void dispose() { + if (defaultTargetPlatform == TargetPlatform.iOS) stopCamera(); + _scanUpdateController.close(); + } + + /// Updates the view dimensions for iOS. + static Future updateDimensions(GlobalKey key, MethodChannel channel, + {QrScannerOverlayShape? overlay}) async { + if (defaultTargetPlatform == TargetPlatform.iOS) { + // Add small delay to ensure the render box is loaded + await Future.delayed(const Duration(milliseconds: 300)); + if (key.currentContext == null) return false; + final renderBox = key.currentContext!.findRenderObject() as RenderBox; + try { + await channel.invokeMethod('setDimensions', { + 'width': renderBox.size.width, + 'height': renderBox.size.height, + 'scanAreaWidth': overlay?.cutOutWidth ?? 0, + 'scanAreaHeight': overlay?.cutOutHeight ?? 0, + 'scanAreaOffset': overlay?.cutOutBottomOffset ?? 0 + }); + return true; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } else if (defaultTargetPlatform == TargetPlatform.android) { + if (overlay == null) { + return false; + } + await channel.invokeMethod('changeScanArea', { + 'scanAreaWidth': overlay.cutOutWidth, + 'scanAreaHeight': overlay.cutOutHeight, + 'cutOutBottomOffset': overlay.cutOutBottomOffset + }); + return true; + } + return false; + } + + //Starts/Stops invert scanning. + Future scanInvert(bool isScanInvert) async { + if (defaultTargetPlatform == TargetPlatform.android) { + try { + await _channel + .invokeMethod('invertScan', {"isInvertScan": isScanInvert}); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/qr_scanner_overlay_shape.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/qr_scanner_overlay_shape.dart new file mode 100644 index 0000000..2d25162 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/qr_scanner_overlay_shape.dart @@ -0,0 +1,185 @@ +// ignore_for_file: no_leading_underscores_for_local_identifiers + +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class QrScannerOverlayShape extends ShapeBorder { + QrScannerOverlayShape({ + this.borderColor = Colors.red, + this.borderWidth = 3.0, + this.overlayColor = const Color.fromRGBO(0, 0, 0, 80), + this.borderRadius = 0, + this.borderLength = 40, + double? cutOutSize, + double? cutOutWidth, + double? cutOutHeight, + this.cutOutBottomOffset = 0, + }) : cutOutWidth = cutOutWidth ?? cutOutSize ?? 250, + cutOutHeight = cutOutHeight ?? cutOutSize ?? 250 { + assert( + borderLength <= + min(this.cutOutWidth, this.cutOutHeight) / 2 + borderWidth * 2, + "Border can't be larger than ${min(this.cutOutWidth, this.cutOutHeight) / 2 + borderWidth * 2}", + ); + assert( + (cutOutWidth == null && cutOutHeight == null) || + (cutOutSize == null && cutOutWidth != null && cutOutHeight != null), + 'Use only cutOutWidth and cutOutHeight or only cutOutSize'); + } + + final Color borderColor; + final double borderWidth; + final Color overlayColor; + final double borderRadius; + final double borderLength; + final double cutOutWidth; + final double cutOutHeight; + final double cutOutBottomOffset; + + @override + EdgeInsetsGeometry get dimensions => const EdgeInsets.all(10); + + @override + Path getInnerPath(Rect rect, {TextDirection? textDirection}) { + return Path() + ..fillType = PathFillType.evenOdd + ..addPath(getOuterPath(rect), Offset.zero); + } + + @override + Path getOuterPath(Rect rect, {TextDirection? textDirection}) { + Path _getLeftTopPath(Rect rect) { + return Path() + ..moveTo(rect.left, rect.bottom) + ..lineTo(rect.left, rect.top) + ..lineTo(rect.right, rect.top); + } + + return _getLeftTopPath(rect) + ..lineTo( + rect.right, + rect.bottom, + ) + ..lineTo( + rect.left, + rect.bottom, + ) + ..lineTo( + rect.left, + rect.top, + ); + } + + @override + void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { + final width = rect.width; + final borderWidthSize = width / 2; + final height = rect.height; + final borderOffset = borderWidth / 2; + final _borderLength = + borderLength > min(cutOutHeight, cutOutHeight) / 2 + borderWidth * 2 + ? borderWidthSize / 2 + : borderLength; + final _cutOutWidth = + cutOutWidth < width ? cutOutWidth : width - borderOffset; + final _cutOutHeight = + cutOutHeight < height ? cutOutHeight : height - borderOffset; + + final backgroundPaint = Paint() + ..color = overlayColor + ..style = PaintingStyle.fill; + + final borderPaint = Paint() + ..color = borderColor + ..style = PaintingStyle.stroke + ..strokeWidth = borderWidth; + + final boxPaint = Paint() + ..color = borderColor + ..style = PaintingStyle.fill + ..blendMode = BlendMode.dstOut; + + final cutOutRect = Rect.fromLTWH( + rect.left + width / 2 - _cutOutWidth / 2 + borderOffset, + -cutOutBottomOffset + + rect.top + + height / 2 - + _cutOutHeight / 2 + + borderOffset, + _cutOutWidth - borderOffset * 2, + _cutOutHeight - borderOffset * 2, + ); + + canvas + ..saveLayer( + rect, + backgroundPaint, + ) + ..drawRect( + rect, + backgroundPaint, + ) + // Draw top right corner + ..drawRRect( + RRect.fromLTRBAndCorners( + cutOutRect.right - _borderLength, + cutOutRect.top, + cutOutRect.right, + cutOutRect.top + _borderLength, + topRight: Radius.circular(borderRadius), + ), + borderPaint, + ) + // Draw top left corner + ..drawRRect( + RRect.fromLTRBAndCorners( + cutOutRect.left, + cutOutRect.top, + cutOutRect.left + _borderLength, + cutOutRect.top + _borderLength, + topLeft: Radius.circular(borderRadius), + ), + borderPaint, + ) + // Draw bottom right corner + ..drawRRect( + RRect.fromLTRBAndCorners( + cutOutRect.right - _borderLength, + cutOutRect.bottom - _borderLength, + cutOutRect.right, + cutOutRect.bottom, + bottomRight: Radius.circular(borderRadius), + ), + borderPaint, + ) + // Draw bottom left corner + ..drawRRect( + RRect.fromLTRBAndCorners( + cutOutRect.left, + cutOutRect.bottom - _borderLength, + cutOutRect.left + _borderLength, + cutOutRect.bottom, + bottomLeft: Radius.circular(borderRadius), + ), + borderPaint, + ) + ..drawRRect( + RRect.fromRectAndRadius( + cutOutRect, + Radius.circular(borderRadius), + ), + boxPaint, + ) + ..restore(); + } + + @override + ShapeBorder scale(double t) { + return QrScannerOverlayShape( + borderColor: borderColor, + borderWidth: borderWidth, + overlayColor: overlayColor, + ); + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/barcode.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/barcode.dart new file mode 100644 index 0000000..c8c3105 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/barcode.dart @@ -0,0 +1,16 @@ +import 'barcode_format.dart'; + +/// The [Barcode] object holds information about the barcode or qr code. +/// +/// [code] is the string-content of the barcode. +/// [format] displays which type the code is. +/// Only for Android and iOS, [rawBytes] gives a list of bytes of the result. +class Barcode { + Barcode(this.code, this.format, this.rawBytes); + + final String? code; + final BarcodeFormat format; + + /// Raw bytes are only supported by Android and iOS. + final List? rawBytes; +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/barcode_format.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/barcode_format.dart new file mode 100644 index 0000000..49bcee2 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/barcode_format.dart @@ -0,0 +1,148 @@ +enum BarcodeFormat { + /// Aztec 2D barcode format. + aztec, + + /// CODABAR 1D format. + /// Not supported in iOS + codabar, + + /// Code 39 1D format. + code39, + + /// Code 93 1D format. + code93, + + /// Code 128 1D format. + code128, + + /// Data Matrix 2D barcode format. + dataMatrix, + + /// EAN-8 1D format. + ean8, + + /// EAN-13 1D format. + ean13, + + /// ITF (Interleaved Two of Five) 1D format. + itf, + + /// MaxiCode 2D barcode format. + /// Not supported in iOS. + maxicode, + + /// PDF417 format. + pdf417, + + /// QR Code 2D barcode format. + qrcode, + + /// RSS 14 + /// Not supported in iOS. + rss14, + + /// RSS EXPANDED + /// Not supported in iOS. + rssExpanded, + + /// UPC-A 1D format. + /// Same as ean-13 on iOS. + upcA, + + /// UPC-E 1D format. + upcE, + + /// UPC/EAN extension format. Not a stand-alone format. + upcEanExtension, + + /// Unknown + unknown +} + +extension BarcodeTypesExtension on BarcodeFormat { + int asInt() { + return index; + } + + static BarcodeFormat fromString(String format) { + switch (format) { + case 'AZTEC': + return BarcodeFormat.aztec; + case 'CODABAR': + return BarcodeFormat.codabar; + case 'CODE_39': + return BarcodeFormat.code39; + case 'CODE_93': + return BarcodeFormat.code93; + case 'CODE_128': + return BarcodeFormat.code128; + case 'DATA_MATRIX': + return BarcodeFormat.dataMatrix; + case 'EAN_8': + return BarcodeFormat.ean8; + case 'EAN_13': + return BarcodeFormat.ean13; + case 'ITF': + return BarcodeFormat.itf; + case 'MAXICODE': + return BarcodeFormat.maxicode; + case 'PDF_417': + return BarcodeFormat.pdf417; + case 'QR_CODE': + return BarcodeFormat.qrcode; + case 'RSS14': + return BarcodeFormat.rss14; + case 'RSS_EXPANDED': + return BarcodeFormat.rssExpanded; + case 'UPC_A': + return BarcodeFormat.upcA; + case 'UPC_E': + return BarcodeFormat.upcE; + case 'UPC_EAN_EXTENSION': + return BarcodeFormat.upcEanExtension; + default: + return BarcodeFormat.unknown; + } + } + + String get formatName { + switch (this) { + case BarcodeFormat.aztec: + return 'AZTEC'; + case BarcodeFormat.codabar: + return 'CODABAR'; + case BarcodeFormat.code39: + return 'CODE_39'; + case BarcodeFormat.code93: + return 'CODE_93'; + case BarcodeFormat.code128: + return 'CODE_128'; + case BarcodeFormat.dataMatrix: + return 'DATA_MATRIX'; + case BarcodeFormat.ean8: + return 'EAN_8'; + case BarcodeFormat.ean13: + return 'EAN_13'; + case BarcodeFormat.itf: + return 'ITF'; + case BarcodeFormat.maxicode: + return 'MAXICODE'; + case BarcodeFormat.pdf417: + return 'PDF_417'; + case BarcodeFormat.qrcode: + return 'QR_CODE'; + case BarcodeFormat.rss14: + return 'RSS14'; + case BarcodeFormat.rssExpanded: + return 'RSS_EXPANDED'; + case BarcodeFormat.upcA: + return 'UPC_A'; + case BarcodeFormat.upcE: + return 'UPC_E'; + case BarcodeFormat.upcEanExtension: + return 'UPC_EAN_EXTENSION'; + default: + return 'UNKNOWN'; + } + } +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/camera.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/camera.dart new file mode 100644 index 0000000..67fe8cb --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/camera.dart @@ -0,0 +1,10 @@ +enum CameraFacing { + /// Shows back facing camera. + back, + + /// Shows front facing camera. + front, + + /// Unknown camera + unknown +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/camera_exception.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/camera_exception.dart new file mode 100644 index 0000000..0abb639 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/camera_exception.dart @@ -0,0 +1,14 @@ +/// This is thrown when the plugin reports an error. +class CameraException implements Exception { + /// Creates a new camera exception with the given error code and description. + CameraException(this.code, this.description); + + /// Error code. + String code; + + /// Textual description of the error. + String? description; + + @override + String toString() => 'CameraException($code, $description)'; +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/features.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/features.dart new file mode 100644 index 0000000..0c3618a --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/types/features.dart @@ -0,0 +1,13 @@ +class SystemFeatures { + SystemFeatures(this.hasFlash, this.hasBackCamera, this.hasFrontCamera); + + factory SystemFeatures.fromJson(Map features) => + SystemFeatures( + features['hasFlash'] ?? false, + features['hasBackCamera'] ?? false, + features['hasFrontCamera'] ?? false); + + final bool hasFlash; + final bool hasFrontCamera; + final bool hasBackCamera; +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/flutter_qr_stub.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/flutter_qr_stub.dart new file mode 100644 index 0000000..a6bc04d --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/flutter_qr_stub.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:qr_code_scanner/src/types/camera.dart'; + +Widget createWebQrView( + {onPlatformViewCreated, onPermissionSet, CameraFacing? cameraFacing}) => + const SizedBox(); diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/flutter_qr_web.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/flutter_qr_web.dart new file mode 100644 index 0000000..32f7570 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/flutter_qr_web.dart @@ -0,0 +1,334 @@ +// ignore_for_file: avoid_web_libraries_in_flutter, library_private_types_in_public_api + +import 'dart:async'; +import 'dart:core'; +import 'dart:html' as html; +import 'dart:js_util'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +import '../../qr_code_scanner.dart'; +import 'jsqr.dart'; +import 'media.dart'; + +/// Even though it has been highly modified, the origial implementation has been +/// adopted from https://github.com:treeder/jsqr_flutter +/// +/// Copyright 2020 @treeder +/// Copyright 2021 The one with the braid + +class WebQrView extends StatefulWidget { + final QRViewCreatedCallback onPlatformViewCreated; + final PermissionSetCallback? onPermissionSet; + final CameraFacing? cameraFacing; + + const WebQrView({ + Key? key, + required this.onPlatformViewCreated, + this.onPermissionSet, + this.cameraFacing = CameraFacing.front, + }) : super(key: key); + + @override + _WebQrViewState createState() => _WebQrViewState(); + + static html.DivElement vidDiv = + html.DivElement(); // need a global for the registerViewFactory + + static Future cameraAvailable() async { + final sources = + await html.window.navigator.mediaDevices!.enumerateDevices(); + // List vidIds = []; + var hasCam = false; + for (final e in sources) { + if (e.kind == 'videoinput') { + // vidIds.add(e['deviceId']); + hasCam = true; + } + } + return hasCam; + } +} + +class _WebQrViewState extends State { + html.MediaStream? _localStream; + // html.CanvasElement canvas; + // html.CanvasRenderingContext2D ctx; + bool _currentlyProcessing = false; + + QRViewControllerWeb? _controller; + + late Size _size = const Size(0, 0); + Timer? timer; + String? code; + String? _errorMsg; + html.VideoElement video = html.VideoElement(); + String viewID = 'QRVIEW-${DateTime.now().millisecondsSinceEpoch}'; + + final StreamController _scanUpdateController = + StreamController(); + late CameraFacing facing; + + Timer? _frameIntervall; + + @override + void initState() { + super.initState(); + + facing = widget.cameraFacing ?? CameraFacing.front; + + // video = html.VideoElement(); + WebQrView.vidDiv.children = [video]; + // ignore: UNDEFINED_PREFIXED_NAME + ui.platformViewRegistry + .registerViewFactory(viewID, (int id) => WebQrView.vidDiv); + // giving JavaScipt some time to process the DOM changes + Timer(const Duration(milliseconds: 500), () { + start(); + }); + } + + Future start() async { + await _makeCall(); + _frameIntervall?.cancel(); + _frameIntervall = + Timer.periodic(const Duration(milliseconds: 200), (timer) { + _captureFrame2(); + }); + } + + void cancel() { + if (timer != null) { + timer!.cancel(); + timer = null; + } + if (_currentlyProcessing) { + _stopStream(); + } + } + + @override + void dispose() { + cancel(); + super.dispose(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future _makeCall() async { + if (_localStream != null) { + return; + } + + try { + var constraints = UserMediaOptions( + video: VideoOptions( + facingMode: (facing == CameraFacing.front ? 'user' : 'environment'), + )); + // dart style, not working properly: + // var stream = + // await html.window.navigator.mediaDevices.getUserMedia(constraints); + // straight JS: + if (_controller == null) { + _controller = QRViewControllerWeb(this); + widget.onPlatformViewCreated(_controller!); + } + var stream = await promiseToFuture(getUserMedia(constraints)); + widget.onPermissionSet?.call(_controller!, true); + _localStream = stream; + video.srcObject = _localStream; + video.setAttribute('playsinline', + 'true'); // required to tell iOS safari we don't want fullscreen + await video.play(); + } catch (e) { + cancel(); + if (e.toString().contains("NotAllowedError")) { + widget.onPermissionSet?.call(_controller!, false); + } + setState(() { + _errorMsg = e.toString(); + }); + return; + } + if (!mounted) return; + + setState(() { + _currentlyProcessing = true; + }); + } + + Future _stopStream() async { + try { + // await _localStream.dispose(); + _localStream!.getTracks().forEach((track) { + if (track.readyState == 'live') { + track.stop(); + } + }); + // video.stop(); + video.srcObject = null; + _localStream = null; + // _localRenderer.srcObject = null; + // ignore: empty_catches + } catch (e) {} + } + + Future _captureFrame2() async { + if (_localStream == null) { + return null; + } + final canvas = + html.CanvasElement(width: video.videoWidth, height: video.videoHeight); + final ctx = canvas.context2D; + // canvas.width = video.videoWidth; + // canvas.height = video.videoHeight; + ctx.drawImage(video, 0, 0); + final imgData = ctx.getImageData(0, 0, canvas.width!, canvas.height!); + + final size = + Size(canvas.width?.toDouble() ?? 0, canvas.height?.toDouble() ?? 0); + if (size != _size) { + setState(() { + _setCanvasSize(size); + }); + } + + try { + final code = jsQR(imgData.data, canvas.width, canvas.height); + // ignore: unnecessary_null_comparison + if (code != null && code.data != null) { + _scanUpdateController + .add(Barcode(code.data, BarcodeFormat.qrcode, code.data.codeUnits)); + } + } on NoSuchMethodError { + // Do nothing, this exception occurs continously in web release when no + // code is found. + // NoSuchMethodError: method not found: 'get$data' on null + } + } + + @override + Widget build(BuildContext context) { + if (_errorMsg != null) { + return Center(child: Text(_errorMsg!)); + } + if (_localStream == null) { + return const Center(child: CircularProgressIndicator()); + } + return LayoutBuilder( + builder: (context, constraints) { + var zoom = 1.0; + + if (_size.height != 0) zoom = constraints.maxHeight / _size.height; + + if (_size.width != 0) { + final horizontalZoom = constraints.maxWidth / _size.width; + if (horizontalZoom > zoom) { + zoom = horizontalZoom; + } + } + + return SizedBox( + width: constraints.maxWidth, + height: constraints.maxHeight, + child: Center( + child: SizedBox.fromSize( + size: _size, + child: Transform.scale( + alignment: Alignment.center, + scale: zoom, + child: HtmlElementView(viewType: viewID), + ), + ), + ), + ); + }, + ); + } + + void _setCanvasSize(ui.Size size) { + setState(() { + _size = size; + }); + } +} + +class QRViewControllerWeb implements QRViewController { + final _WebQrViewState _state; + + QRViewControllerWeb(this._state); + @override + void dispose() => _state.cancel(); + + @override + Future flipCamera() async { + // improve error handling + _state.facing = _state.facing == CameraFacing.front + ? CameraFacing.back + : CameraFacing.front; + await _state.start(); + return _state.facing; + } + + @override + Future getCameraInfo() async { + return _state.facing; + } + + @override + Future getFlashStatus() async { + // flash is simply not supported by JavaScipt. To avoid issuing applications, we always return it to be off. + return false; + } + + @override + Future getSystemFeatures() { + // implement getSystemFeatures + throw UnimplementedError(); + } + + @override + // implement hasPermissions. Blocking: WebQrView.cameraAvailable() returns a Future whereas a bool is required + bool get hasPermissions => throw UnimplementedError(); + + @override + Future pauseCamera() { + // implement pauseCamera + throw UnimplementedError(); + } + + @override + Future resumeCamera() { + // implement resumeCamera + throw UnimplementedError(); + } + + @override + Stream get scannedDataStream => _state._scanUpdateController.stream; + + @override + Future stopCamera() { + // implement stopCamera + throw UnimplementedError(); + } + + @override + Future toggleFlash() async { + // flash is simply not supported by JavaScipt + return; + } + + @override + Future scanInvert(bool isScanInvert) { + // implement scanInvert + throw UnimplementedError(); + } +} + +Widget createWebQrView( + {onPlatformViewCreated, onPermissionSet, CameraFacing? cameraFacing}) => + WebQrView( + onPlatformViewCreated: onPlatformViewCreated, + onPermissionSet: onPermissionSet, + cameraFacing: cameraFacing, + ); diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/jsqr.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/jsqr.dart new file mode 100644 index 0000000..386936c --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/jsqr.dart @@ -0,0 +1,12 @@ +@JS() +library jsqr; + +import 'package:js/js.dart'; + +@JS('jsQR') +external Code jsQR(var data, int? width, int? height); + +@JS() +class Code { + external String get data; +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/media.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/media.dart new file mode 100644 index 0000000..cacd527 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/lib/src/web/media.dart @@ -0,0 +1,36 @@ +// This is here because dart doesn't seem to support this properly +// https://stackoverflow.com/questions/61161135/adding-support-for-navigator-mediadevices-getusermedia-to-dart + +@JS('navigator.mediaDevices') +library media_devices; + +import 'package:js/js.dart'; + +@JS('getUserMedia') +external Future getUserMedia(UserMediaOptions constraints); + +@JS() +@anonymous +class UserMediaOptions { + external VideoOptions get video; + + external factory UserMediaOptions({VideoOptions? video}); +} + +@JS() +@anonymous +class VideoOptions { + external String get facingMode; + // external DeviceIdOptions get deviceId; + + external factory VideoOptions( + {String? facingMode, DeviceIdOptions? deviceId}); +} + +@JS() +@anonymous +class DeviceIdOptions { + external String get exact; + + external factory DeviceIdOptions({String? exact}); +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/pubspec.yaml b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/pubspec.yaml new file mode 100644 index 0000000..72f21cb --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/packages/qr_code_scanner/pubspec.yaml @@ -0,0 +1,31 @@ +name: qr_code_scanner +description: QR code scanner that can be embedded inside flutter. It uses zxing in Android and MTBBarcode scanner in iOS. +version: 1.0.0 +homepage: https://juliuscanute.com +repository: https://github.com/juliuscanute/qr_code_scanner + +environment: + sdk: '>=2.17.0 <3.0.0' + flutter: ">=1.12.0" + +dependencies: + js: ^0.6.3 + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + +dev_dependencies: + flutter_lints: ^1.0.4 + +flutter: + plugin: + platforms: + android: + package: net.touchcapture.qr.flutterqr + pluginClass: FlutterQrPlugin + ios: + pluginClass: FlutterQrPlugin +# web: +# pluginClass: FlutterQrPlugin +# fileName: flutter_qr_web.dart diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/pubspec.yaml b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/pubspec.yaml new file mode 100644 index 0000000..4795335 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/pubspec.yaml @@ -0,0 +1,80 @@ +name: qr_bar_code_scanner_dialog +description: Plugin to show a simple scanner dialog and capture Bar/QR code easily. Works with Android iOS and Web. It uses html5-qrcode scanner for web and qr_code_scanner for Android and iOS +version: 0.0.5 +homepage: https://github.com/Afinas-nl/qr-bar-code-scanner-dialog +publish_to: none + +environment: + sdk: '>=2.18.0 <3.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + plugin_platform_interface: ^2.1.2 + qr_code_scanner: + path: packages/qr_code_scanner + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.northladder.qr_bar_code_scanner_dialog + pluginClass: QrBarCodeScannerDialogPlugin + ios: + pluginClass: QrBarCodeScannerDialogPlugin + web: + pluginClass: QrBarCodeScannerDialogWeb + fileName: qr_bar_code_scanner_dialog_web.dart + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/test/qr_bar_code_scanner_dialog_method_channel_test.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/test/qr_bar_code_scanner_dialog_method_channel_test.dart new file mode 100644 index 0000000..6914ffb --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/test/qr_bar_code_scanner_dialog_method_channel_test.dart @@ -0,0 +1,27 @@ +// ignore_for_file: deprecated_member_use + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog_method_channel.dart'; + +void main() { + MethodChannelQrBarCodeScannerDialog platform = + MethodChannelQrBarCodeScannerDialog(); + const MethodChannel channel = MethodChannel('qr_bar_code_scanner_dialog'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/test/qr_bar_code_scanner_dialog_test.dart b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/test/qr_bar_code_scanner_dialog_test.dart new file mode 100644 index 0000000..911dff1 --- /dev/null +++ b/packages/reown_walletkit/example/packages/qr-bar-code-scanner-dialog/test/qr_bar_code_scanner_dialog_test.dart @@ -0,0 +1,51 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart'; +import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog_platform_interface.dart'; +import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockQrBarCodeScannerDialogPlatform + with MockPlatformInterfaceMixin + implements QrBarCodeScannerDialogPlatform { + @override + Future getPlatformVersion() => Future.value('42'); + + @override + void scanBarOrQrCode( + {BuildContext? context, required Function(String? p1) onScanSuccess}) { + onScanSuccess("CODE"); + } +} + +void main() { + final QrBarCodeScannerDialogPlatform initialPlatform = + QrBarCodeScannerDialogPlatform.instance; + + test('$MethodChannelQrBarCodeScannerDialog is the default instance', () { + expect( + initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + QrBarCodeScannerDialog qrBarCodeScannerDialogPlugin = + QrBarCodeScannerDialog(); + MockQrBarCodeScannerDialogPlatform fakePlatform = + MockQrBarCodeScannerDialogPlatform(); + QrBarCodeScannerDialogPlatform.instance = fakePlatform; + + expect(await qrBarCodeScannerDialogPlugin.getPlatformVersion(), '42'); + }); + + test('scanBarOrQrCodeWeb', () async { + QrBarCodeScannerDialog qrBarCodeScannerDialogPlugin = + QrBarCodeScannerDialog(); + MockQrBarCodeScannerDialogPlatform fakePlatform = + MockQrBarCodeScannerDialogPlatform(); + QrBarCodeScannerDialogPlatform.instance = fakePlatform; + + qrBarCodeScannerDialogPlugin.getScannedQrBarCode(onCode: (code) { + expect(code, 'CODE'); + }); + }); +} diff --git a/packages/reown_walletkit/example/pubspec.yaml b/packages/reown_walletkit/example/pubspec.yaml new file mode 100644 index 0000000..20ce720 --- /dev/null +++ b/packages/reown_walletkit/example/pubspec.yaml @@ -0,0 +1,49 @@ +name: reown_walletkit_wallet +description: An example wallet for Reown's WalletKit built with Flutter + +publish_to: "none" + +version: 1.0.0+1 + +environment: + sdk: ">=2.19.0 <4.0.0" + +dependencies: + bs58: ^1.0.2 + convert: ^3.0.1 + cupertino_icons: ^1.0.2 + eth_sig_util: ^0.0.9 + fl_toast: ^3.1.0 + flutter: + sdk: flutter + get_it: ^7.2.0 + get_it_mixin: ^4.0.0 + http: ^1.2.2 + json_annotation: ^4.8.1 + kadena_dart_sdk: ^2.3.2 + package_info_plus: ^7.0.0 + pointycastle: ^3.9.1 + polkadart: ^0.4.6 + polkadart_keyring: ^0.4.3 + qr_bar_code_scanner_dialog: + path: packages/qr-bar-code-scanner-dialog + # reown_core: + # path: ../../reown_core/ + # reown_sign: + # path: ../../reown_sign/ + reown_walletkit: + path: ../ + shared_preferences: ^2.2.2 + solana: ^0.30.4 + solana_web3: ^0.1.3 + +dev_dependencies: + build_runner: ^2.0.0 + dependency_validator: ^3.2.2 + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + json_serializable: ^6.5.3 + +flutter: + uses-material-design: true diff --git a/packages/reown_walletkit/example/test/widget_test.dart b/packages/reown_walletkit/example/test/widget_test.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/packages/reown_walletkit/example/test/widget_test.dart @@ -0,0 +1 @@ +void main() {} diff --git a/packages/reown_walletkit/example/web/favicon.png b/packages/reown_walletkit/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/packages/reown_walletkit/example/web/favicon.png differ diff --git a/packages/reown_walletkit/example/web/icons/Icon-192.png b/packages/reown_walletkit/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/packages/reown_walletkit/example/web/icons/Icon-192.png differ diff --git a/packages/reown_walletkit/example/web/icons/Icon-512.png b/packages/reown_walletkit/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/packages/reown_walletkit/example/web/icons/Icon-512.png differ diff --git a/packages/reown_walletkit/example/web/icons/Icon-maskable-192.png b/packages/reown_walletkit/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/packages/reown_walletkit/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/reown_walletkit/example/web/icons/Icon-maskable-512.png b/packages/reown_walletkit/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/packages/reown_walletkit/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/reown_walletkit/example/web/index.html b/packages/reown_walletkit/example/web/index.html new file mode 100644 index 0000000..09dc96d --- /dev/null +++ b/packages/reown_walletkit/example/web/index.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + wallet + + + + + + + + + + diff --git a/packages/reown_walletkit/example/web/manifest.json b/packages/reown_walletkit/example/web/manifest.json new file mode 100644 index 0000000..c782297 --- /dev/null +++ b/packages/reown_walletkit/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "wallet", + "short_name": "wallet", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/reown_walletkit/example/windows/.gitignore b/packages/reown_walletkit/example/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/packages/reown_walletkit/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/reown_walletkit/example/windows/CMakeLists.txt b/packages/reown_walletkit/example/windows/CMakeLists.txt new file mode 100644 index 0000000..4a7a22d --- /dev/null +++ b/packages/reown_walletkit/example/windows/CMakeLists.txt @@ -0,0 +1,101 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(wallet LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "wallet") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/reown_walletkit/example/windows/flutter/CMakeLists.txt b/packages/reown_walletkit/example/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..930d207 --- /dev/null +++ b/packages/reown_walletkit/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/reown_walletkit/example/windows/flutter/generated_plugin_registrant.cc b/packages/reown_walletkit/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..5777988 --- /dev/null +++ b/packages/reown_walletkit/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/packages/reown_walletkit/example/windows/flutter/generated_plugin_registrant.h b/packages/reown_walletkit/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/packages/reown_walletkit/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/reown_walletkit/example/windows/flutter/generated_plugins.cmake b/packages/reown_walletkit/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..3103206 --- /dev/null +++ b/packages/reown_walletkit/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + connectivity_plus + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/reown_walletkit/example/windows/runner/CMakeLists.txt b/packages/reown_walletkit/example/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..17411a8 --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/reown_walletkit/example/windows/runner/Runner.rc b/packages/reown_walletkit/example/windows/runner/Runner.rc new file mode 100644 index 0000000..8b8335e --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "wallet" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "wallet" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "wallet.exe" "\0" + VALUE "ProductName", "wallet" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/reown_walletkit/example/windows/runner/flutter_window.cpp b/packages/reown_walletkit/example/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b43b909 --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/reown_walletkit/example/windows/runner/flutter_window.h b/packages/reown_walletkit/example/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/reown_walletkit/example/windows/runner/main.cpp b/packages/reown_walletkit/example/windows/runner/main.cpp new file mode 100644 index 0000000..eb9b53a --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"wallet", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/reown_walletkit/example/windows/runner/resource.h b/packages/reown_walletkit/example/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/reown_walletkit/example/windows/runner/resources/app_icon.ico b/packages/reown_walletkit/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/packages/reown_walletkit/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/reown_walletkit/example/windows/runner/runner.exe.manifest b/packages/reown_walletkit/example/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/reown_walletkit/example/windows/runner/utils.cpp b/packages/reown_walletkit/example/windows/runner/utils.cpp new file mode 100644 index 0000000..f5bf9fa --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/reown_walletkit/example/windows/runner/utils.h b/packages/reown_walletkit/example/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/reown_walletkit/example/windows/runner/win32_window.cpp b/packages/reown_walletkit/example/windows/runner/win32_window.cpp new file mode 100644 index 0000000..c10f08d --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/packages/reown_walletkit/example/windows/runner/win32_window.h b/packages/reown_walletkit/example/windows/runner/win32_window.h new file mode 100644 index 0000000..17ba431 --- /dev/null +++ b/packages/reown_walletkit/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/reown_walletkit/lib/i_walletkit_impl.dart b/packages/reown_walletkit/lib/i_walletkit_impl.dart new file mode 100644 index 0000000..070f045 --- /dev/null +++ b/packages/reown_walletkit/lib/i_walletkit_impl.dart @@ -0,0 +1,9 @@ +import 'package:reown_sign/i_sign_wallet.dart'; +import 'package:reown_sign/reown_sign.dart'; + +abstract class IReownWalletKit implements IReownSignWallet { + final String protocol = 'wc'; + final int version = 2; + + abstract final IReownSign reOwnSign; +} diff --git a/packages/reown_walletkit/lib/reown_walletkit.dart b/packages/reown_walletkit/lib/reown_walletkit.dart new file mode 100644 index 0000000..9f49098 --- /dev/null +++ b/packages/reown_walletkit/lib/reown_walletkit.dart @@ -0,0 +1,7 @@ +library reown_walletkit; + +export 'walletkit_impl.dart'; +export 'i_walletkit_impl.dart'; +export 'package:reown_core/reown_core.dart'; +export 'package:reown_sign/reown_sign.dart'; +export 'package:event/event.dart'; diff --git a/packages/reown_walletkit/lib/version.dart b/packages/reown_walletkit/lib/version.dart new file mode 100644 index 0000000..526cd53 --- /dev/null +++ b/packages/reown_walletkit/lib/version.dart @@ -0,0 +1,2 @@ +// Generated code. Do not modify. +const packageVersion = '1.0.0'; diff --git a/packages/reown_walletkit/lib/walletkit_impl.dart b/packages/reown_walletkit/lib/walletkit_impl.dart new file mode 100644 index 0000000..0e74bf4 --- /dev/null +++ b/packages/reown_walletkit/lib/walletkit_impl.dart @@ -0,0 +1,486 @@ +import 'package:event/event.dart'; +import 'package:reown_core/relay_client/websocket/http_client.dart'; +import 'package:reown_core/relay_client/websocket/i_http_client.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; +import 'package:reown_sign/reown_sign.dart'; +import 'package:reown_walletkit/i_walletkit_impl.dart'; + +class ReownWalletKit implements IReownWalletKit { + bool _initialized = false; + + static Future createInstance({ + required String projectId, + String relayUrl = ReownConstants.DEFAULT_RELAY_URL, + String pushUrl = ReownConstants.DEFAULT_PUSH_URL, + required PairingMetadata metadata, + bool memoryStore = false, + LogLevel logLevel = LogLevel.nothing, + IHttpClient httpClient = const HttpWrapper(), + }) async { + final walletKit = ReownWalletKit( + core: ReownCore( + projectId: projectId, + relayUrl: relayUrl, + pushUrl: pushUrl, + memoryStore: memoryStore, + logLevel: logLevel, + httpClient: httpClient, + ), + metadata: metadata, + ); + await walletKit.init(); + + return walletKit; + } + + ///---------- GENERIC ----------/// + + @override + final String protocol = 'wc'; + + @override + final int version = 2; + + @override + final IReownCore core; + + @override + final PairingMetadata metadata; + + ReownWalletKit({ + required this.core, + required this.metadata, + }) { + reOwnSign = ReownSign( + core: core, + metadata: metadata, + proposals: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PROPOSALS, + version: StoreVersions.VERSION_PROPOSALS, + fromJson: (dynamic value) { + return ProposalData.fromJson(value); + }, + ), + sessions: Sessions( + storage: core.storage, + context: StoreVersions.CONTEXT_SESSIONS, + version: StoreVersions.VERSION_SESSIONS, + fromJson: (dynamic value) { + return SessionData.fromJson(value); + }, + ), + pendingRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PENDING_REQUESTS, + version: StoreVersions.VERSION_PENDING_REQUESTS, + fromJson: (dynamic value) { + return SessionRequest.fromJson(value); + }, + ), + authKeys: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_KEYS, + version: StoreVersions.VERSION_AUTH_KEYS, + fromJson: (dynamic value) { + return AuthPublicKey.fromJson(value); + }, + ), + pairingTopics: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_PAIRING_TOPICS, + version: StoreVersions.VERSION_PAIRING_TOPICS, + fromJson: (dynamic value) { + return value; + }, + ), + sessionAuthRequests: GenericStore( + storage: core.storage, + context: StoreVersions.CONTEXT_AUTH_REQUESTS, + version: StoreVersions.VERSION_AUTH_REQUESTS, + fromJson: (dynamic value) { + return PendingSessionAuthRequest.fromJson(value); + }, + ), + ); + } + + @override + Future init() async { + if (_initialized) { + return; + } + + await core.start(); + await reOwnSign.init(); + + _initialized = true; + } + + @override + Future pair({ + required Uri uri, + }) async { + try { + return await reOwnSign.pair(uri: uri); + } catch (e) { + rethrow; + } + } + + ///---------- SIGN ENGINE ----------/// + + @override + late IReownSign reOwnSign; + + @override + Event get onSessionConnect => reOwnSign.onSessionConnect; + + @override + Event get onSessionDelete => reOwnSign.onSessionDelete; + + @override + Event get onSessionExpire => reOwnSign.onSessionExpire; + + @override + Event get onSessionProposal => + reOwnSign.onSessionProposal; + + @override + Event get onSessionProposalError => + reOwnSign.onSessionProposalError; + + @override + Event get onProposalExpire => + reOwnSign.onProposalExpire; + + @override + Event get onSessionRequest => reOwnSign.onSessionRequest; + + @override + Event get onSessionPing => reOwnSign.onSessionPing; + + @override + IGenericStore get proposals => reOwnSign.proposals; + + @override + ISessions get sessions => reOwnSign.sessions; + + @override + IGenericStore get pendingRequests => + reOwnSign.pendingRequests; + + @override + Future approveSession({ + required int id, + required Map namespaces, + Map? sessionProperties, + String? relayProtocol, + }) async { + try { + return await reOwnSign.approveSession( + id: id, + namespaces: namespaces, + sessionProperties: sessionProperties, + relayProtocol: relayProtocol, + ); + } catch (e) { + rethrow; + } + } + + @override + Future rejectSession({ + required int id, + required ReownSignError reason, + }) async { + try { + return await reOwnSign.rejectSession( + id: id, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + Future updateSession({ + required String topic, + required Map namespaces, + }) async { + try { + return await reOwnSign.updateSession( + topic: topic, + namespaces: namespaces, + ); + } catch (e) { + // final error = e as WCError; + rethrow; + } + } + + @override + Future extendSession({ + required String topic, + }) async { + try { + return await reOwnSign.extendSession(topic: topic); + } catch (e) { + rethrow; + } + } + + @override + void registerRequestHandler({ + required String chainId, + required String method, + dynamic Function(String, dynamic)? handler, + }) { + try { + return reOwnSign.registerRequestHandler( + chainId: chainId, + method: method, + handler: handler, + ); + } catch (e) { + rethrow; + } + } + + @override + Future respondSessionRequest({ + required String topic, + required JsonRpcResponse response, + }) { + try { + return reOwnSign.respondSessionRequest( + topic: topic, + response: response, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerEventEmitter({ + required String chainId, + required String event, + }) { + try { + return reOwnSign.registerEventEmitter( + chainId: chainId, + event: event, + ); + } catch (e) { + rethrow; + } + } + + @override + void registerAccount({ + required String chainId, + required String accountAddress, + }) { + try { + return reOwnSign.registerAccount( + chainId: chainId, + accountAddress: accountAddress, + ); + } catch (e) { + rethrow; + } + } + + @override + Future emitSessionEvent({ + required String topic, + required String chainId, + required SessionEventParams event, + }) async { + try { + return await reOwnSign.emitSessionEvent( + topic: topic, + chainId: chainId, + event: event, + ); + } catch (e) { + rethrow; + } + } + + @override + Future disconnectSession({ + required String topic, + required ReownSignError reason, + }) async { + try { + return await reOwnSign.disconnectSession( + topic: topic, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + SessionData? find({ + required Map requiredNamespaces, + }) { + try { + return reOwnSign.find(requiredNamespaces: requiredNamespaces); + } catch (e) { + rethrow; + } + } + + @override + Map getActiveSessions() { + try { + return reOwnSign.getActiveSessions(); + } catch (e) { + rethrow; + } + } + + @override + Map getSessionsForPairing({ + required String pairingTopic, + }) { + try { + return reOwnSign.getSessionsForPairing( + pairingTopic: pairingTopic, + ); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionProposals() { + try { + return reOwnSign.getPendingSessionProposals(); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionRequests() { + try { + return reOwnSign.getPendingSessionRequests(); + } catch (e) { + rethrow; + } + } + + @override + Future dispatchEnvelope(String url) async { + try { + return await reOwnSign.dispatchEnvelope(url); + } catch (e) { + rethrow; + } + } + + @override + Future redirectToDapp({ + required String topic, + required Redirect? redirect, + }) { + return reOwnSign.redirectToDapp( + topic: topic, + redirect: redirect, + ); + } + + @override + IPairingStore get pairings => core.pairing.getStore(); + + @override + IGenericStore get sessionAuthRequests => + reOwnSign.sessionAuthRequests; + + @override + Event get onSessionAuthRequest => + reOwnSign.onSessionAuthRequest; + + @override + Future approveSessionAuthenticate({ + required int id, + List? auths, + }) { + try { + return reOwnSign.approveSessionAuthenticate( + id: id, + auths: auths, + ); + } catch (e) { + rethrow; + } + } + + @override + Future rejectSessionAuthenticate({ + required int id, + required ReownSignError reason, + }) { + try { + return reOwnSign.rejectSessionAuthenticate( + id: id, + reason: reason, + ); + } catch (e) { + rethrow; + } + } + + @override + Map getPendingSessionAuthRequests() { + try { + return reOwnSign.getPendingSessionAuthRequests(); + } catch (e) { + rethrow; + } + } + + @override + String formatAuthMessage({ + required String iss, + required CacaoRequestPayload cacaoPayload, + }) { + try { + return reOwnSign.formatAuthMessage( + iss: iss, + cacaoPayload: cacaoPayload, + ); + } catch (e) { + rethrow; + } + } + + @override + Future validateSignedCacao({ + required Cacao cacao, + required String projectId, + }) { + try { + return reOwnSign.validateSignedCacao( + cacao: cacao, + projectId: projectId, + ); + } catch (e) { + rethrow; + } + } + + @override + IGenericStore get authKeys => reOwnSign.authKeys; + + @override + IGenericStore get pairingTopics => reOwnSign.pairingTopics; +} diff --git a/packages/reown_walletkit/pubspec.yaml b/packages/reown_walletkit/pubspec.yaml new file mode 100644 index 0000000..27107fc --- /dev/null +++ b/packages/reown_walletkit/pubspec.yaml @@ -0,0 +1,40 @@ +name: reown_walletkit +description: "The communications protocol for web3" +version: 1.0.0 +homepage: https://github.com/reown-com/reown_flutter +repository: https://github.com/reown-com/reown_flutter + +environment: + sdk: ">=2.19.0 <4.0.0" + +dependencies: + event: ^2.1.2 + flutter: + sdk: flutter + reown_core: + path: ../reown_core + reown_sign: + path: ../reown_sign + +dev_dependencies: + build_runner: ^2.4.7 + build_version: ^2.1.1 + dependency_validator: ^3.2.2 + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + http: ^1.2.2 + # freezed: ^2.4.5 + # json_serializable: ^6.7.0 + mockito: ^5.4.3 + package_info_plus: ^7.0.0 + reown_appkit: + path: ../reown_appkit + +platforms: + android: + ios: + web: + macos: + linux: + windows: diff --git a/packages/reown_walletkit/test/reown_walletkit_test.dart b/packages/reown_walletkit/test/reown_walletkit_test.dart new file mode 100644 index 0000000..293f9fe --- /dev/null +++ b/packages/reown_walletkit/test/reown_walletkit_test.dart @@ -0,0 +1,178 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'shared/shared_test_utils.dart'; +import 'shared/shared_test_values.dart'; +import 'walletkit_helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockPackageInfo(); + mockConnectivity(); + + final List Function(PairingMetadata)> appCreators = [ + (PairingMetadata metadata) async => await ReownAppKit.createInstance( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + memoryStore: true, + metadata: metadata, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ), + ]; + final List Function(PairingMetadata)> walletCreators = + [ + (PairingMetadata metadata) async => await ReownWalletKit.createInstance( + projectId: TEST_PROJECT_ID, + relayUrl: TEST_RELAY_URL, + memoryStore: true, + metadata: metadata, + logLevel: LogLevel.info, + httpClient: getHttpWrapper(), + ), + ]; + + final List contexts = ['ReownWalletKit']; + + for (int i = 0; i < walletCreators.length; i++) { + runTests( + context: contexts[i], + appKitCreator: appCreators[i], + walletKitCreator: walletCreators[i], + ); + } +} + +void runTests({ + required String context, + required Future Function(PairingMetadata) appKitCreator, + required Future Function(PairingMetadata) walletKitCreator, +}) { + group(context, () { + late IReownAppKit clientA; + late IReownWalletKit clientB; + + setUp(() async { + clientA = await appKitCreator( + const PairingMetadata( + name: 'App A (Proposer, dapp)', + description: 'Description of Proposer App run by client A', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], + ), + ); + clientB = await walletKitCreator( + const PairingMetadata( + name: 'App B (Responder, Wallet)', + description: 'Description of Proposer App run by client B', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], + ), + ); + }); + + tearDown(() async { + await clientA.core.relayClient.disconnect(); + await clientB.core.relayClient.disconnect(); + }); + + group('happy path', () { + test('connects, reconnects, and emits proper events', () async { + Completer completer = Completer(); + Completer completerBSign = Completer(); + // Completer completerBAuth = Completer(); + int counterA = 0; + int counterBSign = 0; + // int counterBAuth = 0; + clientA.onSessionConnect.subscribe((args) { + counterA++; + completer.complete(); + }); + clientB.onSessionProposal.subscribe((args) { + counterBSign++; + completerBSign.complete(); + }); + // clientB.onAuthRequest.subscribe((args) { + // counterBAuth++; + // completerBAuth.complete(); + // }); + + final connectionInfo = await ReownWalletKitHelpers.testWalletKit( + clientA, + clientB, + qrCodeScanLatencyMs: 1000, + ); + + // print('swag 1'); + await completer.future; + // print('swag 2'); + await completerBSign.future; + // print('swag 3'); + // await completerBAuth.future; + + expect(counterA, 1); + expect(counterBSign, 1); + // expect(counterBAuth, 1); + + expect( + clientA.pairings.getAll().length, + clientB.pairings.getAll().length, + ); + expect( + clientA.getActiveSessions().length, + 1, + ); + expect( + clientA.getActiveSessions().length, + clientB.getActiveSessions().length, + ); + + completer = Completer(); + completerBSign = Completer(); + // completerBAuth = Completer(); + + final _ = await ReownWalletKitHelpers.testWalletKit( + clientA, + clientB, + pairingTopic: connectionInfo.pairing.topic, + ); + + // print('swag 4'); + await completer.future; + // print('swag 5'); + await completerBSign.future; + // print('swag 6'); + // await completerBAuth.future; + + expect(counterA, 2); + expect(counterBSign, 2); + // expect(counterBAuth, 2); + + clientA.onSessionConnect.unsubscribeAll(); + clientB.onSessionProposal.unsubscribeAll(); + }); + + test('connects, and reconnects with scan latency', () async { + final connectionInfo = await ReownWalletKitHelpers.testWalletKit( + clientA, + clientB, + qrCodeScanLatencyMs: 1000, + ); + expect( + clientA.pairings.getAll().length, + clientB.pairings.getAll().length, + ); + final _ = await ReownWalletKitHelpers.testWalletKit( + clientA, + clientB, + pairingTopic: connectionInfo.pairing.topic, + qrCodeScanLatencyMs: 1000, + ); + }); + }); + }); +} diff --git a/packages/reown_walletkit/test/shared/shared_test_utils.dart b/packages/reown_walletkit/test/shared/shared_test_utils.dart new file mode 100644 index 0000000..cc6c760 --- /dev/null +++ b/packages/reown_walletkit/test/shared/shared_test_utils.dart @@ -0,0 +1,119 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import 'package:reown_core/crypto/crypto.dart'; +import 'package:reown_core/crypto/crypto_utils.dart'; +import 'package:reown_core/crypto/i_crypto.dart'; +import 'package:reown_core/relay_client/i_message_tracker.dart'; +import 'package:reown_core/relay_client/message_tracker.dart'; +import 'package:reown_core/relay_client/websocket/http_client.dart'; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart'; +import 'package:reown_core/reown_core.dart'; +import 'package:reown_core/store/generic_store.dart'; +import 'package:reown_core/store/i_generic_store.dart'; + +import 'shared_test_utils.mocks.dart'; + +@GenerateMocks([ + CryptoUtils, + Crypto, + MessageTracker, + HttpWrapper, + ReownCore, + WebSocketHandler, +]) +class SharedTestUtils {} + +ICrypto getCrypto({ + IReownCore? core, + MockCryptoUtils? utils, +}) { + final IReownCore rcore = core ?? ReownCore(projectId: '', memoryStore: true); + final ICrypto crypto = Crypto( + core: rcore, + keyChain: GenericStore( + storage: rcore.storage, + context: StoreVersions.CONTEXT_KEYCHAIN, + version: StoreVersions.VERSION_KEYCHAIN, + fromJson: (dynamic value) => value as String, + ), + utils: utils, + ); + rcore.crypto = crypto; + return crypto; +} + +IMessageTracker getMessageTracker({ + IReownCore? core, +}) { + final IReownCore rcore = core ?? ReownCore(projectId: '', memoryStore: true); + return MessageTracker( + storage: rcore.storage, + context: StoreVersions.CONTEXT_MESSAGE_TRACKER, + version: StoreVersions.VERSION_MESSAGE_TRACKER, + fromJson: (dynamic value) => ReownCoreUtils.convertMapTo(value), + ); +} + +IGenericStore getTopicMap({ + IReownCore? core, +}) { + final IReownCore rcore = core ?? ReownCore(projectId: '', memoryStore: true); + return GenericStore( + storage: rcore.storage, + context: StoreVersions.CONTEXT_TOPIC_MAP, + version: StoreVersions.VERSION_TOPIC_MAP, + fromJson: (dynamic value) => value as String, + ); +} + +MockHttpWrapper getHttpWrapper() { + final MockHttpWrapper httpWrapper = MockHttpWrapper(); + when(httpWrapper.get(any)).thenAnswer((_) async => Response('', 200)); + // when(httpWrapper.post( + // url: anyNamed('url'), + // body: anyNamed('body'), + // )).thenAnswer((_) async => ''); + + return httpWrapper; +} + +mockPackageInfo() { + PackageInfo.setMockInitialValues( + appName: _mockInitialValues['appName'], + packageName: _mockInitialValues['packageName'], + version: _mockInitialValues['version'], + buildNumber: _mockInitialValues['buildNumber'], + buildSignature: _mockInitialValues['buildSignature'], + ); +} + +mockConnectivity([List values = const ['wifi']]) { + const channel = MethodChannel('dev.fluttercommunity.plus/connectivity'); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler( + channel.name, + (data) async { + final call = channel.codec.decodeMethodCall(data); + if (call.method == 'getAll') { + return channel.codec.encodeSuccessEnvelope(_mockInitialValues); + } + if (call.method == 'check') { + return channel.codec.encodeSuccessEnvelope(values); + } + return null; + }, + ); +} + +Map get _mockInitialValues => { + 'appName': 'reown_core', + 'packageName': 'com.reown.flutterdapp', + 'version': '1.0', + 'buildNumber': '2', + 'buildSignature': 'buildSignature', + }; diff --git a/packages/reown_walletkit/test/shared/shared_test_utils.mocks.dart b/packages/reown_walletkit/test/shared/shared_test_utils.mocks.dart new file mode 100644 index 0000000..e7a5ab2 --- /dev/null +++ b/packages/reown_walletkit/test/shared/shared_test_utils.mocks.dart @@ -0,0 +1,1558 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in reown_walletkit/test/shared/shared_test_utils.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i23; +import 'dart:typed_data' as _i21; + +import 'package:event/event.dart' as _i8; +import 'package:http/http.dart' as _i9; +import 'package:logger/logger.dart' as _i19; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i22; +import 'package:reown_core/connectivity/i_connectivity.dart' as _i17; +import 'package:reown_core/core_impl.dart' as _i28; +import 'package:reown_core/crypto/crypto.dart' as _i24; +import 'package:reown_core/crypto/crypto_models.dart' as _i2; +import 'package:reown_core/crypto/crypto_utils.dart' as _i20; +import 'package:reown_core/crypto/i_crypto.dart' as _i10; +import 'package:reown_core/crypto/i_crypto_utils.dart' as _i5; +import 'package:reown_core/echo/i_echo.dart' as _i14; +import 'package:reown_core/heartbit/i_heartbeat.dart' as _i15; +import 'package:reown_core/i_core_impl.dart' as _i3; +import 'package:reown_core/pairing/i_expirer.dart' as _i12; +import 'package:reown_core/pairing/i_pairing.dart' as _i13; +import 'package:reown_core/relay_auth/i_relay_auth.dart' as _i6; +import 'package:reown_core/relay_client/i_relay_client.dart' as _i11; +import 'package:reown_core/relay_client/message_tracker.dart' as _i25; +import 'package:reown_core/relay_client/websocket/http_client.dart' as _i27; +import 'package:reown_core/relay_client/websocket/websocket_handler.dart' + as _i29; +import 'package:reown_core/store/i_generic_store.dart' as _i4; +import 'package:reown_core/store/i_store.dart' as _i7; +import 'package:reown_core/store/link_mode_store.dart' as _i18; +import 'package:reown_core/store/store_models.dart' as _i26; +import 'package:reown_core/verify/i_verify.dart' as _i16; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeCryptoKeyPair_0 extends _i1.SmartFake implements _i2.CryptoKeyPair { + _FakeCryptoKeyPair_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEncodingParams_1 extends _i1.SmartFake + implements _i2.EncodingParams { + _FakeEncodingParams_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEncodingValidation_2 extends _i1.SmartFake + implements _i2.EncodingValidation { + _FakeEncodingValidation_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIReownCore_3 extends _i1.SmartFake implements _i3.IReownCore { + _FakeIReownCore_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIGenericStore_4 extends _i1.SmartFake + implements _i4.IGenericStore { + _FakeIGenericStore_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeICryptoUtils_5 extends _i1.SmartFake implements _i5.ICryptoUtils { + _FakeICryptoUtils_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIRelayAuth_6 extends _i1.SmartFake implements _i6.IRelayAuth { + _FakeIRelayAuth_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIStore_7 extends _i1.SmartFake implements _i7.IStore { + _FakeIStore_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEvent_8 extends _i1.SmartFake + implements _i8.Event { + _FakeEvent_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponse_9 extends _i1.SmartFake implements _i9.Response { + _FakeResponse_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeICrypto_10 extends _i1.SmartFake implements _i10.ICrypto { + _FakeICrypto_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIRelayClient_11 extends _i1.SmartFake implements _i11.IRelayClient { + _FakeIRelayClient_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIExpirer_12 extends _i1.SmartFake implements _i12.IExpirer { + _FakeIExpirer_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIPairing_13 extends _i1.SmartFake implements _i13.IPairing { + _FakeIPairing_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIEcho_14 extends _i1.SmartFake implements _i14.IEcho { + _FakeIEcho_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIHeartBeat_15 extends _i1.SmartFake implements _i15.IHeartBeat { + _FakeIHeartBeat_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIVerify_16 extends _i1.SmartFake implements _i16.IVerify { + _FakeIVerify_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIConnectivity_17 extends _i1.SmartFake + implements _i17.IConnectivity { + _FakeIConnectivity_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeILinkModeStore_18 extends _i1.SmartFake + implements _i18.ILinkModeStore { + _FakeILinkModeStore_18( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLogger_19 extends _i1.SmartFake implements _i19.Logger { + _FakeLogger_19( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [CryptoUtils]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCryptoUtils extends _i1.Mock implements _i20.CryptoUtils { + MockCryptoUtils() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.CryptoKeyPair generateKeyPair() => (super.noSuchMethod( + Invocation.method( + #generateKeyPair, + [], + ), + returnValue: _FakeCryptoKeyPair_0( + this, + Invocation.method( + #generateKeyPair, + [], + ), + ), + ) as _i2.CryptoKeyPair); + + @override + _i21.Uint8List randomBytes(int? length) => (super.noSuchMethod( + Invocation.method( + #randomBytes, + [length], + ), + returnValue: _i21.Uint8List(0), + ) as _i21.Uint8List); + + @override + String generateRandomBytes32() => (super.noSuchMethod( + Invocation.method( + #generateRandomBytes32, + [], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #generateRandomBytes32, + [], + ), + ), + ) as String); + + @override + _i23.Future deriveSymKey( + String? privKeyA, + String? pubKeyB, + ) => + (super.noSuchMethod( + Invocation.method( + #deriveSymKey, + [ + privKeyA, + pubKeyB, + ], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #deriveSymKey, + [ + privKeyA, + pubKeyB, + ], + ), + )), + ) as _i23.Future); + + @override + String hashKey(String? key) => (super.noSuchMethod( + Invocation.method( + #hashKey, + [key], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashKey, + [key], + ), + ), + ) as String); + + @override + String hashMessage(String? message) => (super.noSuchMethod( + Invocation.method( + #hashMessage, + [message], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashMessage, + [message], + ), + ), + ) as String); + + @override + _i23.Future encrypt( + String? message, + String? symKey, { + int? type, + String? iv, + String? senderPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #encrypt, + [ + message, + symKey, + ], + { + #type: type, + #iv: iv, + #senderPublicKey: senderPublicKey, + }, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #encrypt, + [ + message, + symKey, + ], + { + #type: type, + #iv: iv, + #senderPublicKey: senderPublicKey, + }, + ), + )), + ) as _i23.Future); + + @override + _i23.Future decrypt( + String? symKey, + String? encoded, + ) => + (super.noSuchMethod( + Invocation.method( + #decrypt, + [ + symKey, + encoded, + ], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #decrypt, + [ + symKey, + encoded, + ], + ), + )), + ) as _i23.Future); + + @override + String serialize( + int? type, + _i21.Uint8List? sealed, + _i21.Uint8List? iv, { + _i21.Uint8List? senderPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #serialize, + [ + type, + sealed, + iv, + ], + {#senderPublicKey: senderPublicKey}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #serialize, + [ + type, + sealed, + iv, + ], + {#senderPublicKey: senderPublicKey}, + ), + ), + ) as String); + + @override + _i2.EncodingParams deserialize(String? encoded) => (super.noSuchMethod( + Invocation.method( + #deserialize, + [encoded], + ), + returnValue: _FakeEncodingParams_1( + this, + Invocation.method( + #deserialize, + [encoded], + ), + ), + ) as _i2.EncodingParams); + + @override + _i2.EncodingValidation validateDecoding( + String? encoded, { + String? receiverPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #validateDecoding, + [encoded], + {#receiverPublicKey: receiverPublicKey}, + ), + returnValue: _FakeEncodingValidation_2( + this, + Invocation.method( + #validateDecoding, + [encoded], + {#receiverPublicKey: receiverPublicKey}, + ), + ), + ) as _i2.EncodingValidation); + + @override + _i2.EncodingValidation validateEncoding({ + int? type, + String? senderPublicKey, + String? receiverPublicKey, + }) => + (super.noSuchMethod( + Invocation.method( + #validateEncoding, + [], + { + #type: type, + #senderPublicKey: senderPublicKey, + #receiverPublicKey: receiverPublicKey, + }, + ), + returnValue: _FakeEncodingValidation_2( + this, + Invocation.method( + #validateEncoding, + [], + { + #type: type, + #senderPublicKey: senderPublicKey, + #receiverPublicKey: receiverPublicKey, + }, + ), + ), + ) as _i2.EncodingValidation); + + @override + bool isTypeOneEnvelope(_i2.EncodingValidation? result) => (super.noSuchMethod( + Invocation.method( + #isTypeOneEnvelope, + [result], + ), + returnValue: false, + ) as bool); + + @override + bool isTypeTwoEnvelope(_i2.EncodingValidation? result) => (super.noSuchMethod( + Invocation.method( + #isTypeTwoEnvelope, + [result], + ), + returnValue: false, + ) as bool); + + @override + _i21.Uint8List encodeTypeByte(int? type) => (super.noSuchMethod( + Invocation.method( + #encodeTypeByte, + [type], + ), + returnValue: _i21.Uint8List(0), + ) as _i21.Uint8List); + + @override + int decodeTypeByte(_i21.Uint8List? byte) => (super.noSuchMethod( + Invocation.method( + #decodeTypeByte, + [byte], + ), + returnValue: 0, + ) as int); + + @override + String encodeTypeTwoEnvelope({required String? message}) => + (super.noSuchMethod( + Invocation.method( + #encodeTypeTwoEnvelope, + [], + {#message: message}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #encodeTypeTwoEnvelope, + [], + {#message: message}, + ), + ), + ) as String); + + @override + String decodeTypeTwoEnvelope({required String? message}) => + (super.noSuchMethod( + Invocation.method( + #decodeTypeTwoEnvelope, + [], + {#message: message}, + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #decodeTypeTwoEnvelope, + [], + {#message: message}, + ), + ), + ) as String); +} + +/// A class which mocks [Crypto]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCrypto extends _i1.Mock implements _i24.Crypto { + MockCrypto() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.IReownCore get core => (super.noSuchMethod( + Invocation.getter(#core), + returnValue: _FakeIReownCore_3( + this, + Invocation.getter(#core), + ), + ) as _i3.IReownCore); + + @override + _i4.IGenericStore get keyChain => (super.noSuchMethod( + Invocation.getter(#keyChain), + returnValue: _FakeIGenericStore_4( + this, + Invocation.getter(#keyChain), + ), + ) as _i4.IGenericStore); + + @override + set keyChain(_i4.IGenericStore? _keyChain) => super.noSuchMethod( + Invocation.setter( + #keyChain, + _keyChain, + ), + returnValueForMissingStub: null, + ); + + @override + _i5.ICryptoUtils get utils => (super.noSuchMethod( + Invocation.getter(#utils), + returnValue: _FakeICryptoUtils_5( + this, + Invocation.getter(#utils), + ), + ) as _i5.ICryptoUtils); + + @override + set utils(_i5.ICryptoUtils? _utils) => super.noSuchMethod( + Invocation.setter( + #utils, + _utils, + ), + returnValueForMissingStub: null, + ); + + @override + _i6.IRelayAuth get relayAuth => (super.noSuchMethod( + Invocation.getter(#relayAuth), + returnValue: _FakeIRelayAuth_6( + this, + Invocation.getter(#relayAuth), + ), + ) as _i6.IRelayAuth); + + @override + set relayAuth(_i6.IRelayAuth? _relayAuth) => super.noSuchMethod( + Invocation.setter( + #relayAuth, + _relayAuth, + ), + returnValueForMissingStub: null, + ); + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + _i23.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool hasKeys(String? tag) => (super.noSuchMethod( + Invocation.method( + #hasKeys, + [tag], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future getClientId() => (super.noSuchMethod( + Invocation.method( + #getClientId, + [], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #getClientId, + [], + ), + )), + ) as _i23.Future); + + @override + _i23.Future generateKeyPair() => (super.noSuchMethod( + Invocation.method( + #generateKeyPair, + [], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #generateKeyPair, + [], + ), + )), + ) as _i23.Future); + + @override + _i23.Future generateSharedKey( + String? selfPublicKey, + String? peerPublicKey, { + String? overrideTopic, + }) => + (super.noSuchMethod( + Invocation.method( + #generateSharedKey, + [ + selfPublicKey, + peerPublicKey, + ], + {#overrideTopic: overrideTopic}, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #generateSharedKey, + [ + selfPublicKey, + peerPublicKey, + ], + {#overrideTopic: overrideTopic}, + ), + )), + ) as _i23.Future); + + @override + _i23.Future setSymKey( + String? symKey, { + String? overrideTopic, + }) => + (super.noSuchMethod( + Invocation.method( + #setSymKey, + [symKey], + {#overrideTopic: overrideTopic}, + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #setSymKey, + [symKey], + {#overrideTopic: overrideTopic}, + ), + )), + ) as _i23.Future); + + @override + _i23.Future deleteKeyPair(String? publicKey) => (super.noSuchMethod( + Invocation.method( + #deleteKeyPair, + [publicKey], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future deleteSymKey(String? topic) => (super.noSuchMethod( + Invocation.method( + #deleteSymKey, + [topic], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future encode( + String? topic, + Map? payload, { + _i2.EncodeOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #encode, + [ + topic, + payload, + ], + {#options: options}, + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future decode( + String? topic, + String? encoded, { + _i2.DecodeOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #decode, + [ + topic, + encoded, + ], + {#options: options}, + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future signJWT(String? aud) => (super.noSuchMethod( + Invocation.method( + #signJWT, + [aud], + ), + returnValue: _i23.Future.value(_i22.dummyValue( + this, + Invocation.method( + #signJWT, + [aud], + ), + )), + ) as _i23.Future); + + @override + int getPayloadType(String? encoded) => (super.noSuchMethod( + Invocation.method( + #getPayloadType, + [encoded], + ), + returnValue: 0, + ) as int); + + @override + String? getPayloadSenderPublicKey(String? encoded) => + (super.noSuchMethod(Invocation.method( + #getPayloadSenderPublicKey, + [encoded], + )) as String?); + + @override + _i5.ICryptoUtils getUtils() => (super.noSuchMethod( + Invocation.method( + #getUtils, + [], + ), + returnValue: _FakeICryptoUtils_5( + this, + Invocation.method( + #getUtils, + [], + ), + ), + ) as _i5.ICryptoUtils); +} + +/// A class which mocks [MessageTracker]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMessageTracker extends _i1.Mock implements _i25.MessageTracker { + MockMessageTracker() { + _i1.throwOnMissingStub(this); + } + + @override + String get context => (super.noSuchMethod( + Invocation.getter(#context), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#context), + ), + ) as String); + + @override + String get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#version), + ), + ) as String); + + @override + _i7.IStore get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeIStore_7( + this, + Invocation.getter(#storage), + ), + ) as _i7.IStore); + + @override + _i8.Event<_i26.StoreCreateEvent>> get onCreate => + (super.noSuchMethod( + Invocation.getter(#onCreate), + returnValue: _FakeEvent_8<_i26.StoreCreateEvent>>( + this, + Invocation.getter(#onCreate), + ), + ) as _i8.Event<_i26.StoreCreateEvent>>); + + @override + _i8.Event<_i26.StoreUpdateEvent>> get onUpdate => + (super.noSuchMethod( + Invocation.getter(#onUpdate), + returnValue: _FakeEvent_8<_i26.StoreUpdateEvent>>( + this, + Invocation.getter(#onUpdate), + ), + ) as _i8.Event<_i26.StoreUpdateEvent>>); + + @override + _i8.Event<_i26.StoreDeleteEvent>> get onDelete => + (super.noSuchMethod( + Invocation.getter(#onDelete), + returnValue: _FakeEvent_8<_i26.StoreDeleteEvent>>( + this, + Invocation.getter(#onDelete), + ), + ) as _i8.Event<_i26.StoreDeleteEvent>>); + + @override + _i8.Event<_i26.StoreSyncEvent> get onSync => (super.noSuchMethod( + Invocation.getter(#onSync), + returnValue: _FakeEvent_8<_i26.StoreSyncEvent>( + this, + Invocation.getter(#onSync), + ), + ) as _i8.Event<_i26.StoreSyncEvent>); + + @override + Map> get data => (super.noSuchMethod( + Invocation.getter(#data), + returnValue: >{}, + ) as Map>); + + @override + set data(Map>? _data) => super.noSuchMethod( + Invocation.setter( + #data, + _data, + ), + returnValueForMissingStub: null, + ); + + @override + Map Function(dynamic) get fromJson => (super.noSuchMethod( + Invocation.getter(#fromJson), + returnValue: (dynamic __p0) => {}, + ) as Map Function(dynamic)); + + @override + String get storageKey => (super.noSuchMethod( + Invocation.getter(#storageKey), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#storageKey), + ), + ) as String); + + @override + String hashMessage(String? message) => (super.noSuchMethod( + Invocation.method( + #hashMessage, + [message], + ), + returnValue: _i22.dummyValue( + this, + Invocation.method( + #hashMessage, + [message], + ), + ), + ) as String); + + @override + _i23.Future recordMessageEvent( + String? topic, + String? message, + ) => + (super.noSuchMethod( + Invocation.method( + #recordMessageEvent, + [ + topic, + message, + ], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool messageIsRecorded( + String? topic, + String? message, + ) => + (super.noSuchMethod( + Invocation.method( + #messageIsRecorded, + [ + topic, + message, + ], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + bool has(String? key) => (super.noSuchMethod( + Invocation.method( + #has, + [key], + ), + returnValue: false, + ) as bool); + + @override + Map? get(String? key) => + (super.noSuchMethod(Invocation.method( + #get, + [key], + )) as Map?); + + @override + List> getAll() => (super.noSuchMethod( + Invocation.method( + #getAll, + [], + ), + returnValue: >[], + ) as List>); + + @override + _i23.Future set( + String? key, + Map? value, + ) => + (super.noSuchMethod( + Invocation.method( + #set, + [ + key, + value, + ], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future delete(String? key) => (super.noSuchMethod( + Invocation.method( + #delete, + [key], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future persist() => (super.noSuchMethod( + Invocation.method( + #persist, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future restore() => (super.noSuchMethod( + Invocation.method( + #restore, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + void checkInitialized() => super.noSuchMethod( + Invocation.method( + #checkInitialized, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [HttpWrapper]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHttpWrapper extends _i1.Mock implements _i27.HttpWrapper { + MockHttpWrapper() { + _i1.throwOnMissingStub(this); + } + + @override + _i23.Future<_i9.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i23.Future<_i9.Response>); + + @override + _i23.Future<_i9.Response> delete( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + {#headers: headers}, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #delete, + [url], + {#headers: headers}, + ), + )), + ) as _i23.Future<_i9.Response>); + + @override + _i23.Future<_i9.Response> post( + Uri? url, { + Map? headers, + Object? body, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + }, + ), + returnValue: _i23.Future<_i9.Response>.value(_FakeResponse_9( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + }, + ), + )), + ) as _i23.Future<_i9.Response>); +} + +/// A class which mocks [ReownCore]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReownCore extends _i1.Mock implements _i28.ReownCore { + MockReownCore() { + _i1.throwOnMissingStub(this); + } + + @override + String get projectId => (super.noSuchMethod( + Invocation.getter(#projectId), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#projectId), + ), + ) as String); + + @override + String get relayUrl => (super.noSuchMethod( + Invocation.getter(#relayUrl), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#relayUrl), + ), + ) as String); + + @override + set relayUrl(String? _relayUrl) => super.noSuchMethod( + Invocation.setter( + #relayUrl, + _relayUrl, + ), + returnValueForMissingStub: null, + ); + + @override + String get pushUrl => (super.noSuchMethod( + Invocation.getter(#pushUrl), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#pushUrl), + ), + ) as String); + + @override + set pushUrl(String? _pushUrl) => super.noSuchMethod( + Invocation.setter( + #pushUrl, + _pushUrl, + ), + returnValueForMissingStub: null, + ); + + @override + _i10.ICrypto get crypto => (super.noSuchMethod( + Invocation.getter(#crypto), + returnValue: _FakeICrypto_10( + this, + Invocation.getter(#crypto), + ), + ) as _i10.ICrypto); + + @override + set crypto(_i10.ICrypto? _crypto) => super.noSuchMethod( + Invocation.setter( + #crypto, + _crypto, + ), + returnValueForMissingStub: null, + ); + + @override + _i11.IRelayClient get relayClient => (super.noSuchMethod( + Invocation.getter(#relayClient), + returnValue: _FakeIRelayClient_11( + this, + Invocation.getter(#relayClient), + ), + ) as _i11.IRelayClient); + + @override + set relayClient(_i11.IRelayClient? _relayClient) => super.noSuchMethod( + Invocation.setter( + #relayClient, + _relayClient, + ), + returnValueForMissingStub: null, + ); + + @override + _i12.IExpirer get expirer => (super.noSuchMethod( + Invocation.getter(#expirer), + returnValue: _FakeIExpirer_12( + this, + Invocation.getter(#expirer), + ), + ) as _i12.IExpirer); + + @override + set expirer(_i12.IExpirer? _expirer) => super.noSuchMethod( + Invocation.setter( + #expirer, + _expirer, + ), + returnValueForMissingStub: null, + ); + + @override + _i13.IPairing get pairing => (super.noSuchMethod( + Invocation.getter(#pairing), + returnValue: _FakeIPairing_13( + this, + Invocation.getter(#pairing), + ), + ) as _i13.IPairing); + + @override + set pairing(_i13.IPairing? _pairing) => super.noSuchMethod( + Invocation.setter( + #pairing, + _pairing, + ), + returnValueForMissingStub: null, + ); + + @override + _i14.IEcho get echo => (super.noSuchMethod( + Invocation.getter(#echo), + returnValue: _FakeIEcho_14( + this, + Invocation.getter(#echo), + ), + ) as _i14.IEcho); + + @override + set echo(_i14.IEcho? _echo) => super.noSuchMethod( + Invocation.setter( + #echo, + _echo, + ), + returnValueForMissingStub: null, + ); + + @override + _i15.IHeartBeat get heartbeat => (super.noSuchMethod( + Invocation.getter(#heartbeat), + returnValue: _FakeIHeartBeat_15( + this, + Invocation.getter(#heartbeat), + ), + ) as _i15.IHeartBeat); + + @override + set heartbeat(_i15.IHeartBeat? _heartbeat) => super.noSuchMethod( + Invocation.setter( + #heartbeat, + _heartbeat, + ), + returnValueForMissingStub: null, + ); + + @override + _i16.IVerify get verify => (super.noSuchMethod( + Invocation.getter(#verify), + returnValue: _FakeIVerify_16( + this, + Invocation.getter(#verify), + ), + ) as _i16.IVerify); + + @override + set verify(_i16.IVerify? _verify) => super.noSuchMethod( + Invocation.setter( + #verify, + _verify, + ), + returnValueForMissingStub: null, + ); + + @override + _i17.IConnectivity get connectivity => (super.noSuchMethod( + Invocation.getter(#connectivity), + returnValue: _FakeIConnectivity_17( + this, + Invocation.getter(#connectivity), + ), + ) as _i17.IConnectivity); + + @override + set connectivity(_i17.IConnectivity? _connectivity) => super.noSuchMethod( + Invocation.setter( + #connectivity, + _connectivity, + ), + returnValueForMissingStub: null, + ); + + @override + _i18.ILinkModeStore get linkModeStore => (super.noSuchMethod( + Invocation.getter(#linkModeStore), + returnValue: _FakeILinkModeStore_18( + this, + Invocation.getter(#linkModeStore), + ), + ) as _i18.ILinkModeStore); + + @override + set linkModeStore(_i18.ILinkModeStore? _linkModeStore) => super.noSuchMethod( + Invocation.setter( + #linkModeStore, + _linkModeStore, + ), + returnValueForMissingStub: null, + ); + + @override + _i7.IStore> get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeIStore_7>( + this, + Invocation.getter(#storage), + ), + ) as _i7.IStore>); + + @override + set storage(_i7.IStore>? _storage) => super.noSuchMethod( + Invocation.setter( + #storage, + _storage, + ), + returnValueForMissingStub: null, + ); + + @override + String get protocol => (super.noSuchMethod( + Invocation.getter(#protocol), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#protocol), + ), + ) as String); + + @override + String get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: _i22.dummyValue( + this, + Invocation.getter(#version), + ), + ) as String); + + @override + _i19.Logger get logger => (super.noSuchMethod( + Invocation.getter(#logger), + returnValue: _FakeLogger_19( + this, + Invocation.getter(#logger), + ), + ) as _i19.Logger); + + @override + void addLogListener(_i19.LogCallback? callback) => super.noSuchMethod( + Invocation.method( + #addLogListener, + [callback], + ), + returnValueForMissingStub: null, + ); + + @override + bool removeLogListener(_i19.LogCallback? callback) => (super.noSuchMethod( + Invocation.method( + #removeLogListener, + [callback], + ), + returnValue: false, + ) as bool); + + @override + _i23.Future start() => (super.noSuchMethod( + Invocation.method( + #start, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future addLinkModeSupportedApp(String? universalLink) => + (super.noSuchMethod( + Invocation.method( + #addLinkModeSupportedApp, + [universalLink], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + List getLinkModeSupportedApps() => (super.noSuchMethod( + Invocation.method( + #getLinkModeSupportedApps, + [], + ), + returnValue: [], + ) as List); + + @override + void confirmOnlineStateOrThrow() => super.noSuchMethod( + Invocation.method( + #confirmOnlineStateOrThrow, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [WebSocketHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSocketHandler extends _i1.Mock implements _i29.WebSocketHandler { + MockWebSocketHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i23.Future get ready => (super.noSuchMethod( + Invocation.getter(#ready), + returnValue: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future setup({required String? url}) => (super.noSuchMethod( + Invocation.method( + #setup, + [], + {#url: url}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future connect() => (super.noSuchMethod( + Invocation.method( + #connect, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + + @override + _i23.Future close() => (super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); +} diff --git a/packages/reown_walletkit/test/shared/shared_test_values.dart b/packages/reown_walletkit/test/shared/shared_test_values.dart new file mode 100644 index 0000000..59edf2d --- /dev/null +++ b/packages/reown_walletkit/test/shared/shared_test_values.dart @@ -0,0 +1,236 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +const TEST_RELAY_URL = String.fromEnvironment( + 'RELAY_ENDPOINT', + defaultValue: 'wss://relay.walletconnect.org', +); +const TEST_PROJECT_ID = String.fromEnvironment( + 'PROJECT_ID', + defaultValue: 'cad4956f31a5e40a00b62865b030c6f8', +); + +const PROPOSER = PairingMetadata( + name: 'App A (Proposer, dapp)', + description: 'Description of Proposer App run by client A', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], +); +const RESPONDER = PairingMetadata( + name: 'App B (Responder, Wallet)', + description: 'Description of Proposer App run by client B', + url: 'https://reown.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], +); + +const TEST_PAIRING_TOPIC = ''; +const TEST_SESSION_TOPIC = ''; +const TEST_KEY_PAIRS = { + 'A': CryptoKeyPair( + '1fb63fca5c6ac731246f2f069d3bc2454345d5208254aa8ea7bffc6d110c8862', + 'ff7a7d5767c362b0a17ad92299ebdb7831dcbd9a56959c01368c7404543b3342', + ), + 'B': CryptoKeyPair( + '36bf507903537de91f5e573666eaa69b1fa313974f23b2b59645f20fea505854', + '590c2c627be7af08597091ff80dd41f7fa28acd10ef7191d7e830e116d3a186a', + ), +}; + +const TEST_SHARED_KEY = + '9c87e48e69b33a613907515bcd5b1b4cc10bbaf15167b19804b00f0a9217e607'; +const TEST_HASHED_KEY = + 'a492906ccc809a411bb53a84572b57329375378c6ad7566f3e1c688200123e77'; +const TEST_SYM_KEY = + '0653ca620c7b4990392e1c53c4a51c14a2840cd20f0f1524cf435b17b6fe988c'; + +const TEST_URI = + 'wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&relay-protocol=irn'; +const TEST_URI_V1 = + 'wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@1?key=abc&bridge=xyz'; + +const TEST_ETHEREUM_CHAIN = 'eip155:1'; + +final Set availableAccounts = { + 'namespace1:chain1:address1', + 'namespace1:chain1:address2', + 'namespace2:chain1:address3', + 'namespace2:chain1:address4', + 'namespace2:chain2:address5', + 'namespace4:chain1:address6', +}; + +final Set availableMethods = { + 'namespace1:chain1:method1', + 'namespace1:chain1:method2', + 'namespace2:chain1:method3', + 'namespace2:chain1:method4', + 'namespace2:chain2:method3', + 'namespace4:chain1:method5', +}; + +final Set availableEvents = { + 'namespace1:chain1:event1', + 'namespace1:chain1:event2', + 'namespace2:chain1:event3', + 'namespace2:chain1:event4', + 'namespace2:chain2:event3', + 'namespace4:chain1:event5', +}; + +final Map requiredNamespacesInAvailable = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1'], + events: ['event1'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesInAvailable2 = { + 'namespace1': const RequiredNamespace( + methods: ['method1'], + events: ['event1'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesMatchingAvailable1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1'], + methods: ['method3', 'method4'], + events: ['event3', 'event4'], + ), +}; + +final Map requiredNamespacesNonconformingAccounts1 = + { + 'namespace3': const RequiredNamespace( + chains: ['namespace3:chain1'], + methods: [], + events: [], + ), +}; + +final Map requiredNamespacesNonconformingMethods1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2', 'method3'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingMethods2 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2', 'method3'], + events: ['event1', 'event2'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3', 'method4'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingEvents1 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2', 'event3'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3'], + ), +}; + +final Map requiredNamespacesNonconformingEvents2 = { + 'namespace1:chain1': const RequiredNamespace( + methods: ['method1', 'method2'], + events: ['event1', 'event2', 'event3'], + ), + 'namespace2': const RequiredNamespace( + chains: ['namespace2:chain1', 'namespace2:chain2'], + methods: ['method3'], + events: ['event3', 'event4'], + ), +}; + +Map optionalNamespaces = { + 'namespace4:chain1': const RequiredNamespace( + methods: ['method5'], + events: ['event5', 'event2'], + ), +}; + +const sepolia = 'eip155:11155111'; + +final Set availableAccounts3 = { + '$sepolia:0x99999999999999999999999999', +}; + +final Set availableMethods3 = { + '$sepolia:eth_sendTransaction', + '$sepolia:personal_sign', + '$sepolia:eth_signTypedData', + '$sepolia:eth_signTypedData_v4', + '$sepolia:eth_sign', +}; + +final Set availableEvents3 = { + '$sepolia:chainChanged', + '$sepolia:accountsChanged', +}; + +final Map requiredNamespacesInAvailable3 = { + 'eip155': const RequiredNamespace( + chains: [sepolia], + methods: ['eth_sendTransaction', 'personal_sign'], + events: ['chainChanged', 'accountsChanged'], + ), +}; + +final Map optionalNamespacesInAvailable3 = { + 'eip155': const RequiredNamespace(chains: [ + 'eip155:1', + 'eip155:5', + sepolia, + 'eip155:137', + 'eip155:80001', + 'eip155:42220', + 'eip155:44787', + 'eip155:56', + 'eip155:43114', + 'eip155:42161', + 'eip155:421613', + 'eip155:10', + 'eip155:420', + 'eip155:8453' + ], methods: [ + 'eth_sendTransaction', + 'personal_sign', + 'eth_signTypedData', + 'eth_signTypedData_v4', + 'eth_sign' + ], events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ]), +}; diff --git a/packages/reown_walletkit/test/shared/sign_client_constants.dart b/packages/reown_walletkit/test/shared/sign_client_constants.dart new file mode 100644 index 0000000..6a7e658 --- /dev/null +++ b/packages/reown_walletkit/test/shared/sign_client_constants.dart @@ -0,0 +1,227 @@ +import 'package:reown_core/reown_core.dart'; +import 'package:reown_sign/reown_sign.dart'; + +import '../shared/shared_test_values.dart'; + +const TEST_RELAY_OPTIONS = { + 'protocol': ReownConstants.RELAYER_DEFAULT_PROTOCOL, +}; + +const EVM_NAMESPACE = 'eip155'; + +const TEST_ARBITRUM_CHAIN = 'eip155:42161'; +const TEST_AVALANCHE_CHAIN = 'eip155:43114'; +const TEST_UNINCLUDED_CHAIN = 'eip155:2'; + +const TEST_CHAINS = [ + TEST_ETHEREUM_CHAIN, + TEST_ARBITRUM_CHAIN, +]; +const TEST_CHAIN_INVALID_1 = 'swag'; +const TEST_CHAIN_INVALID_2 = 's:w:a'; +const TEST_CHAINS_INVALID = [ + TEST_CHAIN_INVALID_1, + TEST_CHAIN_INVALID_2, +]; + +const TEST_ETHEREUM_ADDRESS = '0x3c582121909DE92Dc89A36898633C1aE4790382b'; + +const TEST_ETHEREUM_ACCOUNT = '$TEST_ETHEREUM_CHAIN:$TEST_ETHEREUM_ADDRESS'; +const TEST_ARBITRUM_ACCOUNT = '$TEST_ARBITRUM_CHAIN:$TEST_ETHEREUM_ADDRESS'; +const TEST_AVALANCHE_ACCOUNT = '$TEST_AVALANCHE_CHAIN:$TEST_ETHEREUM_ADDRESS'; + +const TEST_ACCOUNTS = [ + TEST_ETHEREUM_ACCOUNT, + TEST_ARBITRUM_ACCOUNT, +]; +const TEST_ACCOUNT_INVALID_1 = 'swag'; +const TEST_ACCOUNT_INVALID_2 = 's:w'; +const TEST_ACCOUNT_INVALID_3 = 's:w:a:g'; +const TEST_ACCOUNTS_INVALID = [ + TEST_ACCOUNT_INVALID_1, + TEST_ACCOUNT_INVALID_2, + TEST_ACCOUNT_INVALID_3, +]; + +const TEST_METHOD_1 = 'eth_sendTransaction'; +const TEST_METHOD_2 = 'eth_signTransaction'; +const TEST_METHOD_3 = 'personal_sign'; +const TEST_METHOD_4 = 'eth_signTypedData'; +const TEST_METHODS_1 = [ + TEST_METHOD_1, + TEST_METHOD_2, +]; +const TEST_METHODS_2 = [ + TEST_METHOD_3, + TEST_METHOD_4, +]; +const TEST_METHODS_FULL = [ + ...TEST_METHODS_1, + ...TEST_METHODS_2, +]; +const TEST_METHOD_INVALID_1 = 'eth_invalid'; + +const TEST_EVENT_1 = 'chainChanged'; +const TEST_EVENT_2 = 'accountsChanged'; +const TEST_EVENTS_FULL = [ + TEST_EVENT_1, + TEST_EVENT_2, +]; +const TEST_EVENT_INVALID_1 = 'eth_event_invalid'; + +const TEST_ETH_ARB_REQUIRED_NAMESPACE = RequiredNamespace( + chains: TEST_CHAINS, + methods: TEST_METHODS_1, + events: [TEST_EVENT_1], +); +const TEST_AVA_REQUIRED_NAMESPACE = RequiredNamespace( + methods: TEST_METHODS_2, + events: [TEST_EVENT_2], +); +const TEST_REQUIRED_NAMESPACES = { + EVM_NAMESPACE: TEST_ETH_ARB_REQUIRED_NAMESPACE, + TEST_AVALANCHE_CHAIN: TEST_AVA_REQUIRED_NAMESPACE, +}; + +const TEST_ETH_ARB_NAMESPACE = Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_1, + events: [TEST_EVENT_1], +); +const TEST_AVA_NAMESPACE = Namespace( + accounts: [TEST_AVALANCHE_ACCOUNT], + methods: TEST_METHODS_2, + events: [TEST_EVENT_2], +); +const TEST_NAMESPACES = { + EVM_NAMESPACE: TEST_ETH_ARB_NAMESPACE, + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; + +// Invalid RequiredNamespaces +const TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_1 = { + 'eip155:2': TEST_ETH_ARB_REQUIRED_NAMESPACE, +}; +const TEST_REQUIRED_NAMESPACES_INVALID_CHAINS_2 = { + EVM_NAMESPACE: RequiredNamespace( + chains: ['eip155:1', TEST_CHAIN_INVALID_1], + methods: [], + events: [], + ), +}; + +// Invalid Namespaces +const TEST_NAMESPACES_INVALID_ACCOUNTS = { + EVM_NAMESPACE: Namespace( + accounts: [TEST_ACCOUNT_INVALID_1], + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), +}; + +// Invalid Conforming Namespaces +const TEST_NAMESPACES_NONCONFORMING_KEY_VALUE = 'eip1111'; +const TEST_NAMESPACES_NONCONFORMING_KEY_1 = { + TEST_NAMESPACES_NONCONFORMING_KEY_VALUE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ) +}; +const TEST_NAMESPACES_NONCONFORMING_KEY_2 = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), +}; +const TEST_NAMESPACES_NONCONFORMING_CHAINS = { + EVM_NAMESPACE: Namespace( + accounts: [TEST_ETHEREUM_ACCOUNT], + methods: TEST_METHODS_FULL, + events: TEST_EVENTS_FULL, + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; +const TEST_NAMESPACES_NONCONFORMING_METHODS = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: [TEST_METHOD_INVALID_1], + events: TEST_EVENTS_FULL, + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; +const TEST_NAMESPACES_NONCONFORMING_EVENTS = { + EVM_NAMESPACE: Namespace( + accounts: TEST_ACCOUNTS, + methods: TEST_METHODS_FULL, + events: [TEST_EVENT_INVALID_1], + ), + TEST_AVALANCHE_CHAIN: TEST_AVA_NAMESPACE, +}; + +// Session Data +const TEST_SESSION_INVALID_TOPIC = 'swagmaster'; +const TEST_SESSION_VALID_TOPIC = 'abc'; +const TEST_SESSION_EXPIRED_TOPIC = 'expired'; +final testSessionValid = SessionData( + topic: TEST_SESSION_VALID_TOPIC, + pairingTopic: TEST_PAIRING_TOPIC, + relay: Relay('irn'), + expiry: 1000000000000, + acknowledged: true, + controller: 'test', + namespaces: TEST_NAMESPACES, + requiredNamespaces: { + EVM_NAMESPACE: TEST_ETH_ARB_REQUIRED_NAMESPACE, + }, + optionalNamespaces: {}, + self: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), + peer: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), +); +final testSessionExpired = SessionData( + topic: TEST_SESSION_EXPIRED_TOPIC, + pairingTopic: TEST_PAIRING_TOPIC, + relay: Relay('irn'), + expiry: -1, + acknowledged: true, + controller: 'test', + namespaces: TEST_NAMESPACES, + requiredNamespaces: TEST_REQUIRED_NAMESPACES, + optionalNamespaces: {}, + self: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), + peer: const ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata(name: '', description: '', url: '', icons: []), + ), +); + +// Test Messages + +const TEST_MESSAGE_1 = {'test': 'Hello'}; +const TEST_MESSAGE_2 = 'Hello'; + +const TEST_MESSAGE = 'My name is John Doe'; +const TEST_SIGNATURE = + '0xc8906b32c9f74d0805226ffff5ecd6897ea55cdf58f54a53a2e5b5d5a21fb67f43ef1d4c2ed790a724a1549b4cc40137403048c4aed9825cfd5ba6c1d15bd0721c'; + +const TEST_SIGN_METHOD = 'personal_sign'; +const TEST_SIGN_PARAMS = [ + TEST_MESSAGE, + TEST_ETHEREUM_ADDRESS, +]; +const TEST_SIGN_REQUEST = { + 'method': TEST_SIGN_METHOD, + 'params': TEST_SIGN_PARAMS +}; + +const TEST_RANDOM_REQUEST = {'method': 'random_method', 'params': []}; diff --git a/packages/reown_walletkit/test/walletkit_helpers.dart b/packages/reown_walletkit/test/walletkit_helpers.dart new file mode 100644 index 0000000..01ea53c --- /dev/null +++ b/packages/reown_walletkit/test/walletkit_helpers.dart @@ -0,0 +1,262 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'shared/sign_client_constants.dart'; + +class TestConnectMethodReturn { + PairingInfo pairing; + SessionData session; + int connectLatency; + int settleLatency; + + TestConnectMethodReturn( + this.pairing, + this.session, + this.connectLatency, + this.settleLatency, + ); +} + +class ReownWalletKitHelpers { + static Future testWalletKit( + IReownAppKit a, + IReownWalletKit b, { + Map? namespaces, + Map? requiredNamespaces, + List? relays, + String? pairingTopic, + int? qrCodeScanLatencyMs, + bool testFailure = false, + }) async { + final start = DateTime.now().millisecondsSinceEpoch; + final Map reqNamespaces = + requiredNamespaces ?? TEST_REQUIRED_NAMESPACES; + + Map workingNamespaces = namespaces ?? TEST_NAMESPACES; + + SessionData? sessionA; + SessionData? sessionB; + // AuthResponse? authResponse; + + // Listen for a proposal via connect to avoid race conditions + Completer signCompleter = Completer(); + signHandler(SessionProposalEvent? args) async { + // print('B Session Proposal'); + + expect( + args!.params.requiredNamespaces, + reqNamespaces, + ); + + expect(b.getPendingSessionProposals().length, 1); + + ApproveResponse response = await b.approveSession( + id: args.id, + namespaces: workingNamespaces, + ); + sessionB = response.session; + signCompleter.complete(); + // if (sessionB == null) { + // print('session b was set to null'); + // } + } + + b.onSessionProposal.subscribe(signHandler); + + // // Listen for a auth request + // Completer authCompleter = Completer(); + // authHandler(AuthRequest? args) async { + // // print('B Auth Request'); + + // expect(b.getPendingAuthRequests().length, 1); + + // // Create the message to be signed + // String message = b.authEngine.formatAuthMessage( + // iss: TEST_ISSUER_EIP191, + // cacaoPayload: CacaoRequestPayload.fromPayloadParams( + // args!.payloadParams, + // ), + // ); + + // String sig = EthSigUtil.signPersonalMessage( + // message: Uint8List.fromList(message.codeUnits), + // privateKey: TEST_PRIVATE_KEY_EIP191, + // ); + + // await b.respondAuthRequest( + // id: args.id, + // iss: TEST_ISSUER_EIP191, + // signature: CacaoSignature(t: CacaoSignature.EIP191, s: sig), + // ); + // authCompleter.complete(); + // } + + // b.onAuthRequest.subscribe(authHandler); + + // Connect to client b from a, this will trigger the above event + // print('connecting'); + ConnectResponse connectResponse = await a.connect( + requiredNamespaces: reqNamespaces, + pairingTopic: pairingTopic, + relays: relays, + ); + Uri? uri = connectResponse.uri; + + // // Send an auth request as well + // // print('requesting auth'); + // AuthRequestResponse authReqResponse = await a.requestAuth( + // params: testAuthRequestParamsValid, + // pairingTopic: connectResponse.pairingTopic, + // ); + // expect(connectResponse.pairingTopic, authReqResponse.pairingTopic); + // expect(authReqResponse.uri, null); + + // Track latency + final clientAConnectLatencyMs = + DateTime.now().millisecondsSinceEpoch - start; + + // Track pairings from "QR Scans" + PairingInfo? pairingA; + PairingInfo? pairingB; + + if (pairingTopic == null) { + // Simulate qr code scan latency if we want + if (uri == null) { + throw Exception('uri is missing'); + } + if (qrCodeScanLatencyMs != null) { + await Future.delayed( + Duration( + milliseconds: qrCodeScanLatencyMs, + ), + ); + } + + final uriParams = ReownCoreUtils.parseUri(connectResponse.uri!); + pairingA = a.pairings.get(uriParams.topic); + expect(pairingA != null, true); + expect(pairingA!.topic, uriParams.topic); + expect(pairingA.relay.protocol, uriParams.v2Data!.relay.protocol); + + // If we recieved no pairing topic, then we want to create one + // e.g. we pair from b to a using the uri created from the connect + // params (The QR code). + const pairTimeoutMs = 15000; + final timeout = Timer(const Duration(milliseconds: pairTimeoutMs), () { + throw Exception('Pair timed out after $pairTimeoutMs ms'); + }); + // print('pairing B -> A'); + pairingB = await b.pair(uri: uri); + timeout.cancel(); + expect(pairingA.topic, pairingB.topic); + expect(pairingA.relay.protocol, pairingB.relay.protocol); + } else { + pairingA = a.pairings.get(pairingTopic); + pairingB = b.pairings.get(pairingTopic); + } + + if (pairingA == null) { + throw Exception('expect pairing A to be defined'); + } + + // Assign session now that we have paired + // print('Waiting for connect response'); + sessionA = await connectResponse.session.future; + // // print( + // // 'Waiting for auth response: ${authReqResponse.completer.isCompleted}'); + // authResponse = await authReqResponse.completer.future; + + final settlePairingLatencyMs = DateTime.now().millisecondsSinceEpoch - + start - + (qrCodeScanLatencyMs ?? 0); + + // print('here 1'); + await signCompleter.future; + // // print('here 2'); + // await authCompleter.future; + + // if (sessionA == null) throw Exception("expect session A to be defined"); + if (sessionB == null) throw Exception('expect session B to be defined'); + + expect(sessionA.topic, sessionB!.topic); + // relay + expect( + sessionA.relay.protocol, + TEST_RELAY_OPTIONS['protocol'], + ); + expect(sessionA.relay.protocol, sessionB!.relay.protocol); + // namespaces + expect(sessionA.namespaces, workingNamespaces); + expect(sessionA.namespaces, sessionB!.namespaces); + // expiry + expect((sessionA.expiry - sessionB!.expiry).abs() < 5, true); + // Check that there is an expiry + expect(a.core.expirer.has(sessionA.topic), true); + expect(b.core.expirer.has(sessionB!.topic), true); + // acknowledged + expect(sessionA.acknowledged, sessionB!.acknowledged); + // participants + expect(sessionA.self, sessionB!.peer); + expect(sessionA.peer, sessionB!.self); + // controller + + expect(sessionA.controller, sessionB!.controller); + expect(sessionA.controller, sessionA.peer.publicKey); + expect(sessionB!.controller, sessionB!.self.publicKey); + // metadata + expect(sessionA.self.metadata, sessionB!.peer.metadata); + expect(sessionB!.self.metadata, sessionA.peer.metadata); + + // if (authResponse == null) + // throw Exception("expect authResponse to be defined"); + + // expect(authResponse.result != null, true); + // expect(authResponse.error == null, true); + // expect(authResponse.jsonRpcError == null, true); + + // if (pairingA == null) throw Exception("expect pairing A to be defined"); + if (pairingB == null) throw Exception('expect pairing B to be defined'); + + // update pairing state beforehand + pairingA = a.pairings.get(pairingA.topic); + pairingB = b.pairings.get(pairingB.topic); + + // topic + expect(pairingA!.topic, pairingB!.topic); + // relay + expect( + pairingA.relay.protocol, + TEST_RELAY_OPTIONS['protocol'], + ); + expect( + pairingB.relay.protocol, + TEST_RELAY_OPTIONS['protocol'], + ); + // active + expect(pairingA.active, true); + expect(pairingB.active, true); + // metadata + expect( + pairingA.peerMetadata, + sessionA.peer.metadata, + ); + expect( + pairingB.peerMetadata, + sessionB!.peer.metadata, + ); + + b.onSessionProposal.unsubscribe(signHandler); + // b.onAuthRequest.unsubscribe(authHandler); + + return TestConnectMethodReturn( + pairingA, + sessionA, + clientAConnectLatencyMs, + settlePairingLatencyMs, + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..cf96cd2 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,17 @@ +name: reown_flutter +description: "The communications protocol for web3" +version: 1.0.0 +homepage: https://github.com/reown-com/reown_flutter +repository: https://github.com/reown-com/reown_flutter + +environment: + sdk: '>=3.3.3 <4.0.0' + flutter: ">=1.17.0" + +# dependencies: +# flutter: +# sdk: flutter + +# dev_dependencies: +# flutter_test: +# sdk: flutter \ No newline at end of file