diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d6e688a..177c7d4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,9 +8,11 @@ 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 '....' @@ -18,24 +20,32 @@ Steps to reproduce the behavior: 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. + +If applicable, add a minimum reproducible code snippet. +A minimum reproducible code should have only Reown's SDK as dependency (besides flutter sdk of course) +There is no point you copy/paste your code if we can't run it as it is on our side. **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/workflows/release_dapp_android.yml b/.github/workflows/release_dapp_android.yml index 46fdccb..32762aa 100644 --- a/.github/workflows/release_dapp_android.yml +++ b/.github/workflows/release_dapp_android.yml @@ -6,6 +6,11 @@ on: types: - closed +# inputs: +# working-directory +# flavor +# BUNDLE_ID + jobs: build: if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master') || github.event_name == 'workflow_dispatch' diff --git a/.github/workflows/release_dapp_android_internal.yml b/.github/workflows/release_dapp_android_internal.yml index e3ade37..a3d80d0 100644 --- a/.github/workflows/release_dapp_android_internal.yml +++ b/.github/workflows/release_dapp_android_internal.yml @@ -6,6 +6,11 @@ on: types: - closed +# inputs: +# working-directory +# flavor +# BUNDLE_ID + jobs: build: if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop') || github.event_name == 'workflow_dispatch' diff --git a/.github/workflows/release_modal_android.yml b/.github/workflows/release_modal_android.yml index 908d53c..53783c0 100644 --- a/.github/workflows/release_modal_android.yml +++ b/.github/workflows/release_modal_android.yml @@ -1,4 +1,4 @@ -name: Build Android App Release +name: Android Modal (production) deploy on: workflow_dispatch: @@ -11,12 +11,14 @@ on: jobs: build_with_signing: name: Build Android App Release - runs-on: macos-latest + runs-on: macos-latest-xlarge steps: # Checkout the repo - name: Checkout repository uses: actions/checkout@v4 + + # Create temp firebase key - name: Create temp firebase key env: FIREBASE_KEY_BASE64: ${{ secrets.FIREBASE_KEY_BASE64 }} @@ -26,6 +28,7 @@ jobs: # 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 @@ -34,6 +37,8 @@ jobs: java-version: '17' architecture: x86_64 cache: 'gradle' + + # Cache Gradle - name: Cache Gradle uses: actions/cache@v3 with: @@ -43,25 +48,15 @@ jobs: 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 + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies 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 + working-directory: packages/reown_appkit/example/modal + # Build Android example app - name: Build Android APK - working-directory: example + working-directory: packages/reown_appkit/example/modal env: PROJECT_ID: ${{ secrets.PROJECT_ID }} APPKIT_AUTH: ${{ secrets.APPKIT_AUTH }} @@ -69,10 +64,10 @@ jobs: AUTH_SERVICE_URL: ${{ secrets.AUTH_SERVICE_URL }} run: | # Get app version from file + # VERSION_FILE=$GITHUB_WORKSPACE/lib/version.dart + # VERSION=`echo $(cat $VERSION_FILE) | sed "s/[^']*'\([^']*\)'.*/\1/"` + VERSION=4.0.0 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 @@ -89,18 +84,23 @@ jobs: # 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 }} + + # Upload APK - name: Upload APK - working-directory: example/build/app/outputs/flutter-apk + working-directory: packages/reown_appkit/example/modal/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" + firebase appdistribution:distribute app-stable-release.apk --app $APP_ID --release-notes "AppKit Flutter sample production" --groups "flutter-team, javascript-team, kotlin-team" + + # Notify Channel - name: Notify Channel uses: slackapi/slack-github-action@v1.24.0 env: @@ -109,7 +109,7 @@ jobs: 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" + "text":"🤖 AppKit *Modal* Flutter Sample *${{ github.ref_name }}* was just deployed. Test at https://appdistribution.firebase.dev/i/a8efff56e3f0fdb0" } # Clean up Flutter envs @@ -119,4 +119,8 @@ jobs: rm $RUNNER_TEMP/flutter-c7c2c-6df892fe6ddb.json flutter clean cd example - flutter clean \ No newline at end of file + flutter clean + +# Launch locally +# Needs docker to be running +# act -j build_with_signing --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret.modal -W .github/workflows/release_modal_android.yml \ No newline at end of file diff --git a/.github/workflows/release_modal_android_internal.yml b/.github/workflows/release_modal_android_internal.yml index 29bb0c7..86fdbc5 100644 --- a/.github/workflows/release_modal_android_internal.yml +++ b/.github/workflows/release_modal_android_internal.yml @@ -1,4 +1,4 @@ -name: Build Android App Internal (beta) +name: Android Modal (internal) deploy on: workflow_dispatch: @@ -11,12 +11,14 @@ on: jobs: build_with_signing: name: Build Android App Internal (beta) - runs-on: macos-latest + runs-on: macos-latest-xlarge steps: # Checkout the repo - name: Checkout repository uses: actions/checkout@v4 + + # Create temp firebase key - name: Create temp firebase key env: FIREBASE_KEY_BASE64: ${{ secrets.FIREBASE_KEY_BASE64 }} @@ -26,6 +28,7 @@ jobs: # 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 @@ -34,6 +37,8 @@ jobs: java-version: '17' architecture: x86_64 cache: 'gradle' + + # Cache Gradle - name: Cache Gradle uses: actions/cache@v3 with: @@ -43,25 +48,15 @@ jobs: 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 + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies 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 + working-directory: packages/reown_appkit/example/modal + # Build Android example app - name: Build Android APK - working-directory: example + working-directory: packages/reown_appkit/example/modal env: PROJECT_ID: ${{ secrets.PROJECT_ID }} APPKIT_AUTH: ${{ secrets.APPKIT_AUTH }} @@ -69,10 +64,10 @@ jobs: AUTH_SERVICE_URL: ${{ secrets.AUTH_SERVICE_URL }} run: | # Get app version from file + # VERSION_FILE=$GITHUB_WORKSPACE/lib/version.dart + # VERSION=`echo $(cat $VERSION_FILE) | sed "s/[^']*'\([^']*\)'.*/\1/"` + VERSION=4.0.0 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 @@ -89,18 +84,23 @@ jobs: # 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 }} + + # Upload APK - name: Upload APK - working-directory: example/build/app/outputs/flutter-apk + working-directory: packages/reown_appkit/example/modal/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" + firebase appdistribution:distribute app-beta-release.apk --app $APP_ID --release-notes "AppKit Flutter sample internal" --groups "flutter-team, javascript-team, kotlin-team" + + # Notify Channel - name: Notify Channel uses: slackapi/slack-github-action@v1.24.0 env: @@ -109,8 +109,9 @@ jobs: with: payload: |- { - "text":"🤖 New *Android* build *${{ github.ref_name }}* version for *Web3Modal Flutter* was just deployed. Test at https://appdistribution.firebase.dev/i/a47ee97e86fbdfff" + "text":"🤖 AppKit *Modal* Flutter Sample *Internal* *${{ github.ref_name }}* was just deployed. Test at https://appdistribution.firebase.dev/i/a47ee97e86fbdfff" } + # Clean up Flutter envs - name: Clean up if: ${{ always() }} @@ -118,4 +119,8 @@ jobs: rm $RUNNER_TEMP/flutter-c7c2c-6df892fe6ddb.json flutter clean cd example - flutter clean \ No newline at end of file + flutter clean + +# Launch locally +# Needs docker to be running +# act -j build_with_signing --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret.modal -W .github/workflows/release_modal_android_internal.yml \ No newline at end of file diff --git a/.github/workflows/release_modal_ios.yml b/.github/workflows/release_modal_ios.yml index c51f444..41a2447 100644 --- a/.github/workflows/release_modal_ios.yml +++ b/.github/workflows/release_modal_ios.yml @@ -1,4 +1,4 @@ -name: Build iOS App Release +name: iOS Modal (production) deploy on: workflow_dispatch: @@ -16,6 +16,7 @@ jobs: # 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: @@ -27,7 +28,7 @@ jobs: # create variables BUILD_CERT_PATH=$RUNNER_TEMP/build_certificate.p12 PP_PATH=$RUNNER_TEMP/FlutterAppStoreProfileWithPush.mobileprovision - KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + KEYCHAIN_PATH=$RUNNER_TEMP/release-modal.keychain-db # import certificate and provisioning profile from secrets echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $BUILD_CERT_PATH @@ -45,6 +46,7 @@ jobs: # 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: @@ -58,25 +60,15 @@ jobs: # 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 + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies 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 + working-directory: packages/reown_appkit/example/modal + # Build ios example app - name: Build ios example app - working-directory: example + working-directory: packages/reown_appkit/example/modal env: PROJECT_ID: ${{ secrets.PROJECT_ID }} APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} @@ -86,10 +78,10 @@ jobs: 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//-/ }) + # FILE_VALUE=$(echo | grep "^version: " pubspec.yaml) + # PARTS=(${FILE_VALUE//:/ }) + # FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=4.0.0 # 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 @@ -99,8 +91,9 @@ jobs: 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" + xcodebuild -workspace "$GITHUB_WORKSPACE/packages/reown_appkit/example/modal/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/packages/reown_appkit/example/modal/ios/Runner.xcarchive" -exportOptionsPlist "$GITHUB_WORKSPACE/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsInternal.plist" -exportPath "$GITHUB_WORKSPACE/packages/reown_appkit/example/modal/build/ios/ipa" -authenticationKeyIssuerID $APPLE_ISSUER_ID -authenticationKeyID $APP_STORE_KEY_ID -authenticationKeyPath "$GITHUB_WORKSPACE/packages/reown_appkit/example/modal/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 @@ -108,6 +101,8 @@ jobs: 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 + + # Notify Channel - name: Notify Channel uses: slackapi/slack-github-action@v1.24.0 env: @@ -116,13 +111,14 @@ jobs: with: payload: |- { - "text":"🍎 New *iOS* build *${{ github.ref_name }}* stable version for *Web3Modal Flutter* was just deployed." + "text":"🍎 AppKit *Modal* Flutter Sample *${{ github.ref_name }}* was just deployed." } - # Clean up + + # # Clean up - name: Clean up if: ${{ always() }} run: | - security delete-keychain $RUNNER_TEMP/app-signing.keychain-db + security delete-keychain $RUNNER_TEMP/release-modal.keychain-db rm ~/Library/MobileDevice/Provisioning\ Profiles/FlutterAppStoreProfileWithPush.mobileprovision flutter clean cd example diff --git a/.github/workflows/release_modal_ios_internal.yml b/.github/workflows/release_modal_ios_internal.yml index e6d75b8..fb7c3f1 100644 --- a/.github/workflows/release_modal_ios_internal.yml +++ b/.github/workflows/release_modal_ios_internal.yml @@ -1,4 +1,4 @@ -name: Build iOS App Internal (beta) +name: iOS Modal (internal) deploy on: workflow_dispatch: @@ -16,6 +16,7 @@ jobs: # 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: @@ -27,7 +28,7 @@ jobs: # create variables BUILD_CERT_PATH=$RUNNER_TEMP/build_certificate.p12 PP_PATH=$RUNNER_TEMP/FlutterAppStoreProfileInternal.mobileprovision - KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + KEYCHAIN_PATH=$RUNNER_TEMP/release-modal.keychain-db # import certificate and provisioning profile from secrets echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $BUILD_CERT_PATH @@ -45,6 +46,7 @@ jobs: # 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: @@ -58,25 +60,15 @@ jobs: # 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 + + # Install Flutter and Dependencies + - uses: ./.github/actions/dependencies 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 + working-directory: packages/reown_appkit/example/modal + # Build ios example app - name: Build ios example app - working-directory: example + working-directory: packages/reown_appkit/example/modal env: PROJECT_ID: ${{ secrets.PROJECT_ID }} APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }} @@ -86,10 +78,10 @@ jobs: 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//-/ }) + # FILE_VALUE=$(echo | grep "^version: " pubspec.yaml) + # PARTS=(${FILE_VALUE//:/ }) + # FULL_VERSION=${PARTS[1]} + VERSION_NUMBER=4.0.0 # Change bundleId in Runner scheme sed -i '' 's/com.web3modal.flutterExample/com.web3modal.flutterExample.internal/g' ios/Runner.xcodeproj/project.pbxproj @@ -106,8 +98,9 @@ jobs: 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" + xcodebuild -workspace "$GITHUB_WORKSPACE/packages/reown_appkit/example/modal/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/packages/reown_appkit/example/modal/ios/Runner.xcarchive" -exportOptionsPlist "$GITHUB_WORKSPACE/packages/reown_appkit/example/modal/ios/Runner/ExportOptionsInternal.plist" -exportPath "$GITHUB_WORKSPACE/packages/reown_appkit/example/modal/build/ios/ipa" -authenticationKeyIssuerID $APPLE_ISSUER_ID -authenticationKeyID $APP_STORE_KEY_ID -authenticationKeyPath "$GITHUB_WORKSPACE/packages/reown_appkit/example/modal/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 @@ -116,6 +109,8 @@ jobs: 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 + + # Notify Channel - name: Notify Channel uses: slackapi/slack-github-action@v1.24.0 env: @@ -124,14 +119,18 @@ jobs: with: payload: |- { - "text":"🍎 New *iOS* build *${{ github.ref_name }}* version for *Web3Modal Flutter* was just deployed. Test at https://testflight.apple.com/join/pzF2SUVm" + "text":"🍎 AppKit *Modal* Flutter Sample *${{ github.ref_name }}-internal* 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 + security delete-keychain $RUNNER_TEMP/release-modal.keychain-db rm ~/Library/MobileDevice/Provisioning\ Profiles/FlutterAppStoreProfileInternal.mobileprovision flutter clean cd example flutter clean + +# Launch locally +# act -j build_with_signing --container-architecture linux/amd64 -P macos-latest-xlarge=-self-hosted --secret-file .github/workflows/.env.secret.modal -W .github/workflows/release_modal_ios_internal.yml \ No newline at end of file diff --git a/.github/workflows/release_wallet_android.yml b/.github/workflows/release_wallet_android.yml index 232feea..45edb5f 100644 --- a/.github/workflows/release_wallet_android.yml +++ b/.github/workflows/release_wallet_android.yml @@ -6,6 +6,11 @@ on: types: - closed +# inputs: +# working-directory +# flavor +# BUNDLE_ID + jobs: build: if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master') || github.event_name == 'workflow_dispatch' diff --git a/.github/workflows/release_wallet_android_internal.yml b/.github/workflows/release_wallet_android_internal.yml index 19e803d..95b91b6 100644 --- a/.github/workflows/release_wallet_android_internal.yml +++ b/.github/workflows/release_wallet_android_internal.yml @@ -6,6 +6,11 @@ on: types: - closed +# inputs: +# working-directory +# flavor +# BUNDLE_ID + jobs: build: if: (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop') || github.event_name == 'workflow_dispatch' diff --git a/.gitignore b/.gitignore index 2819f6d..1d0d6ea 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ app.*.map.json # Run scripts run_tests_all.sh -*.env.secret +*.env.secret* diff --git a/README.md b/README.md index b5c97da..b635ecc 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,24 @@ The communications protocol for web3, Reown brings the ecosystem together by ena | [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.1 | 1.0.1 | +| 1.0.4 | 1.0.4 | 1.0.3 | 1.0.1 | ## License Reown is released under the Apache 2.0 license. [See LICENSE](/LICENSE) for details. -### Generate project dependencies +### To try this repo out -- Run `sh scripts/generate_all.sh` in the root folder to generate dependencies. +``` +1. git clone https://github.com/reown-com/reown_flutter.git +2. cd reown_flutter +3. sh scripts/generate_all.sh +``` ### Run WalletKit Sample 1. Run `cd packages/reown_walletkit/example` -2. Run `flutter run --dart-define=PROJECT_ID=0123... --flavor internal --debug` +2. Run `flutter run --dart-define="PROJECT_ID=0123..." --flavor internal` You can add your own keys for testing purposes as follows: @@ -42,12 +46,14 @@ _NB: WalletKit sample is intended to be used just as an explanatory project_ ### Run AppKit Sample -AppKit has two samples, `base`, which is made with `ReownAppKit` UI-less SDK, and `modal`, which is made with `ReownAppKitModal` - 1. Run `cd packages/reown_appkit/example/base` -2. Run `flutter run --dart-define=PROJECT_ID=0123... --flavor internal --debug` +2. Run `flutter run --dart-define="PROJECT_ID=0123..." --flavor internal` -or +### Test Sample Dapp and Wallet -1. Run `cd packages/reown_appkit/example/modal` -2. Run `flutter run --dart-define=PROJECT_ID=0123... --debug` \ No newline at end of file +- Sample Wallet: + - [Sample Wallet for iOS](https://testflight.apple.com/join/Uv0XoBuD) + - [Sample Wallet for Android](https://appdistribution.firebase.dev/i/2b8b3dce9e2831cd) +- AppKit DApp: + - [AppKit Dapp for iOS](https://testflight.apple.com/join/6aRJSllc) + - [AppKit Dapp for Android](https://appdistribution.firebase.dev/i/2c6573f6956fa7b5) \ No newline at end of file diff --git a/packages/reown_appkit/CHANGELOG.md b/packages/reown_appkit/CHANGELOG.md index 2b6d35a..711ce48 100644 --- a/packages/reown_appkit/CHANGELOG.md +++ b/packages/reown_appkit/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.2.0 + +- Non-EVM Chains support +- Social Logins +- Bug fixes + ## 1.0.1 - Updated Coinbase Wallet SDK to support Android Gradle Plugin 8 diff --git a/packages/reown_appkit/analysis_options.yaml b/packages/reown_appkit/analysis_options.yaml index b677fc6..3e16fe1 100644 --- a/packages/reown_appkit/analysis_options.yaml +++ b/packages/reown_appkit/analysis_options.yaml @@ -29,6 +29,7 @@ linter: sort_pub_dependencies: true avoid_unnecessary_containers: true cancel_subscriptions: true + public_member_api_docs: false analyzer: exclude: diff --git a/packages/reown_appkit/dart_dependency_validator.yaml b/packages/reown_appkit/dart_dependency_validator.yaml new file mode 100644 index 0000000..46c10a3 --- /dev/null +++ b/packages/reown_appkit/dart_dependency_validator.yaml @@ -0,0 +1,10 @@ +# dart_dependency_validator.yaml + +# Set true if you allow pinned packages in your project. +# allow_pins: true +# Exclude one or more paths from being scanned. Supports glob syntax. +exclude: + - 'example/**' # Glob's are supported +# Ignore one or more packages. +# ignore: +# - analyzer \ No newline at end of file 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 index fe850f8..a464976 100644 --- a/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml +++ b/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml @@ -52,8 +52,8 @@ - - + + - - - - - - - - - - - - diff --git a/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.pbxproj b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.pbxproj index 8233dbe..29492ec 100644 --- a/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/reown_appkit/example/base/ios/Runner.xcodeproj/project.pbxproj @@ -10,11 +10,11 @@ 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 */; }; + 59CABD5203E9490B70E54AF3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E3D33A121EFB3D9443E8911 /* Pods_Runner.framework */; }; 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 */; }; - DC20FE362923283F45816BFC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 787ABA0FE3B824874C8F17D8 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -39,15 +39,13 @@ 09969A8C2C73BC9100B14363 /* 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 = ""; }; + 3416E0D9F715E26B46021BBE /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3BC12F23EEF9AAEBD6DC9470 /* 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 = ""; }; - 502CCCDE3EE7AF774B525040 /* 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 = ""; }; - 6CC9F567B7A0A0781351BF49 /* 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 = ""; }; + 5E3D33A121EFB3D9443E8911 /* 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 = ""; }; - 787ABA0FE3B824874C8F17D8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 949DAC436C6D6BCA06DF2227 /* 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 = ""; }; + 920FE43105AD6367320BA170 /* 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; }; @@ -55,8 +53,10 @@ 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 = ""; }; - 9DAD5C3635B945C6034033E7 /* 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 = ""; }; - CB4B1CD3242757983B2C4FFF /* 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 = ""; }; + B355374D5D19CAFABA4263C4 /* 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 = ""; }; + C4ADA4C49431830B16CD5E4C /* 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 = ""; }; + CB3FE56545D3E38912E96518 /* 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 = ""; }; + DE145D03FB7834DA9D562601 /* 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 */ @@ -64,7 +64,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC20FE362923283F45816BFC /* Pods_Runner.framework in Frameworks */, + 59CABD5203E9490B70E54AF3 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -74,12 +74,12 @@ 337F646810BF58BC9EACB5E5 /* Pods */ = { isa = PBXGroup; children = ( - 502CCCDE3EE7AF774B525040 /* Pods-Runner.debug-production.xcconfig */, - 3BC12F23EEF9AAEBD6DC9470 /* Pods-Runner.debug-internal.xcconfig */, - 949DAC436C6D6BCA06DF2227 /* Pods-Runner.release-production.xcconfig */, - 6CC9F567B7A0A0781351BF49 /* Pods-Runner.release-internal.xcconfig */, - CB4B1CD3242757983B2C4FFF /* Pods-Runner.profile-production.xcconfig */, - 9DAD5C3635B945C6034033E7 /* Pods-Runner.profile-internal.xcconfig */, + B355374D5D19CAFABA4263C4 /* Pods-Runner.debug-production.xcconfig */, + C4ADA4C49431830B16CD5E4C /* Pods-Runner.debug-internal.xcconfig */, + CB3FE56545D3E38912E96518 /* Pods-Runner.release-production.xcconfig */, + DE145D03FB7834DA9D562601 /* Pods-Runner.release-internal.xcconfig */, + 3416E0D9F715E26B46021BBE /* Pods-Runner.profile-production.xcconfig */, + 920FE43105AD6367320BA170 /* Pods-Runner.profile-internal.xcconfig */, ); path = Pods; sourceTree = ""; @@ -106,7 +106,7 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 337F646810BF58BC9EACB5E5 /* Pods */, - 982F7AB9DEC126C4156BE443 /* Frameworks */, + D2394B1738CB57D1DF475570 /* Frameworks */, ); sourceTree = ""; }; @@ -135,10 +135,10 @@ path = Runner; sourceTree = ""; }; - 982F7AB9DEC126C4156BE443 /* Frameworks */ = { + D2394B1738CB57D1DF475570 /* Frameworks */ = { isa = PBXGroup; children = ( - 787ABA0FE3B824874C8F17D8 /* Pods_Runner.framework */, + 5E3D33A121EFB3D9443E8911 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -150,14 +150,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 22CDF3ADC8F132E392D0F7D4 /* [CP] Check Pods Manifest.lock */, + CEDF4D2527B372D5159DA206 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 6F66D87ECCC3A46691E42A5B /* [CP] Embed Pods Frameworks */, + FF3230E269FB3D3822F1DBD2 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -217,75 +217,75 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 22CDF3ADC8F132E392D0F7D4 /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); + name = "Thin Binary"; 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; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); - name = "Thin Binary"; + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; - 6F66D87ECCC3A46691E42A5B /* [CP] Embed Pods Frameworks */ = { + CEDF4D2527B372D5159DA206 /* [CP] Check Pods Manifest.lock */ = { 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"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + 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; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + FF3230E269FB3D3822F1DBD2 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputPaths = ( + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "Run Script"; - outputPaths = ( + 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 = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -648,7 +648,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterdapp 1724090152"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterdapp 1727257949"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -817,7 +817,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterdapp 1724090152"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterdapp 1727257949"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/packages/reown_appkit/example/base/ios/Runner/AppDelegate.swift b/packages/reown_appkit/example/base/ios/Runner/AppDelegate.swift index 805d27d..53e1dfa 100644 --- a/packages/reown_appkit/example/base/ios/Runner/AppDelegate.swift +++ b/packages/reown_appkit/example/base/ios/Runner/AppDelegate.swift @@ -1,5 +1,6 @@ import UIKit import Flutter +import CoinbaseWalletSDK @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { @@ -48,14 +49,31 @@ import Flutter } 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 linkStreamHandler.handleLink(url.absoluteString) } 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 + } + } + } + if userActivity.activityType == NSUserActivityTypeBrowsingWeb { handleIncomingUniversalLink(userActivity: userActivity) return true } + return false } diff --git a/packages/reown_appkit/example/base/ios/Runner/Info-internal.plist b/packages/reown_appkit/example/base/ios/Runner/Info-internal.plist index dbaa42e..0327438 100644 --- a/packages/reown_appkit/example/base/ios/Runner/Info-internal.plist +++ b/packages/reown_appkit/example/base/ios/Runner/Info-internal.plist @@ -41,8 +41,53 @@ LSApplicationQueriesSchemes - wcflutterwallet-internal + 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 + bitizen + ape + uniswap + zerion + oasys-wallet + coinstats + ledgerlive + safe + okex + trust + thorwallet + krakenwallet + coinwallet + mewwallet + metamask + avacus walletapp + wcflutterwallet-internal + rn-web3wallet-internal ITSAppUsesNonExemptEncryption diff --git a/packages/reown_appkit/example/base/ios/Runner/Info.plist b/packages/reown_appkit/example/base/ios/Runner/Info.plist index f70379f..43ad88f 100644 --- a/packages/reown_appkit/example/base/ios/Runner/Info.plist +++ b/packages/reown_appkit/example/base/ios/Runner/Info.plist @@ -43,8 +43,53 @@ LSApplicationQueriesSchemes - wcflutterwallet + 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 + bitizen + ape + uniswap + zerion + oasys-wallet + coinstats + ledgerlive + safe + okex + trust + thorwallet + krakenwallet + coinwallet + mewwallet + metamask + avacus walletapp + wcflutterwallet + rn-web3wallet LSRequiresIPhoneOS diff --git a/packages/reown_appkit/example/base/ios/fastlane/Fastfile b/packages/reown_appkit/example/base/ios/fastlane/Fastfile index 81c07ae..8091736 100644 --- a/packages/reown_appkit/example/base/ios/fastlane/Fastfile +++ b/packages/reown_appkit/example/base/ios/fastlane/Fastfile @@ -115,6 +115,8 @@ platform :ios do distribute_external: true, notify_external_testers: true, skip_waiting_for_build_processing: false, + beta_app_feedback_email: "alfredo@reown.com", + beta_app_description: "AppKit sample app", groups: ["External Testers"] ) diff --git a/packages/reown_appkit/example/base/lib/main.dart b/packages/reown_appkit/example/base/lib/main.dart index c22e52a..9d30dd3 100644 --- a/packages/reown_appkit/example/base/lib/main.dart +++ b/packages/reown_appkit/example/base/lib/main.dart @@ -1,22 +1,21 @@ import 'dart:convert'; import 'dart:developer'; +import 'dart:io'; 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/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'; +// ignore: depend_on_referenced_packages +import 'package:shared_preferences/shared_preferences.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -24,18 +23,55 @@ void main() { runApp(const MyApp()); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); - // This widget is the root of your application. + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State with WidgetsBindingObserver { + bool _isDarkMode = false; + + @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(); + } + @override Widget build(BuildContext context) { - return MaterialApp( - title: StringConstants.appTitle, - theme: ThemeData( - primarySwatch: Colors.blue, + return ReownAppKitModalTheme( + isDarkMode: _isDarkMode, + child: MaterialApp( + title: StringConstants.appTitle, + home: const MyHomePage(), ), - home: const MyHomePage(), ); } } @@ -48,8 +84,6 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - bool _initializing = true; - ReownAppKit? _appKit; ReownAppKitModal? _appKitModal; @@ -101,13 +135,11 @@ class _MyHomePageState extends State { _appKit = ReownAppKit( core: ReownCore( projectId: DartDefines.projectId, - logLevel: LogLevel.error, + logLevel: LogLevel.all, ), metadata: _pairingMetadata(), ); - _appKit!.core.addLogListener(_logListener); - // Register event handlers _appKit!.core.relayClient.onRelayClientError.subscribe( _relayClientError, @@ -124,11 +156,99 @@ class _MyHomePageState extends State { _appKit!.onSessionConnect.subscribe(_onSessionConnect); _appKit!.onSessionAuthResponse.subscribe(_onSessionAuthResponse); + // See https://docs.reown.com/appkit/flutter/core/custom-chains + // final extraChains = ReownAppKitModalNetworks.extra['eip155']!; + // ReownAppKitModalNetworks.addSupportedNetworks('eip155', extraChains); + // ReownAppKitModalNetworks.removeSupportedNetworks('solana'); + // ReownAppKitModalNetworks.removeTestNetworks(); + + final prefs = await SharedPreferences.getInstance(); + final linkMode = prefs.getBool('appkit_sample_linkmode') ?? false; + if (!linkMode) { + ReownAppKitModalNetworks.addSupportedNetworks('polkadot', [ + ReownAppKitModalNetworkInfo( + name: 'Polkadot', + chainId: '91b171bb158e2d3848fa23a9f1c25182', + chainIcon: 'https://cryptologos.cc/logos/polkadot-new-dot-logo.png', + currency: 'DOT', + rpcUrl: 'https://rpc.polkadot.io', + explorerUrl: 'https://polkadot.subscan.io', + ), + ReownAppKitModalNetworkInfo( + name: 'Westend', + chainId: 'e143f23803ac50e8f6f8e62695d1ce9e', + currency: 'DOT', + rpcUrl: 'https://westend-rpc.polkadot.io', + explorerUrl: 'https://westend.subscan.io', + isTestNetwork: true, + ), + ]); + } else { + ReownAppKitModalNetworks.removeSupportedNetworks('solana'); + } + _appKitModal = ReownAppKitModal( context: context, appKit: _appKit, - siweConfig: _siweConfig(), - enableEmail: true, + siweConfig: _siweConfig(linkMode), + enableAnalytics: true, + featuresConfig: FeaturesConfig( + email: true, + socials: [ + AppKitSocialOption.Farcaster, + AppKitSocialOption.X, + AppKitSocialOption.Apple, + AppKitSocialOption.Discord, + ], + showMainWallets: false, // OPTIONAL - true 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 + optionalNamespaces: !linkMode + ? { + // This is needed if more chains besides EVM and Solana are supported + // mostly because we can not define internally every possible method for every possible chain + 'eip155': RequiredNamespace.fromJson({ + 'chains': ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: 'eip155', + ).map((chain) => 'eip155:${chain.chainId}').toList(), + 'methods': + NetworkUtils.defaultNetworkMethods['eip155']!.toList(), + 'events': NetworkUtils.defaultNetworkEvents['eip155']!.toList(), + }), + 'solana': RequiredNamespace.fromJson({ + 'chains': ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: 'solana', + ).map((chain) => 'solana:${chain.chainId}').toList(), + 'methods': + NetworkUtils.defaultNetworkMethods['solana']!.toList(), + 'events': [], + }), + 'polkadot': RequiredNamespace.fromJson({ + 'chains': ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: 'polkadot', + ).map((chain) => 'polkadot:${chain.chainId}').toList(), + 'methods': [ + 'polkadot_signMessage', + 'polkadot_signTransaction', + ], + 'events': [] + }), + } + : null, ); _appKitModal!.onModalConnect.subscribe(_onModalConnect); @@ -137,44 +257,73 @@ class _MyHomePageState extends State { _appKitModal!.onModalDisconnect.subscribe(_onModalDisconnect); _appKitModal!.onModalError.subscribe(_onModalError); + _pageDatas = [ + PageData( + page: ConnectPage( + appKitModal: _appKitModal!, + linkMode: linkMode, + reinitialize: (bool linkMode) async { + final result = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text('App will be closed to apply changes'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text('Ok'), + ), + ], + ); + }, + ); + if (result == true) { + await prefs.setBool('appkit_sample_linkmode', linkMode); + if (!kDebugMode) { + exit(0); + } + } + }, + ), + title: StringConstants.connectPageTitle, + icon: Icons.home, + ), + PageData( + page: PairingsPage(appKitModal: _appKitModal!), + title: StringConstants.pairingsPageTitle, + icon: Icons.vertical_align_center_rounded, + ), + // PageData( + // page: SessionsPage(appKitModal: _appKitModal!), + // title: StringConstants.sessionsPageTitle, + // icon: Icons.workspaces_filled, + // ), + ]; + await _appKitModal!.init(); await _registerEventHandlers(); - DeepLinkHandler.init(_appKit!); + DeepLinkHandler.init(_appKitModal!); DeepLinkHandler.checkInitialLink(); + final allChains = ReownAppKitModalNetworks.getAllSupportedNetworks(); // Loop through all the chain data - for (final ChainMetadata chain in ChainData.allChains) { + for (final chain in allChains) { // Loop through the events for that chain - for (final event in getChainEvents(chain.type)) { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chain.chainId, + ); + for (final event in getChainEvents(namespace)) { _appKit!.registerEventHandler( chainId: chain.chainId, event: event, ); } } - - setState(() { - _pageDatas = [ - PageData( - page: ConnectPage(appKitModal: _appKitModal!), - 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 { @@ -186,9 +335,13 @@ class _MyHomePageState extends State { } // Loop through all the chain data - for (final ChainMetadata chain in ChainData.allChains) { + final allChains = ReownAppKitModalNetworks.getAllSupportedNetworks(); + for (final chain in allChains) { // Loop through the events for that chain - for (final event in getChainEvents(chain.type)) { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chain.chainId, + ); + for (final event in getChainEvents(namespace)) { _appKit!.registerEventHandler( chainId: chain.chainId, event: event, @@ -198,22 +351,14 @@ class _MyHomePageState extends State { } void _onSessionConnect(SessionConnect? event) { - debugPrint('[SampleDapp] _onSessionConnect $event'); - Future.delayed(const Duration(milliseconds: 500), () { - setState(() => _selectedIndex = 2); - }); + log('[SampleDapp] _onSessionConnect ${jsonEncode(event?.session.toJson())}'); } 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 _setState(_) => setState(() {}); void _relayClientError(ErrorEvent? event) { debugPrint('[SampleDapp] _relayClientError ${event?.error}'); @@ -223,8 +368,6 @@ class _MyHomePageState extends State { @override void dispose() { // Unregister event handlers - _appKit!.core.removeLogListener(_logListener); - _appKit!.core.relayClient.onRelayClientError.unsubscribe( _relayClientError, ); @@ -249,25 +392,11 @@ class _MyHomePageState extends State { 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, - ), - ); + if (_pageDatas.isEmpty) { + return Center(child: CircularProgressIndicator()); } - final List navRail = []; if (MediaQuery.of(context).size.width >= Constants.smallScreen) { navRail.add(_buildNavigationRail()); @@ -393,7 +522,7 @@ class _MyHomePageState extends State { } } - SIWEConfig _siweConfig() => SIWEConfig( + SIWEConfig _siweConfig(bool enabled) => SIWEConfig( getNonce: () async { // this has to be called at the very moment of creating the pairing uri return SIWEUtils.generateNonce(); @@ -437,8 +566,11 @@ class _MyHomePageState extends State { }, getSession: () async { // Return proper session from your Web Service - final address = _appKitModal!.session!.address!; - final chainId = _appKitModal!.session!.chainId; + final chainId = _appKitModal!.selectedChain?.chainId ?? '1'; + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + final address = _appKitModal!.session!.getAddress(namespace)!; return SIWESession(address: address, chains: [chainId]); }, onSignIn: (SIWESession session) { @@ -453,20 +585,21 @@ class _MyHomePageState extends State { // Called when disconnecting WalletConnect session was successfull debugPrint('[SIWEConfig] onSignOut()'); }, - enabled: true, + enabled: enabled, signOutOnDisconnect: true, - signOutOnAccountChange: true, + signOutOnAccountChange: false, signOutOnNetworkChange: false, // nonceRefetchIntervalMs: 300000, // sessionRefetchIntervalMs: 300000, ); void _onModalConnect(ModalConnect? event) async { - setState(() {}); debugPrint('[ExampleApp] _onModalConnect ${event?.session.toJson()}'); + setState(() {}); } void _onModalUpdate(ModalConnect? event) { + debugPrint('[ExampleApp] _onModalUpdate ${event?.session.toJson()}'); setState(() {}); } diff --git a/packages/reown_appkit/example/base/lib/models/chain_metadata.dart b/packages/reown_appkit/example/base/lib/models/chain_metadata.dart deleted file mode 100644 index 834a0ba..0000000 --- a/packages/reown_appkit/example/base/lib/models/chain_metadata.dart +++ /dev/null @@ -1,51 +0,0 @@ -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/pages/connect_page.dart b/packages/reown_appkit/example/base/lib/pages/connect_page.dart index c7dff84..163af43 100644 --- a/packages/reown_appkit/example/base/lib/pages/connect_page.dart +++ b/packages/reown_appkit/example/base/lib/pages/connect_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:fl_toast/fl_toast.dart'; import 'package:flutter/foundation.dart'; @@ -7,35 +8,44 @@ 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/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/sample_wallets.dart'; import 'package:reown_appkit_dapp/utils/string_constants.dart'; import 'package:reown_appkit_dapp/widgets/chain_button.dart'; +import 'package:reown_appkit_dapp/widgets/method_dialog.dart'; class ConnectPage extends StatefulWidget { const ConnectPage({ super.key, required this.appKitModal, + required this.reinitialize, + this.linkMode = false, }); final ReownAppKitModal appKitModal; + final Function(bool linkMode) reinitialize; + final bool linkMode; @override ConnectPageState createState() => ConnectPageState(); } class ConnectPageState extends State { - final List _selectedChains = []; + final List _selectedChains = []; bool _shouldDismissQrCode = true; @override void initState() { super.initState(); + widget.appKitModal.onModalConnect.subscribe(_onModalConnect); + widget.appKitModal.onModalUpdate.subscribe(_onModalUpdate); + widget.appKitModal.onModalNetworkChange.subscribe(_onModalNetworkChange); + widget.appKitModal.onModalDisconnect.subscribe(_onModalDisconnect); + widget.appKitModal.onModalError.subscribe(_onModalError); + // widget.appKitModal.appKit!.onSessionConnect.subscribe( _onSessionConnect, ); @@ -49,6 +59,11 @@ class ConnectPageState extends State { @override void dispose() { + widget.appKitModal.onModalConnect.unsubscribe(_onModalConnect); + widget.appKitModal.onModalUpdate.unsubscribe(_onModalUpdate); + widget.appKitModal.onModalNetworkChange.unsubscribe(_onModalNetworkChange); + widget.appKitModal.onModalDisconnect.unsubscribe(_onModalDisconnect); + widget.appKitModal.onModalError.unsubscribe(_onModalError); widget.appKitModal.onModalDisconnect.unsubscribe( _onModalDisconnect, ); @@ -61,7 +76,7 @@ class ConnectPageState extends State { super.dispose(); } - void _selectChain(ChainMetadata chain) { + void _selectChain(ReownAppKitModalNetworkInfo chain) { setState(() { if (_selectedChains.contains(chain)) { _selectedChains.remove(chain); @@ -78,61 +93,58 @@ class ConnectPageState extends State { void _updateNamespaces() { optionalNamespaces = {}; - final evmChains = - _selectedChains.where((e) => e.type == ChainType.eip155).toList(); + final evmChains = _selectedChains.where((c) { + final ns = ReownAppKitModalNetworks.getNamespaceForChainId(c.chainId); + return ns == 'eip155'; + }).toList(); if (evmChains.isNotEmpty) { optionalNamespaces['eip155'] = RequiredNamespace( - chains: evmChains.map((c) => c.chainId).toList(), + chains: evmChains.map((c) => 'eip155:${c.chainId}').toList(), methods: EIP155.methods.values.toList(), events: EIP155.events.values.toList(), ); } - final solanaChains = - _selectedChains.where((e) => e.type == ChainType.solana).toList(); + final solanaChains = _selectedChains.where((c) { + final ns = ReownAppKitModalNetworks.getNamespaceForChainId(c.chainId); + return ns == 'solana'; + }).toList(); if (solanaChains.isNotEmpty) { optionalNamespaces['solana'] = RequiredNamespace( - chains: solanaChains.map((c) => c.chainId).toList(), + chains: solanaChains.map((c) => 'solana:${c.chainId}').toList(), methods: Solana.methods.values.toList(), events: Solana.events.values.toList(), ); } - final polkadotChains = - _selectedChains.where((e) => e.type == ChainType.polkadot).toList(); + final polkadotChains = _selectedChains.where((c) { + final ns = ReownAppKitModalNetworks.getNamespaceForChainId(c.chainId); + return ns == 'polkadot'; + }).toList(); if (polkadotChains.isNotEmpty) { optionalNamespaces['polkadot'] = RequiredNamespace( - chains: polkadotChains.map((c) => c.chainId).toList(), + chains: polkadotChains.map((c) => 'polkadot:${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'], - ), - }; - } + debugPrint( + '[$runtimeType] optionalNamespaces ${jsonEncode(optionalNamespaces)}', + ); } @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 allChains = ReownAppKitModalNetworks.getAllSupportedNetworks(); + final mainChains = allChains.where((e) => !e.isTestNetwork).toList(); + final testChains = allChains.where((e) => e.isTestNetwork).toList(); final List chainButtons = []; final List testButtons = []; - for (final ChainMetadata chain in mainChains) { + for (final chain in mainChains) { // Build the button chainButtons.add( ChainButton( @@ -142,7 +154,7 @@ class ConnectPageState extends State { ), ); } - for (final ChainMetadata chain in testChains) { + for (final chain in testChains) { // Build the button testButtons.add( ChainButton( @@ -154,28 +166,23 @@ class ConnectPageState extends State { } return ListView( - padding: const EdgeInsets.symmetric(horizontal: StyleConstants.linear8), + padding: const EdgeInsets.symmetric( + horizontal: StyleConstants.linear8, + ), children: [ Text( widget.appKitModal.appKit!.metadata.name, style: StyleConstants.subtitleText, textAlign: TextAlign.center, ), - const SizedBox(height: StyleConstants.linear8), - const Divider(), + const SizedBox(height: StyleConstants.linear16), + const Divider(height: 1.0), + const SizedBox(height: StyleConstants.linear16), const Text( - 'Connect With AppKit Modal and Link Mode:', + 'Connect With AppKit Modal', style: StyleConstants.buttonText, textAlign: TextAlign.center, ), - Text( - 'Only EVM chains', - style: TextStyle( - color: Colors.black.withOpacity(0.7), - fontSize: 12.0, - ), - textAlign: TextAlign.center, - ), const SizedBox(height: StyleConstants.linear8), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -192,222 +199,124 @@ class ConnectPageState extends State { const SizedBox(height: StyleConstants.linear8), Visibility( visible: widget.appKitModal.isConnected, - child: AppKitModalAccountButton( - appKit: widget.appKitModal, - ), - ), - const SizedBox(height: StyleConstants.linear8), - Visibility( - visible: !widget.appKitModal.isConnected, child: Column( children: [ + AppKitModalAccountButton( + appKitModal: widget.appKitModal, + ), + const SizedBox.square(dimension: 8.0), Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Expanded( - child: const Divider(), - ), - const Text( - ' Or ', - style: StyleConstants.buttonText, - textAlign: TextAlign.center, + AppKitModalBalanceButton( + appKitModal: widget.appKitModal, + onTap: widget.appKitModal.openNetworksView, ), - Expanded( - child: const Divider(), + const SizedBox.square(dimension: 8.0), + AppKitModalAddressButton( + appKitModal: widget.appKitModal, + onTap: widget.appKitModal.openModalView, ), ], ), - const SizedBox(height: StyleConstants.linear8), - const Text( - 'Connect With AppKit multichain', - style: StyleConstants.buttonText, - textAlign: TextAlign.center, - ), - const SizedBox(height: StyleConstants.linear8), - Wrap( - spacing: 10.0, - children: chainButtons, - ), - // const Divider(), - const Text('Test chains'), - Wrap( - spacing: 10.0, - children: testButtons, - ), - const SizedBox(height: StyleConstants.linear16), - // const Divider(), + const SizedBox.square(dimension: 8.0), + ...(_buildRequestButtons()), + ], + ), + ), + const SizedBox(height: StyleConstants.linear8), + Visibility( + visible: !widget.appKitModal.isConnected, + child: Column( + children: [ Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - '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(), - ), - ], + child: Text( + 'non-EVM\nSession Proposal', + textAlign: TextAlign.end, + style: TextStyle( + fontWeight: !widget.linkMode + ? FontWeight.bold + : FontWeight.normal, + ), ), ), - const SizedBox.square(dimension: 8.0), + Switch( + value: widget.linkMode, + onChanged: (value) { + widget.reinitialize(value); + }, + ), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - 'Link Mode:', - 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(), - ), - ], + child: Text( + 'only EVM\nLink Mode', + style: TextStyle( + fontWeight: widget.linkMode + ? FontWeight.bold + : FontWeight.normal, + ), ), ), ], ), - 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.appKitModal.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.appKitModal.appKit!.metadata.redirect?.universal}', - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ), - ], - ), - Row( - children: [ - const Text('Link Mode: '), - Text( - '${widget.appKitModal.appKit!.metadata.redirect?.linkMode}', - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), + const Divider(height: 1.0), + const SizedBox(height: StyleConstants.linear8), + _FooterWidget(appKitModal: widget.appKitModal), 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), ], ); } + List _buildRequestButtons() { + final chainId = widget.appKitModal.selectedChain?.chainId ?? '1'; + final ns = ReownAppKitModalNetworks.getNamespaceForChainId(chainId); + return widget.appKitModal.getApprovedMethods(namespace: ns)?.map((method) { + final topic = widget.appKitModal.session!.topic ?? ''; + final chainId = widget.appKitModal.selectedChain!.chainId; + final address = widget.appKitModal.session!.getAddress(ns)!; + final chainInfo = ReownAppKitModalNetworks.getNetworkById( + ns, + chainId, + ); + // final requestParams = await getParams(method, address); + // final enabled = requestParams != null; + return Container( + height: 40.0, + width: double.infinity, + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + child: FutureBuilder( + future: getParams(method, address, rpcUrl: chainInfo?.rpcUrl), + builder: (_, snapshot) { + final enabled = snapshot.data != null; + return ElevatedButton( + onPressed: enabled + ? () { + widget.appKitModal.launchConnectedWallet(); + final future = widget.appKitModal.request( + topic: topic, + chainId: chainId, + request: snapshot.data!, + ); + MethodDialog.show(context, method, future); + } + : null, + child: Text(method), + ); + }), + ); + }).toList() ?? + []; + } + + // ignore: unused_element Future _onConnect({ required String nativeLink, VoidCallback? closeModal, @@ -439,6 +348,7 @@ class ConnectPageState extends State { closeModal?.call(); } + // ignore: unused_element void _sessionAuthenticate({ required String nativeLink, required String universalLink, @@ -446,12 +356,13 @@ class ConnectPageState extends State { Function(String message)? showToast, }) async { debugPrint( - '[SampleDapp] Creating authenticate with $nativeLink, $universalLink'); + '[SampleDapp] Creating authentication with $nativeLink, $universalLink', + ); final methods1 = requiredNamespaces['eip155']?.methods ?? []; final methods2 = optionalNamespaces['eip155']?.methods ?? []; final authResponse = await widget.appKitModal.appKit!.authenticate( params: SessionAuthRequestParams( - chains: _selectedChains.map((e) => e.chainId).toList(), + chains: _selectedChains.map((e) => 'eip155:${e.chainId}').toList(), domain: Uri.parse(widget.appKitModal.appKit!.metadata.url).authority, nonce: AuthUtils.generateNonce(), uri: widget.appKitModal.appKit!.metadata.url, @@ -559,17 +470,34 @@ class ConnectPageState extends State { } } + void _onModalConnect(ModalConnect? event) async { + setState(() {}); + } + + void _onModalUpdate(ModalConnect? event) { + setState(() {}); + } + + void _onModalNetworkChange(ModalNetworkChange? event) { + setState(() {}); + } + void _onModalDisconnect(ModalDisconnect? event) { setState(() {}); } + void _onModalError(ModalError? event) { + setState(() {}); + } + + // ignore: unused_element ButtonStyle get _buttonStyle => ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith( (states) { if (states.contains(MaterialState.disabled)) { return StyleConstants.grayColor; } - return StyleConstants.primaryColor; + return Colors.blue; }, ), textStyle: MaterialStateProperty.resolveWith( @@ -595,6 +523,106 @@ class ConnectPageState extends State { ); } +class _FooterWidget extends StatefulWidget { + const _FooterWidget({required this.appKitModal}); + final ReownAppKitModal appKitModal; + + @override + State<_FooterWidget> createState() => __FooterWidgetState(); +} + +class __FooterWidgetState extends State<_FooterWidget> { + @override + Widget build(BuildContext context) { + final textStyle = TextStyle(fontSize: 12.0); + final textStyleBold = textStyle.copyWith(fontWeight: FontWeight.bold); + final redirect = widget.appKitModal.appKit!.metadata.redirect; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: StyleConstants.linear8), + Text('Redirect:', style: textStyleBold), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Native: ', style: textStyle), + Expanded( + child: Text('${redirect?.native}', style: textStyleBold), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Universal: ', style: textStyle), + Expanded( + child: Text('${redirect?.universal}', style: textStyleBold), + ), + ], + ), + Row( + children: [ + Text('Link Mode: ', style: textStyle), + Text('${redirect?.linkMode}', style: textStyleBold), + ], + ), + 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: [ + Text('App Version: ', style: textStyle), + Expanded( + child: Text( + '$v-$f ($b) - SDK v$packageVersion', + style: textStyleBold, + ), + ), + ], + ); + }, + ), + const SizedBox(height: StyleConstants.linear8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: !widget.appKitModal.isConnected, + child: SizedBox( + height: 30.0, + child: ElevatedButton( + onPressed: () async { + await widget.appKitModal.appKit!.core.storage.deleteAll(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Storage cleared'), + duration: Duration(seconds: 1), + )); + }, + child: Text( + 'CLEAR STORAGE', + style: TextStyle(fontSize: 10.0), + ), + ), + ), + ), + ], + ), + ], + ); + } +} + class QRCodeScreen extends StatefulWidget { const QRCodeScreen({ super.key, diff --git a/packages/reown_appkit/example/base/lib/pages/pairings_page.dart b/packages/reown_appkit/example/base/lib/pages/pairings_page.dart index a3db3e0..46f0517 100644 --- a/packages/reown_appkit/example/base/lib/pages/pairings_page.dart +++ b/packages/reown_appkit/example/base/lib/pages/pairings_page.dart @@ -8,10 +8,10 @@ import 'package:reown_appkit_dapp/widgets/pairing_item.dart'; class PairingsPage extends StatefulWidget { const PairingsPage({ super.key, - required this.appKit, + required this.appKitModal, }); - final ReownAppKit appKit; + final ReownAppKitModal appKitModal; @override PairingsPageState createState() => PairingsPageState(); @@ -19,26 +19,31 @@ class PairingsPage extends StatefulWidget { class PairingsPageState extends State { List _pairings = []; + late IReownAppKit _appKit; @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); + _appKit = widget.appKitModal.appKit!; + _pairings = _appKit.pairings.getAll(); + _appKit.core.pairing.onPairingDelete.subscribe(_onPairingDelete); + _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); + _appKit.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); + _appKit.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete); super.dispose(); } @override Widget build(BuildContext context) { + if (_pairings.isEmpty) { + return Center( + child: Text('No relay pairings'), + ); + } final List pairingItems = _pairings .map( (PairingInfo pairing) => PairingItem( @@ -71,7 +76,7 @@ class PairingsPageState extends State { ), onPressed: () async { try { - widget.appKit.core.pairing.disconnect( + _appKit.core.pairing.disconnect( topic: pairing.topic, ); Navigator.of(context).pop(); @@ -104,7 +109,7 @@ class PairingsPageState extends State { void _onPairingDelete(PairingEvent? event) { setState(() { - _pairings = widget.appKit.pairings.getAll(); + _pairings = _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 index 3b23b9d..412e318 100644 --- a/packages/reown_appkit/example/base/lib/pages/sessions_page.dart +++ b/packages/reown_appkit/example/base/lib/pages/sessions_page.dart @@ -9,37 +9,44 @@ import 'package:reown_appkit_dapp/widgets/session_widget.dart'; class SessionsPage extends StatefulWidget { const SessionsPage({ super.key, - required this.appKit, + required this.appKitModal, }); - final ReownAppKit appKit; + final ReownAppKitModal appKitModal; @override SessionsPageState createState() => SessionsPageState(); } class SessionsPageState extends State { + late IReownAppKit _appKit; Map _activeSessions = {}; - String _selectedSession = ''; + String _selectedTopic = ''; @override void initState() { - _activeSessions = widget.appKit.getActiveSessions(); - widget.appKit.onSessionDelete.subscribe(_onSessionDelete); - widget.appKit.onSessionExpire.subscribe(_onSessionExpire); + _appKit = widget.appKitModal.appKit!; + _activeSessions = _appKit.getActiveSessions(); + _appKit.onSessionDelete.subscribe(_onSessionDelete); + _appKit.onSessionExpire.subscribe(_onSessionExpire); super.initState(); } @override void dispose() { - widget.appKit.onSessionDelete.unsubscribe(_onSessionDelete); - widget.appKit.onSessionExpire.unsubscribe(_onSessionExpire); + _appKit.onSessionDelete.unsubscribe(_onSessionDelete); + _appKit.onSessionExpire.unsubscribe(_onSessionExpire); super.dispose(); } @override Widget build(BuildContext context) { final List sessions = _activeSessions.values.toList(); + if (sessions.isEmpty) { + return Center( + child: Text('No relay sessions'), + ); + } return Center( child: Container( constraints: const BoxConstraints( @@ -53,14 +60,14 @@ class SessionsPageState extends State { materialGapSize: 0.0, expansionCallback: (int index, bool isExpanded) { setState(() { - _selectedSession = !isExpanded ? '' : sessions[index].topic; + _selectedTopic = !isExpanded ? '' : sessions[index].topic; }); }, children: sessions .map( (session) => ExpansionPanel( canTapOnHeader: true, - isExpanded: _selectedSession == session.topic, + isExpanded: _selectedTopic == session.topic, backgroundColor: Colors.blue.withOpacity(0.2), headerBuilder: (context, isExpanded) { return SessionItem( @@ -84,7 +91,7 @@ class SessionsPageState extends State { } Widget _buildSessionView() { - if (_selectedSession == '') { + if (_selectedTopic == '') { return const Center( child: Text( StringConstants.noSessionSelected, @@ -93,29 +100,27 @@ class SessionsPageState extends State { ); } - final SessionData session = _activeSessions[_selectedSession]!; - return SessionWidget( - appKit: widget.appKit, - session: session, + appKitModal: widget.appKitModal, + sessionTopic: _selectedTopic, ); } void _onSessionDelete(SessionDelete? event) { setState(() { - if (event!.topic == _selectedSession) { - _selectedSession = ''; + if (event!.topic == _selectedTopic) { + _selectedTopic = ''; } - _activeSessions = widget.appKit.getActiveSessions(); + _activeSessions = _appKit.getActiveSessions(); }); } void _onSessionExpire(SessionExpire? event) { setState(() { - if (event!.topic == _selectedSession) { - _selectedSession = ''; + if (event!.topic == _selectedTopic) { + _selectedTopic = ''; } - _activeSessions = widget.appKit.getActiveSessions(); + _activeSessions = _appKit.getActiveSessions(); }); } } 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 deleted file mode 100644 index b6b4396..0000000 --- a/packages/reown_appkit/example/base/lib/utils/crypto/chain_data.dart +++ /dev/null @@ -1,199 +0,0 @@ -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 index 0556792..594bb28 100644 --- a/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart +++ b/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart @@ -1,12 +1,9 @@ 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/crypto/helpers.dart'; import 'package:reown_appkit_dapp/utils/smart_contracts.dart'; -import 'package:reown_appkit_dapp/utils/test_data.dart'; enum EIP155Methods { personalSign, @@ -36,10 +33,10 @@ class EIP155 { }; static Future callMethod({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String method, - required ChainMetadata chainData, + required ReownAppKitModalNetworkInfo chainData, required String address, }) { switch (method) { @@ -49,7 +46,6 @@ class EIP155 { topic: topic, chainId: chainData.chainId, address: address, - message: testSignData, ); case 'eth_sign': return ethSign( @@ -57,7 +53,6 @@ class EIP155 { topic: topic, chainId: chainData.chainId, address: address, - message: testSignData, ); case 'eth_signTypedData': return ethSignTypedData( @@ -65,33 +60,18 @@ class EIP155 { 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'; @@ -99,7 +79,7 @@ class EIP155 { } static Future callSmartContract({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String address, required String action, @@ -113,21 +93,23 @@ class EIP155 { EthereumAddress.fromHex(SepoliaTestContract.contractAddress), ); - final sepolia = - ChainData.allChains.firstWhere((e) => e.chainId == 'eip155:11155111'); + final sepolia = ReownAppKitModalNetworks.getNetworkById( + 'eip155', + '11155111', + )!; switch (action) { case 'read': return readSmartContract( appKit: appKit, - rpcUrl: sepolia.rpc.first, + rpcUrl: sepolia.rpcUrl, contract: deployedContract, address: address, ); case 'write': return appKit.requestWriteContract( topic: topic, - chainId: sepolia.chainId, + chainId: 'eip155:${sepolia.chainId}', deployedContract: deployedContract, functionName: 'transfer', transaction: Transaction( @@ -148,93 +130,70 @@ class EIP155 { } static Future personalSign({ - required ReownAppKit appKit, + required IReownAppKit 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], - ), + chainId: 'eip155:$chainId', + request: (await getParams('personal_sign', address))!, ); } static Future ethSign({ - required ReownAppKit appKit, + required IReownAppKit 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], - ), + chainId: 'eip155:$chainId', + request: (await getParams('eth_sign', address))!, ); } static Future ethSignTypedData({ - required ReownAppKit appKit, + required IReownAppKit 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], - ), + chainId: 'eip155:$chainId', + request: (await getParams('eth_signTypedData', address))!, ); } static Future ethSignTransaction({ - required ReownAppKit appKit, + required IReownAppKit 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()], - ), + chainId: 'eip155:$chainId', + request: (await getParams('eth_signTransaction', ''))!, ); } static Future ethSendTransaction({ - required ReownAppKit appKit, + required IReownAppKit 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()], - ), + chainId: 'eip155:$chainId', + request: (await getParams('eth_sendTransaction', ''))!, ); } static Future readSmartContract({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String rpcUrl, required String address, required DeployedContract contract, diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/helpers.dart b/packages/reown_appkit/example/base/lib/utils/crypto/helpers.dart index 6ead377..5caf4b9 100644 --- a/packages/reown_appkit/example/base/lib/utils/crypto/helpers.dart +++ b/packages/reown_appkit/example/base/lib/utils/crypto/helpers.dart @@ -1,55 +1,155 @@ -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 'dart:convert'; + +import 'package:bs58/bs58.dart'; +import 'package:eth_sig_util/util/utils.dart'; +import 'package:reown_appkit/reown_appkit.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/test_data.dart'; -String getChainName(String chain) { - try { - return ChainData.allChains - .where((element) => element.chainId == chain) - .first - .name; - } catch (e) { - debugPrint('[SampleDapp] Invalid chain'); - } - return 'Unknown'; -} +import 'package:solana_web3/solana_web3.dart' as solana; -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: +List getChainMethods(String namespace) { + switch (namespace) { + case 'eip155': return EIP155.methods.values.toList(); - case ChainType.solana: + case 'solana': return Solana.methods.values.toList(); - case ChainType.polkadot: + case 'polkadot': return Polkadot.methods.values.toList(); default: return []; } } -List getChainEvents(ChainType value) { - switch (value) { - case ChainType.eip155: +List getChainEvents(String namespace) { + switch (namespace) { + case 'eip155': return EIP155.events.values.toList(); - case ChainType.solana: + case 'solana': return Solana.events.values.toList(); - case ChainType.polkadot: + case 'polkadot': return Polkadot.events.values.toList(); default: return []; } } + +Future getParams( + String method, + String address, { + String? rpcUrl, +}) async { + switch (method) { + case 'personal_sign': + final bytes = utf8.encode(testSignData); + final encoded = bytesToHex(bytes, include0x: true); + return SessionRequestParams( + method: method, + params: [encoded, address], + ); + case 'eth_sign': + return SessionRequestParams( + method: method, + params: [address, testSignData], + ); + case 'eth_signTypedData': + return SessionRequestParams( + method: method, + params: [address, typedData], + ); + case 'eth_signTransaction': + return SessionRequestParams( + method: method, + params: [ + Transaction( + from: EthereumAddress.fromHex(address), + to: EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + value: EtherAmount.fromInt(EtherUnit.finney, 12), // == 0.012 + ).toJson(), + ], + ); + case 'eth_sendTransaction': + return SessionRequestParams( + method: method, + params: [ + Transaction( + from: EthereumAddress.fromHex(address), + to: EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + value: EtherAmount.fromInt(EtherUnit.finney, 12), // == 0.012 + ).toJson(), + ], + ); + case 'solana_signMessage': + final bytes = utf8.encode(testSignData); + final message = base58.encode(bytes); + return 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(rpcUrl!).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 SessionRequestParams( + method: method, + params: { + 'transaction': encodedV0Trx, + // 'pubkey': address, + // 'feePayer': address, + // ...transactionv0.message.toJson(), + }, + ); + default: + return null; + } +} diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart b/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart index 27eec4f..194aacb 100644 --- a/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart +++ b/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart @@ -18,17 +18,17 @@ class Polkadot { static final Map events = {}; static Future callMethod({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String method, - required String chainId, + required ReownAppKitModalNetworkInfo chainData, required String address, }) { switch (method) { case 'polkadot_signMessage': return appKit.request( topic: topic, - chainId: chainId, + chainId: chainData.chainId, request: SessionRequestParams( method: method, params: { @@ -41,7 +41,7 @@ class Polkadot { case 'polkadot_signTransaction': return appKit.request( topic: topic, - chainId: chainId, + chainId: chainData.chainId, request: SessionRequestParams( method: method, params: { diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart b/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart index 329d83c..57266e1 100644 --- a/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart +++ b/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart @@ -1,11 +1,6 @@ -import 'dart:convert'; -import 'package:bs58/bs58.dart'; -import 'package:solana_web3/solana_web3.dart' as solana; - +import 'package:reown_appkit_dapp/utils/crypto/helpers.dart'; import 'package:reown_appkit/reown_appkit.dart'; -import 'package:reown_appkit_dapp/models/chain_metadata.dart'; - enum SolanaMethods { solanaSignTransaction, solanaSignMessage, @@ -24,90 +19,28 @@ class Solana { static final Map events = {}; static Future callMethod({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String method, - required ChainMetadata chainData, + required ReownAppKitModalNetworkInfo 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, - }, - ), + request: (await getParams(method, address))!, ); 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(), - }, - ), + request: (await getParams( + method, + address, + rpcUrl: chainData.rpcUrl, + ))!, ); default: throw 'Method unimplemented'; 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 index 7e22515..10eedaa 100644 --- a/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart +++ b/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart @@ -1,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; class DeepLinkHandler { static const _methodChannel = MethodChannel( @@ -10,7 +10,7 @@ class DeepLinkHandler { 'com.walletconnect.flutterdapp/events', ); static final waiting = ValueNotifier(false); - static late IReownAppKit _appKit; + static late IReownAppKitModal _appKitModal; static void initListener() { if (kIsWeb) return; @@ -20,13 +20,13 @@ class DeepLinkHandler { onError: _onError, ); } catch (e) { - debugPrint('[SampleWallet] [DeepLinkHandler] checkInitialLink $e'); + debugPrint('[SampleDapp] checkInitialLink $e'); } } - static void init(IReownAppKit appKit) { + static void init(IReownAppKitModal appKitModal) { if (kIsWeb) return; - _appKit = appKit; + _appKitModal = appKitModal; } static void checkInitialLink() async { @@ -34,22 +34,22 @@ class DeepLinkHandler { try { _methodChannel.invokeMethod('initialLink'); } catch (e) { - debugPrint('[SampleWallet] [DeepLinkHandler] checkInitialLink $e'); + debugPrint('[SampleDapp] checkInitialLink $e'); } } static Uri get nativeUri => - Uri.parse(_appKit.metadata.redirect?.native ?? ''); + Uri.parse(_appKitModal.appKit!.metadata.redirect?.native ?? ''); static Uri get universalUri => - Uri.parse(_appKit.metadata.redirect?.universal ?? ''); + Uri.parse(_appKitModal.appKit!.metadata.redirect?.universal ?? ''); static String get host => universalUri.host; static void _onLink(dynamic link) async { + debugPrint('[SampleDapp] _onLink $link'); if (link == null) return; - final envelope = ReownCoreUtils.getSearchParamFromURL(link, 'wc_ev'); - if (envelope.isNotEmpty) { - debugPrint('[SampleDapp] is linkMode $link'); - await _appKit.dispatchEnvelope(link); + final handled = await _appKitModal.dispatchEnvelope(link); + if (!handled) { + debugPrint('[SampleDapp] _onLink not handled by AppKit'); } } diff --git a/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart b/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart index 885e8e5..cc3a800 100644 --- a/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart +++ b/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart @@ -23,7 +23,7 @@ class WCSampleWallets { 'name': 'RN Wallet (internal)', 'platform': ['ios', 'android'], 'id': '1234567890123456789012345678922', - 'schema': 'rn-web3wallet://wc', + 'schema': 'rn-web3wallet-internal://', 'bundleId': 'com.walletconnect.web3wallet.rnsample.internal', 'universal': 'https://appkit-lab.reown.com/rn_walletkit_internal', }, @@ -32,9 +32,8 @@ class WCSampleWallets { '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', + 'bundleId': 'com.reown.sample.wallet.internal', + 'universal': 'https://appkit-lab.reown.com/wallet_internal', }, ]; @@ -59,7 +58,7 @@ class WCSampleWallets { 'name': 'RN Wallet', 'platform': ['ios', 'android'], 'id': '123456789012345678901234567892', - 'schema': 'rn-web3wallet://wc', + 'schema': 'rn-web3wallet://', 'bundleId': 'com.walletconnect.web3wallet.rnsample', 'universal': 'https://appkit-lab.reown.com/rn_walletkit', }, @@ -68,9 +67,8 @@ class WCSampleWallets { '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', + 'bundleId': 'com.reown.sample.wallet', + 'universal': 'https://appkit-lab.reown.com/wallet_release', }, ]; diff --git a/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart b/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart index 9b20505..594d8a1 100644 --- a/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart +++ b/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart @@ -277,3 +277,783 @@ class SepoliaTestContract { } ]; } + +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' + } + ]; +} + +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/base/lib/utils/test_data.dart b/packages/reown_appkit/example/base/lib/utils/test_data.dart index cb78e1d..636ea9a 100644 --- a/packages/reown_appkit/example/base/lib/utils/test_data.dart +++ b/packages/reown_appkit/example/base/lib/utils/test_data.dart @@ -36,6 +36,166 @@ String testSignTypedData(String address) => jsonEncode( 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"}}'''; +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, +// ), +// ], +// ); + /// KADENA /// // SignRequest createSignRequest({ diff --git a/packages/reown_appkit/example/base/lib/widgets/chain_button.dart b/packages/reown_appkit/example/base/lib/widgets/chain_button.dart index b923c1e..2184019 100644 --- a/packages/reown_appkit/example/base/lib/widgets/chain_button.dart +++ b/packages/reown_appkit/example/base/lib/widgets/chain_button.dart @@ -1,7 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:reown_appkit_dapp/models/chain_metadata.dart'; +import 'package:reown_appkit/reown_appkit.dart'; import 'package:reown_appkit_dapp/utils/constants.dart'; class ChainButton extends StatelessWidget { @@ -12,7 +12,7 @@ class ChainButton extends StatelessWidget { this.selected = false, }); - final ChainMetadata chain; + final ReownAppKitModalNetworkInfo chain; final VoidCallback onPressed; final bool selected; @@ -24,19 +24,20 @@ class ChainButton extends StatelessWidget { 2) - 14.0, height: StyleConstants.linear48, - margin: const EdgeInsets.symmetric( - vertical: StyleConstants.linear8, + margin: const EdgeInsets.only( + bottom: StyleConstants.linear8, ), child: ElevatedButton( onPressed: onPressed, style: ButtonStyle( + elevation: MaterialStateProperty.all(0.0), backgroundColor: MaterialStateProperty.all( - selected ? Colors.grey.shade400 : Colors.white, + selected ? Colors.white : Colors.grey.shade300, ), shape: MaterialStateProperty.all( RoundedRectangleBorder( side: BorderSide( - color: selected ? Colors.grey.shade400 : chain.color, + color: selected ? Colors.blue : Colors.grey.shade300, width: selected ? 4 : 2, ), borderRadius: BorderRadius.circular( diff --git a/packages/reown_appkit/example/base/lib/widgets/session_widget.dart b/packages/reown_appkit/example/base/lib/widgets/session_widget.dart index 93e0989..86c7b32 100644 --- a/packages/reown_appkit/example/base/lib/widgets/session_widget.dart +++ b/packages/reown_appkit/example/base/lib/widgets/session_widget.dart @@ -1,8 +1,6 @@ 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'; @@ -14,23 +12,33 @@ import 'package:reown_appkit_dapp/widgets/method_dialog.dart'; class SessionWidget extends StatefulWidget { const SessionWidget({ super.key, - required this.session, - required this.appKit, + required this.sessionTopic, + required this.appKitModal, }); - final SessionData session; - final ReownAppKit appKit; + final String sessionTopic; + final ReownAppKitModal appKitModal; @override SessionWidgetState createState() => SessionWidgetState(); } class SessionWidgetState extends State { + late IReownAppKit _appKit; + late SessionData _session; + + @override + void initState() { + super.initState(); + _appKit = widget.appKitModal.appKit!; + _session = _appKit.sessions.get(widget.sessionTopic)!; + } + @override Widget build(BuildContext context) { final List children = [ Text( - '${StringConstants.sessionTopic}${widget.session.topic}', + '${StringConstants.sessionTopic}${_session.topic}', ), ]; @@ -38,7 +46,7 @@ class SessionWidgetState extends State { final List namespaceAccounts = []; // Loop through the namespaces, and get the accounts - for (final Namespace namespace in widget.session.namespaces.values) { + for (final Namespace namespace in _session.namespaces.values) { namespaceAccounts.addAll(namespace.accounts); } @@ -61,8 +69,8 @@ class SessionWidgetState extends State { ), child: ElevatedButton( onPressed: () async { - await widget.appKit.disconnectSession( - topic: widget.session.topic, + await _appKit.disconnectSession( + topic: _session.topic, reason: Errors.getSdkError( Errors.USER_DISCONNECTED, ).toSignError(), @@ -90,11 +98,17 @@ class SessionWidgetState extends State { Widget _buildAccountWidget(String namespaceAccount) { final chainId = NamespaceUtils.getChainFromAccount(namespaceAccount); final account = NamespaceUtils.getAccount(namespaceAccount); - final chainMetadata = getChainMetadataFromChain(chainId); + final namespace = NamespaceUtils.getNamespaceFromChain( + chainId, + ); + final chainData = ReownAppKitModalNetworks.getNetworkById( + namespace, + chainId.split(':').last, + ); final List children = [ Text( - chainMetadata.name, + chainData!.name, style: StyleConstants.subtitleText, ), const SizedBox( @@ -113,7 +127,7 @@ class SessionWidgetState extends State { ), ]; - children.addAll(_buildChainMethodButtons(chainMetadata, account)); + children.addAll(_buildChainMethodButtons(chainData, account)); children.add(const Divider()); @@ -131,11 +145,7 @@ class SessionWidgetState extends State { style: StyleConstants.subtitleText, ), ]); - children.addAll( - _buildChainEventsTiles( - chainMetadata, - ), - ); + children.addAll(_buildChainEventsTiles(chainData)); // final ChainMetadata return Container( @@ -149,7 +159,7 @@ class SessionWidgetState extends State { ), decoration: BoxDecoration( border: Border.all( - color: chainMetadata.color, + color: Colors.blue, ), borderRadius: const BorderRadius.all( Radius.circular( @@ -164,13 +174,16 @@ class SessionWidgetState extends State { } List _buildChainMethodButtons( - ChainMetadata chainMetadata, + ReownAppKitModalNetworkInfo 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 namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainMetadata.chainId, + ); + for (final String method in getChainMethods(namespace)) { + final namespaces = _session.namespaces[namespace]; final supported = namespaces?.methods.contains(method) ?? false; buttons.add( Container( @@ -195,7 +208,7 @@ class SessionWidgetState extends State { backgroundColor: MaterialStateProperty.resolveWith( (states) => states.contains(MaterialState.disabled) ? Colors.grey - : chainMetadata.color, + : Colors.blue, ), shape: MaterialStateProperty.all( RoundedRectangleBorder( @@ -220,39 +233,41 @@ class SessionWidgetState extends State { Future callChainMethod( String method, - ChainMetadata chainMetadata, + ReownAppKitModalNetworkInfo chainMetadata, String address, ) { - switch (chainMetadata.type) { - case ChainType.eip155: + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainMetadata.chainId, + ); + switch (namespace) { + case 'eip155': return EIP155.callMethod( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, method: method, chainData: chainMetadata, address: address, ); - case ChainType.polkadot: + case 'polkadot': return Polkadot.callMethod( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, method: method, - chainId: chainMetadata.chainId, + chainData: chainMetadata, address: address, ); - case ChainType.solana: + case 'solana': return Solana.callMethod( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, method: method, chainData: chainMetadata, address: address, - isV0: true, ); // case ChainType.kadena: // return Kadena.callMethod( - // appKit: widget.appKit, - // topic: widget.session.topic, + // appKit: _appKit, + // topic: _session.topic, // method: method.toKadenaMethod()!, // chainId: chainMetadata.chainId, // address: address.toLowerCase(), @@ -264,9 +279,9 @@ class SessionWidgetState extends State { void _launchWallet() { if (kIsWeb) return; - widget.appKit.redirectToWallet( - topic: widget.session.topic, - redirect: widget.session.peer.metadata.redirect, + _appKit.redirectToWallet( + topic: _session.topic, + redirect: _session.peer.metadata.redirect, ); } @@ -284,8 +299,8 @@ class SessionWidgetState extends State { onPressed: enabled ? () async { final future = EIP155.callSmartContract( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, address: address, action: 'read', ); @@ -326,8 +341,8 @@ class SessionWidgetState extends State { onPressed: enabled ? () async { final future = EIP155.callSmartContract( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, address: address, action: 'write', ); @@ -362,10 +377,12 @@ class SessionWidgetState extends State { return buttons; } - List _buildChainEventsTiles(ChainMetadata chainMetadata) { + List _buildChainEventsTiles(ReownAppKitModalNetworkInfo chainData) { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainData.chainId, + ); final List values = []; - - for (final String event in getChainEvents(chainMetadata.type)) { + for (final String event in getChainEvents(namespace)) { values.add( Container( width: double.infinity, @@ -375,7 +392,7 @@ class SessionWidgetState extends State { ), decoration: BoxDecoration( border: Border.all( - color: chainMetadata.color, + color: Colors.blue, ), borderRadius: const BorderRadius.all( Radius.circular( diff --git a/packages/reown_appkit/example/base/pubspec.yaml b/packages/reown_appkit/example/base/pubspec.yaml index 1934ff9..f928b0e 100644 --- a/packages/reown_appkit/example/base/pubspec.yaml +++ b/packages/reown_appkit/example/base/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: sdk: flutter intl: ^0.19.0 json_annotation: ^4.8.1 - package_info_plus: ^7.0.0 + package_info_plus: ^8.0.2 qr_flutter: ^4.0.0 reown_appkit: path: ../.. diff --git a/packages/reown_appkit/example/example.md b/packages/reown_appkit/example/example.md index 2abf4a8..e998461 100644 --- a/packages/reown_appkit/example/example.md +++ b/packages/reown_appkit/example/example.md @@ -38,7 +38,7 @@ class _MyHomePageState extends State { super.initState(); _appKitModal = ReownAppKitModal( context: context, - projectId: '07429........', + projectId: '074.....', metadata: const PairingMetadata( name: 'Example App', description: 'Example app description', @@ -51,7 +51,7 @@ class _MyHomePageState extends State { ), ); - _appKitModal.init(); + _appKitModal.init().then((value) => setState(() {})); } @override @@ -61,25 +61,26 @@ class _MyHomePageState extends State { backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), - body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AppKitModalNetworkSelectButton( - appKit: _appKitModal, - context: context, - ), - AppKitModalConnectButton( - appKit: _appKitModal, - context: context, - ), - Visibility( - visible: _appKitModal.isConnected, - child: AppKitModalAccountButton( + body: Center( + child: Column( + children: [ + AppKitModalNetworkSelectButton( appKit: _appKitModal, context: context, ), - ) - ], + AppKitModalConnectButton( + appKit: _appKitModal, + context: context, + ), + Visibility( + visible: _appKitModal.isConnected, + child: AppKitModalAccountButton( + appKit: _appKitModal, + context: context, + ), + ) + ], + ), ), ); } 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 index a56e8f2..dade05d 100644 --- a/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml +++ b/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml @@ -52,8 +52,8 @@ - - + + + + + + + + + + + + + + + + + + @@ -95,4 +111,4 @@ android:name="flutterEmbedding" android:value="2" /> - + \ No newline at end of file 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 index 4094c0c..877b2ca 100644 --- 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 @@ -1,6 +1,65 @@ package com.web3modal.flutterExample 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.web3modal.flutterExample/events" + private val methodsChannel = "com.web3modal.flutterExample/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/modal/ios/Podfile b/packages/reown_appkit/example/modal/ios/Podfile index 3e44f9c..9a3af86 100644 --- a/packages/reown_appkit/example/modal/ios/Podfile +++ b/packages/reown_appkit/example/modal/ios/Podfile @@ -40,5 +40,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end end end diff --git a/packages/reown_appkit/example/modal/ios/Podfile.lock b/packages/reown_appkit/example/modal/ios/Podfile.lock index 35be0e9..0d8a466 100644 --- a/packages/reown_appkit/example/modal/ios/Podfile.lock +++ b/packages/reown_appkit/example/modal/ios/Podfile.lock @@ -84,6 +84,6 @@ SPEC CHECKSUMS: url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1 -PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 +PODFILE CHECKSUM: 0a7d5b7d0e53420cb0284f7b2f171f93843b94d2 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 index cb7df21..0f7d8c5 100644 --- a/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/reown_appkit/example/modal/ios/Runner.xcodeproj/project.pbxproj @@ -7,15 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 0FC0BB3BB94AB404E045720B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 169E0B2740AE2BC995671894 /* 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 */; }; - 39AD9E7C47D2C2E726BC1CD1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67AE57406F4CAFF18576119B /* Pods_RunnerTests.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 8FA6AEDFDC4C361E30EC02B5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6989162A0A87467687F5F26F /* 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 */; }; + EDDE4E51D009541346361FC7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EBC2BC702D406D694ACF7E0 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,16 +45,19 @@ 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 = ""; }; - 16888CA3F81E677DDEC3A0CD /* 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 = ""; }; + 169E0B2740AE2BC995671894 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 178F7352ABF9CF9D6C0AC3E1 /* 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 = ""; }; + 28F59A072E15C899242AF2D5 /* 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 = ""; }; + 2EBC2BC702D406D694ACF7E0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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; }; - 362D7224BA747ECF2E28DB1F /* 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 = ""; }; + 34526704E4066B8A8B16487C /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 67AE57406F4CAFF18576119B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6989162A0A87467687F5F26F /* 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 = ""; }; + 7A0275FA2B710C9A4864BF04 /* 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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7EADC7CA6C879427CE8FE8E4 /* 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 = ""; }; 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; }; @@ -62,10 +65,7 @@ 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 = ""; }; - B2930B7741FEE25EBE53F9E1 /* 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 = ""; }; - D06C3C4B2781C786140748A0 /* 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 = ""; }; - E9367D8F7B7044CEE6C79C75 /* 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 = ""; }; - FBB093E541A895EE125D344E /* 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 = ""; }; + F67BA6AC18A21E534AD2806E /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -73,7 +73,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 39AD9E7C47D2C2E726BC1CD1 /* Pods_RunnerTests.framework in Frameworks */, + 0FC0BB3BB94AB404E045720B /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -81,22 +81,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8FA6AEDFDC4C361E30EC02B5 /* Pods_Runner.framework in Frameworks */, + EDDE4E51D009541346361FC7 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1FB98289B12D49950E73D65F /* Frameworks */ = { - isa = PBXGroup; - children = ( - 6989162A0A87467687F5F26F /* Pods_Runner.framework */, - 67AE57406F4CAFF18576119B /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -124,7 +115,7 @@ 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, E880F334C300E1CEFAFD6E4C /* Pods */, - 1FB98289B12D49950E73D65F /* Frameworks */, + E653A4133AC9A030D05E99B5 /* Frameworks */, ); sourceTree = ""; }; @@ -153,15 +144,24 @@ path = Runner; sourceTree = ""; }; + E653A4133AC9A030D05E99B5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 2EBC2BC702D406D694ACF7E0 /* Pods_Runner.framework */, + 169E0B2740AE2BC995671894 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; E880F334C300E1CEFAFD6E4C /* Pods */ = { isa = PBXGroup; children = ( - 16888CA3F81E677DDEC3A0CD /* Pods-Runner.debug.xcconfig */, - E9367D8F7B7044CEE6C79C75 /* Pods-Runner.release.xcconfig */, - B2930B7741FEE25EBE53F9E1 /* Pods-Runner.profile.xcconfig */, - D06C3C4B2781C786140748A0 /* Pods-RunnerTests.debug.xcconfig */, - 362D7224BA747ECF2E28DB1F /* Pods-RunnerTests.release.xcconfig */, - FBB093E541A895EE125D344E /* Pods-RunnerTests.profile.xcconfig */, + F67BA6AC18A21E534AD2806E /* Pods-Runner.debug.xcconfig */, + 28F59A072E15C899242AF2D5 /* Pods-Runner.release.xcconfig */, + 7A0275FA2B710C9A4864BF04 /* Pods-Runner.profile.xcconfig */, + 178F7352ABF9CF9D6C0AC3E1 /* Pods-RunnerTests.debug.xcconfig */, + 7EADC7CA6C879427CE8FE8E4 /* Pods-RunnerTests.release.xcconfig */, + 34526704E4066B8A8B16487C /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -173,7 +173,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - A651C144B5361BC3EF29C3BB /* [CP] Check Pods Manifest.lock */, + AF2FA7F17BAB05056A62906B /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 6D09732E2619FACF7894A4A1 /* Frameworks */, @@ -192,14 +192,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 19E33495D5B1E24E7C81DB51 /* [CP] Check Pods Manifest.lock */, + 65C32C19F7EBA60FB984317E /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - A8353A6875DADDB86DC82CE5 /* [CP] Embed Pods Frameworks */, + 81B204DD1922A787090F78E0 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -270,7 +270,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 19E33495D5B1E24E7C81DB51 /* [CP] Check Pods Manifest.lock */ = { + 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"; + }; + 65C32C19F7EBA60FB984317E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -292,21 +308,22 @@ 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 */ = { + 81B204DD1922A787090F78E0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "Thin Binary"; - outputPaths = ( + 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 = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -323,7 +340,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - A651C144B5361BC3EF29C3BB /* [CP] Check Pods Manifest.lock */ = { + AF2FA7F17BAB05056A62906B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -345,23 +362,6 @@ 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; }; - A8353A6875DADDB86DC82CE5 /* [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 */ @@ -492,7 +492,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D06C3C4B2781C786140748A0 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 178F7352ABF9CF9D6C0AC3E1 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -510,7 +510,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 362D7224BA747ECF2E28DB1F /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 7EADC7CA6C879427CE8FE8E4 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -526,7 +526,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FBB093E541A895EE125D344E /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 34526704E4066B8A8B16487C /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift b/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift index 81aa14a..9002210 100644 --- a/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift +++ b/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift @@ -4,11 +4,47 @@ import CoinbaseWalletSDK @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { + + private static let EVENTS_CHANNEL = "com.web3modal.flutterExample/events" + private static let METHODS_CHANNEL = "com.web3modal.flutterExample/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) } @@ -21,19 +57,61 @@ import CoinbaseWalletSDK } } - return super.application(app, open: url, options: options) + return linkStreamHandler.handleLink(url.absoluteString) } 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 { + if let url = userActivity.webpageURL, (try? CoinbaseWalletSDK.shared.handleResponse(url)) == true { return true } } } - return super.application(application, continue: userActivity, restorationHandler: restorationHandler) + 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/modal/ios/Runner/Info.plist b/packages/reown_appkit/example/modal/ios/Runner/Info.plist index 119d42e..a97c4c3 100644 --- a/packages/reown_appkit/example/modal/ios/Runner/Info.plist +++ b/packages/reown_appkit/example/modal/ios/Runner/Info.plist @@ -69,7 +69,6 @@ cbwallet okto bitkeep - hyperPay bitizen ape uniswap @@ -90,6 +89,7 @@ wcflutterwallet wcflutterwallet-internal rn-web3wallet + rn-web3wallet-internal LSRequiresIPhoneOS diff --git a/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements b/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements index 903def2..bfe855c 100644 --- a/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements +++ b/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements @@ -2,7 +2,9 @@ - aps-environment - development + com.apple.developer.associated-domains + + applinks:appkit-lab.reown.com + diff --git a/packages/reown_appkit/example/modal/lib/home_page.dart b/packages/reown_appkit/example/modal/lib/home_page.dart index a674459..f48418b 100644 --- a/packages/reown_appkit/example/modal/lib/home_page.dart +++ b/packages/reown_appkit/example/modal/lib/home_page.dart @@ -1,12 +1,13 @@ +import 'dart:convert'; import 'dart:developer'; import 'package:fl_toast/fl_toast.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:reown_appkit_example/services/deep_link_handler.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'; @@ -33,7 +34,7 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - final overlay = OverlayController(const Duration(milliseconds: 200)); + late OverlayController overlay; late ReownAppKitModal _appKitModal; late SIWESampleWebService _siweTestService; bool _initialized = false; @@ -43,7 +44,6 @@ class _MyHomePageState extends State { super.initState(); _siweTestService = SIWESampleWebService(); WidgetsBinding.instance.addPostFrameCallback((_) { - _toggleOverlay(); _initializeService(widget.prefs); }); } @@ -53,20 +53,17 @@ class _MyHomePageState extends State { } String get _flavor { - // String flavor = '-${const String.fromEnvironment('FLUTTER_APP_FLAVOR')}'; - // return flavor.replaceAll('-production', ''); final internal = widget.bundleId.endsWith('.internal'); final debug = widget.bundleId.endsWith('.debug'); if (internal || debug || kDebugMode) { - return 'internal'; + return '-internal'; } return ''; } String _universalLink() { - // TODO change /flutter_appkit to something else - Uri link = Uri.parse('https://appkit-lab.reown.com/flutter_appkit'); - if (_flavor.isNotEmpty) { + Uri link = Uri.parse('https://appkit-lab.reown.com/flutter_appkit_modal'); + if (_flavor.isNotEmpty && !kDebugMode) { return link.replace(path: '${link.path}_internal').toString(); } return link.toString(); @@ -88,7 +85,7 @@ class _MyHomePageState extends State { description: StringConstants.pageTitle, url: _universalLink(), icons: [ - 'https://docs.walletconnect.com/assets/images/web3modalLogo-2cee77e07851ba0a710b56d03d4d09dd.png' + 'https://raw.githubusercontent.com/reown-com/reown_flutter/refs/heads/develop/assets/appkit_logo.png', ], redirect: _constructRedirect(), ); @@ -168,8 +165,11 @@ class _MyHomePageState extends State { } 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; + final chainId = _appKitModal.selectedChain?.chainId ?? '1'; + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + final address = _appKitModal.session!.getAddress(namespace)!; return SIWESession(address: address, chains: [chainId]); } }, @@ -207,18 +207,56 @@ class _MyHomePageState extends State { final siweAuthValue = prefs.getBool('appkit_siwe_auth') ?? true; // See https://docs.reown.com/appkit/flutter/core/custom-chains - final testNetworks = ReownAppKitModalNetworks.test['eip155'] ?? []; - ReownAppKitModalNetworks.addNetworks('eip155', testNetworks); + // Add extra chains + // final extraChains = ReownAppKitModalNetworks.extra['eip155']!; + // ReownAppKitModalNetworks.addSupportedNetworks('eip155', extraChains); + // Remove every test network + // ReownAppKitModalNetworks.removeTestNetworks(); + if (siweAuthValue) { + // Remove Solana support + ReownAppKitModalNetworks.removeSupportedNetworks('solana'); + } else { + // Add custom chains + ReownAppKitModalNetworks.addSupportedNetworks('polkadot', [ + ReownAppKitModalNetworkInfo( + name: 'Polkadot', + chainId: '91b171bb158e2d3848fa23a9f1c25182', + chainIcon: 'https://cryptologos.cc/logos/polkadot-new-dot-logo.png', + currency: 'DOT', + rpcUrl: 'https://rpc.polkadot.io', + explorerUrl: 'https://polkadot.subscan.io', + ), + ReownAppKitModalNetworkInfo( + name: 'Westend', + chainId: 'e143f23803ac50e8f6f8e62695d1ce9e', + currency: 'DOT', + rpcUrl: 'https://westend-rpc.polkadot.io', + explorerUrl: 'https://westend.subscan.io', + isTestNetwork: true, + ), + ]); + } try { _appKitModal = ReownAppKitModal( context: context, projectId: DartDefines.projectId, - logLevel: LogLevel.error, + logLevel: LogLevel.all, metadata: _pairingMetadata(), siweConfig: _siweConfig(siweAuthValue), enableAnalytics: analyticsValue, // OPTIONAL - null by default - enableEmail: emailWalletValue, // OPTIONAL - false by default + featuresConfig: emailWalletValue + ? FeaturesConfig( + email: true, + socials: [ + AppKitSocialOption.Farcaster, + AppKitSocialOption.X, + AppKitSocialOption.Apple, + AppKitSocialOption.Discord, + ], + // showMainWallets: false, // OPTIONAL - true by default + ) + : null, // requiredNamespaces: {}, // optionalNamespaces: {}, // includedWalletIds: {}, @@ -230,11 +268,51 @@ class _MyHomePageState extends State { 'c03dfee351b6fcc421b4494ea33b9d4b92a984f87aa76d1663bb28705e95034a', // Uniswap '38f5d18bd8522c244bdd70cb4a68e0e718865155811c043f052fb9f1c51de662', // Bitget }, - // excludedWalletIds: { - // 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', // Coinbase - // }, + // excludedWalletIds: {}, // MORE WALLETS https://explorer.walletconnect.com/?type=wallet&chains=eip155%3A1 + // getBalanceFallback: () async { + // // This method will be triggered if getting the balance from our blockchain API fails + // // You could place here your own getBalance method + // return 0.123; + // }, + optionalNamespaces: siweAuthValue + ? null + : { + 'eip155': RequiredNamespace.fromJson({ + 'chains': ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: 'eip155', + ).map((chain) => 'eip155:${chain.chainId}').toList(), + 'methods': + NetworkUtils.defaultNetworkMethods['eip155']!.toList(), + 'events': + NetworkUtils.defaultNetworkEvents['eip155']!.toList(), + }), + 'solana': RequiredNamespace.fromJson({ + 'chains': ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: 'solana', + ).map((chain) => 'solana:${chain.chainId}').toList(), + 'methods': + NetworkUtils.defaultNetworkMethods['solana']!.toList(), + 'events': [], + }), + 'polkadot': RequiredNamespace.fromJson({ + 'chains': [ + 'polkadot:91b171bb158e2d3848fa23a9f1c25182', + 'polkadot:e143f23803ac50e8f6f8e62695d1ce9e' + ], + 'methods': [ + 'polkadot_signMessage', + 'polkadot_signTransaction', + ], + 'events': [] + }), + }, + ); + overlay = OverlayController( + const Duration(milliseconds: 200), + appKitModal: _appKitModal, ); + _toggleOverlay(); setState(() => _initialized = true); } on ReownAppKitModalException catch (e) { debugPrint('⛔️ ${e.message}'); @@ -260,25 +338,18 @@ class _MyHomePageState extends State { _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 || event.level == Level.error) { - // TODO send to mixpanel - log('${event.message}'); - } else { - debugPrint('${event.message}'); - } + DeepLinkHandler.init(_appKitModal); + DeepLinkHandler.checkInitialLink(); + + setState(() {}); } @override void dispose() { // - _appKitModal.appKit!.core.removeLogListener(_logListener); _appKitModal.appKit!.core.relayClient.onRelayClientConnect.unsubscribe( _onRelayClientConnect, ); @@ -304,7 +375,7 @@ class _MyHomePageState extends State { void _onModalConnect(ModalConnect? event) async { setState(() {}); - debugPrint('[ExampleApp] _onModalConnect ${event?.session.toJson()}'); + log('[ExampleApp] _onModalConnect ${jsonEncode(event?.session.toJson())}'); } void _onModalUpdate(ModalConnect? event) { @@ -312,17 +383,17 @@ class _MyHomePageState extends State { } void _onModalNetworkChange(ModalNetworkChange? event) { - debugPrint('[ExampleApp] _onModalNetworkChange ${event?.toString()}'); + log('[ExampleApp] _onModalNetworkChange ${event?.toString()}'); setState(() {}); } void _onModalDisconnect(ModalDisconnect? event) { - debugPrint('[ExampleApp] _onModalDisconnect ${event?.toString()}'); + log('[ExampleApp] _onModalDisconnect ${event?.toString()}'); setState(() {}); } void _onModalError(ModalError? event) { - debugPrint('[ExampleApp] _onModalError ${event?.toString()}'); + log('[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 @@ -352,7 +423,7 @@ class _MyHomePageState extends State { showTextToast(text: 'Relay connected', context: context); } - void _onRelayClientError(EventArgs? event) { + void _onRelayClientError(ErrorEvent? event) { setState(() {}); showTextToast(text: 'Relay disconnected', context: context); } @@ -367,6 +438,9 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { + if (!_initialized) { + return SizedBox.shrink(); + } return Scaffold( backgroundColor: ReownAppKitModalTheme.colorsOf(context).background125, appBar: AppBar( @@ -396,6 +470,7 @@ class _MyHomePageState extends State { toggleOverlay: _toggleOverlay, toggleBrightness: widget.toggleBrightness, toggleTheme: widget.toggleTheme, + appKitModal: _appKitModal, ), ), onEndDrawerChanged: (isOpen) { @@ -483,7 +558,7 @@ class _ConnectedView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ AppKitModalAccountButton( - appKit: appKit, + appKitModal: appKit, // custom: ValueListenableBuilder( // valueListenable: appKit.balanceNotifier, // builder: (_, balance, __) { @@ -496,6 +571,20 @@ class _ConnectedView extends StatelessWidget { // }, // ), ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppKitModalBalanceButton( + appKitModal: appKit, + onTap: appKit.openNetworksView, + ), + const SizedBox.square(dimension: 8.0), + AppKitModalAddressButton( + appKitModal: appKit, + onTap: appKit.openModalView, + ), + ], + ), 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 index f4d292d..7142b46 100644 --- a/packages/reown_appkit/example/modal/lib/main.dart +++ b/packages/reown_appkit/example/modal/lib/main.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; import 'package:reown_appkit_example/home_page.dart'; +import 'package:reown_appkit_example/services/deep_link_handler.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() { + WidgetsFlutterBinding.ensureInitialized(); + DeepLinkHandler.initListener(); runApp(const MyApp()); } diff --git a/packages/reown_appkit/example/modal/lib/services/deep_link_handler.dart b/packages/reown_appkit/example/modal/lib/services/deep_link_handler.dart new file mode 100644 index 0000000..afcc105 --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/services/deep_link_handler.dart @@ -0,0 +1,60 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; + +class DeepLinkHandler { + static const _methodChannel = MethodChannel( + 'com.web3modal.flutterExample/methods', + ); + static const _eventChannel = EventChannel( + 'com.web3modal.flutterExample/events', + ); + static final waiting = ValueNotifier(false); + static late IReownAppKitModal _appKitModal; + + static void initListener() { + if (kIsWeb) return; + try { + _eventChannel.receiveBroadcastStream().listen( + _onLink, + onError: _onError, + ); + } catch (e) { + debugPrint('[SampleModal] initListener $e'); + } + } + + static void init(IReownAppKitModal appKitModal) { + if (kIsWeb) return; + _appKitModal = appKitModal; + } + + static void checkInitialLink() async { + if (kIsWeb) return; + try { + _methodChannel.invokeMethod('initialLink'); + } catch (e) { + debugPrint('[SampleModal] checkInitialLink $e'); + } + } + + static Uri get nativeUri => + Uri.parse(_appKitModal.appKit!.metadata.redirect?.native ?? ''); + static Uri get universalUri => + Uri.parse(_appKitModal.appKit!.metadata.redirect?.universal ?? ''); + static String get host => universalUri.host; + + static void _onLink(dynamic link) async { + debugPrint('[SampleModal] _onLink $link'); + if (link == null) return; + final handled = await _appKitModal.dispatchEnvelope(link); + if (!handled) { + debugPrint('[SampleModal] _onLink not handled by AppKit'); + } + } + + static void _onError(dynamic error) { + debugPrint('[SampleModal] _onError $error'); + waiting.value = false; + } +} diff --git a/packages/reown_appkit/example/modal/lib/services/eip155_service.dart b/packages/reown_appkit/example/modal/lib/services/methods_service.dart similarity index 57% rename from packages/reown_appkit/example/modal/lib/services/eip155_service.dart rename to packages/reown_appkit/example/modal/lib/services/methods_service.dart index 4fd8c3b..2dab1fe 100644 --- a/packages/reown_appkit/example/modal/lib/services/eip155_service.dart +++ b/packages/reown_appkit/example/modal/lib/services/methods_service.dart @@ -5,11 +5,13 @@ import 'package:reown_appkit/reown_appkit.dart'; // ignore: depend_on_referenced_packages import 'package:convert/convert.dart'; +// ignore: depend_on_referenced_packages +import 'package:bs58/bs58.dart'; import 'package:reown_appkit_example/services/contracts/aave_contract.dart'; import 'package:reown_appkit_example/services/contracts/test_data.dart'; -enum EIP155UIMethods { +enum SupportedMethods { personalSign, ethSendTransaction, requestAccounts, @@ -17,7 +19,8 @@ enum EIP155UIMethods { ethSignTypedDataV3, ethSignTypedDataV4, ethSignTransaction, - walletWatchAsset; + walletWatchAsset, + solanaSignMessage; String get name { switch (this) { @@ -37,71 +40,76 @@ enum EIP155UIMethods { return 'eth_signTransaction'; case walletWatchAsset: return 'wallet_watchAsset'; + case solanaSignMessage: + return 'solana_signMessage'; } } } -class EIP155 { - static EIP155UIMethods methodFromName(String name) { +class MethodsService { + static SupportedMethods methodFromName(String name) { switch (name) { case 'personal_sign': - return EIP155UIMethods.personalSign; + return SupportedMethods.personalSign; case 'eth_signTypedData_v4': - return EIP155UIMethods.ethSignTypedDataV4; + return SupportedMethods.ethSignTypedDataV4; case 'eth_sendTransaction': - return EIP155UIMethods.ethSendTransaction; + return SupportedMethods.ethSendTransaction; case 'eth_requestAccounts': - return EIP155UIMethods.requestAccounts; + return SupportedMethods.requestAccounts; case 'eth_signTypedData_v3': - return EIP155UIMethods.ethSignTypedDataV3; + return SupportedMethods.ethSignTypedDataV3; case 'eth_signTypedData': - return EIP155UIMethods.ethSignTypedData; + return SupportedMethods.ethSignTypedData; case 'eth_signTransaction': - return EIP155UIMethods.ethSignTransaction; + return SupportedMethods.ethSignTransaction; case 'wallet_watchAsset': - return EIP155UIMethods.walletWatchAsset; + return SupportedMethods.walletWatchAsset; + case 'solana_signMessage': + return SupportedMethods.solanaSignMessage; default: - throw Exception('Unrecognized method'); + throw Exception('Method not implemented'); } } static Future callMethod({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String topic, - required EIP155UIMethods method, + required String method, required String chainId, required String address, }) { - final cid = int.parse(chainId); - switch (method) { - case EIP155UIMethods.requestAccounts: + // final cid = int.parse(chainId); + final supportedMethod = MethodsService.methodFromName(method); + switch (supportedMethod) { + case SupportedMethods.requestAccounts: return requestAccounts( - appKit: appKit, + appKitModal: appKitModal, ); - case EIP155UIMethods.personalSign: + case SupportedMethods.personalSign: return personalSign( - appKit: appKit, + appKitModal: appKitModal, message: testSignData, ); - case EIP155UIMethods.ethSignTypedDataV3: + case SupportedMethods.ethSignTypedDataV3: return ethSignTypedDataV3( - appKit: appKit, - data: jsonEncode(typeDataV3(cid)), + appKitModal: appKitModal, + data: jsonEncode(typeDataV3(int.parse(chainId))), ); - case EIP155UIMethods.ethSignTypedData: + case SupportedMethods.ethSignTypedData: return ethSignTypedData( - appKit: appKit, + appKitModal: appKitModal, data: jsonEncode(typedData()), ); - case EIP155UIMethods.ethSignTypedDataV4: + case SupportedMethods.ethSignTypedDataV4: return ethSignTypedDataV4( - appKit: appKit, - data: jsonEncode(typeDataV4(cid)), + appKitModal: appKitModal, + data: jsonEncode(typeDataV4(int.parse(chainId))), ); - case EIP155UIMethods.ethSignTransaction: - case EIP155UIMethods.ethSendTransaction: + case SupportedMethods.ethSignTransaction: + case SupportedMethods.ethSendTransaction: return ethSendOrSignTransaction( - appKit: appKit, + appKitModal: appKitModal, method: method, transaction: Transaction( from: EthereumAddress.fromHex(address), @@ -112,107 +120,145 @@ class EIP155 { data: utf8.encode('0x'), // to make it work with some wallets ), ); - case EIP155UIMethods.walletWatchAsset: + case SupportedMethods.walletWatchAsset: return walletWatchAsset( - appKit: appKit, + appKitModal: appKitModal, + ); + case SupportedMethods.solanaSignMessage: + return solanaSignMessage( + appKitModal: appKitModal, + message: testSignData, ); } } static Future requestAccounts({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( - method: EIP155UIMethods.requestAccounts.name, + method: SupportedMethods.requestAccounts.name, params: [], ), ); } static Future personalSign({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String message, }) async { final bytes = utf8.encode(message); final encoded = hex.encode(bytes); + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + appKitModal.selectedChain!.chainId, + ); - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( - method: EIP155UIMethods.personalSign.name, + method: SupportedMethods.personalSign.name, params: [ '0x$encoded', - appKit.session!.address!, + appKitModal.session!.getAddress(namespace)!, ], ), ); } + static Future solanaSignMessage({ + required ReownAppKitModal appKitModal, + required String message, + }) async { + final bytes = utf8.encode(testSignData); + final message = base58.encode(bytes); + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + appKitModal.selectedChain!.chainId, + ); + final address = appKitModal.session!.getAddress(namespace)!; + + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, + request: SessionRequestParams( + method: SupportedMethods.solanaSignMessage.name, + params: {'pubkey': address, 'message': message}, + ), + ); + } + static Future ethSignTypedData({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String data, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + appKitModal.selectedChain!.chainId, + ); + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( - method: EIP155UIMethods.ethSignTypedData.name, + method: SupportedMethods.ethSignTypedData.name, params: [ data, - appKit.session!.address!, + appKitModal.session!.getAddress(namespace)!, ], ), ); } static Future ethSignTypedDataV3({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String data, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + appKitModal.selectedChain!.chainId, + ); + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( - method: EIP155UIMethods.ethSignTypedDataV3.name, + method: SupportedMethods.ethSignTypedDataV3.name, params: [ data, - appKit.session!.address!, + appKitModal.session!.getAddress(namespace)!, ], ), ); } static Future ethSignTypedDataV4({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String data, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + appKitModal.selectedChain!.chainId, + ); + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( - method: EIP155UIMethods.ethSignTypedDataV4.name, + method: SupportedMethods.ethSignTypedDataV4.name, params: [ data, - appKit.session!.address!, + appKitModal.session!.getAddress(namespace)!, ], ), ); } static Future ethSendOrSignTransaction({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required Transaction transaction, - required EIP155UIMethods method, + required String method, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( - method: method.name, + method: method, params: [ transaction.toJson(), ], @@ -221,13 +267,13 @@ class EIP155 { } static Future walletWatchAsset({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( - method: EIP155UIMethods.walletWatchAsset.name, + method: SupportedMethods.walletWatchAsset.name, params: { 'type': 'ERC20', 'options': { @@ -244,7 +290,7 @@ class EIP155 { // Example of calling `transfer` function from AAVE token Smart Contract static Future callTestSmartContract({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String action, }) async { // Create DeployedContract object using contract's ABI and address @@ -259,7 +305,7 @@ class EIP155 { switch (action) { case 'read': return _readSmartContract( - appKit: appKit, + appKitModal: appKitModal, contract: deployedContract, ); case 'write': @@ -276,22 +322,27 @@ class EIP155 { // ); // 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, + final decimals = await appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.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, + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + appKitModal.selectedChain!.chainId, + ); + return appKitModal.requestWriteContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: deployedContract, functionName: 'transfer', transaction: Transaction( - from: EthereumAddress.fromHex(appKit.session!.address!), + from: EthereumAddress.fromHex( + appKitModal.session!.getAddress(namespace)!, + ), ), parameters: [ EthereumAddress.fromHex( @@ -327,7 +378,7 @@ class EIP155 { // Example of calling `transfer` function from USDT token Smart Contract static Future callUSDTSmartContract({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String action, }) async { // Create DeployedContract object using contract's ABI and address @@ -342,28 +393,33 @@ class EIP155 { switch (action) { case 'read': return _readSmartContract( - appKit: appKit, + appKitModal: appKitModal, 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, + final decimals = await appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.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, + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + appKitModal.selectedChain!.chainId, + ); + return appKitModal.requestWriteContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: deployedContract, functionName: 'transfer', transaction: Transaction( - from: EthereumAddress.fromHex(appKit.session!.address!), + from: EthereumAddress.fromHex( + appKitModal.session!.getAddress(namespace)!, + ), ), parameters: [ EthereumAddress.fromHex( @@ -378,38 +434,41 @@ class EIP155 { } static Future _readSmartContract({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required DeployedContract contract, }) async { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + appKitModal.selectedChain!.chainId, + ); final results = await Future.wait([ // results[0] - appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: contract, functionName: 'name', ), // results[1] - appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: contract, functionName: 'totalSupply', ), // results[2] - appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: contract, functionName: 'balanceOf', parameters: [ - EthereumAddress.fromHex(appKit.session!.address!), + EthereumAddress.fromHex(appKitModal.session!.getAddress(namespace)!), ], ), // results[4] - appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: contract, functionName: 'decimals', ), diff --git a/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart b/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart index 4cc98f1..c74f71a 100644 --- a/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart +++ b/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart @@ -1,24 +1,32 @@ +// ignore_for_file: depend_on_referenced_packages + 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'; +import 'package:reown_appkit/version.dart' as apkt_v; + +import 'package:reown_sign/version.dart' as sign_v; +import 'package:reown_core/version.dart' as core_v; + class DebugDrawer extends StatefulWidget { const DebugDrawer({ super.key, required this.toggleOverlay, required this.toggleBrightness, required this.toggleTheme, + required this.appKitModal, }); final VoidCallback toggleOverlay; final VoidCallback toggleBrightness; final VoidCallback toggleTheme; + final ReownAppKitModal appKitModal; @override State createState() => _DebugDrawerState(); @@ -174,7 +182,7 @@ class _DebugDrawerState extends State with WidgetsBindingObserver { color: ReownAppKitModalTheme.colorsOf(context).foreground100, ), - title: const Text('Analytics On'), + title: const Text('Analytics'), titleTextStyle: TextStyle( color: ReownAppKitModalTheme.colorsOf(context).foreground100, @@ -195,7 +203,7 @@ class _DebugDrawerState extends State with WidgetsBindingObserver { color: ReownAppKitModalTheme.colorsOf(context).foreground100, ), - title: const Text('Email Wallet On'), + title: const Text('Email & Socials'), titleTextStyle: TextStyle( color: ReownAppKitModalTheme.colorsOf(context).foreground100, @@ -216,7 +224,7 @@ class _DebugDrawerState extends State with WidgetsBindingObserver { color: ReownAppKitModalTheme.colorsOf(context).foreground100, ), - title: const Text('1-CA + SIWE On'), + title: const Text('1-CA + SIWE'), titleTextStyle: TextStyle( color: ReownAppKitModalTheme.colorsOf(context).foreground100, @@ -234,32 +242,105 @@ class _DebugDrawerState extends State with WidgetsBindingObserver { ], ), ), - 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', + const SizedBox(height: 16.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Redirect:', + textAlign: TextAlign.left, style: TextStyle( fontSize: 12.0, - color: - ReownAppKitModalTheme.colorsOf(context).foreground100, + fontWeight: FontWeight.bold, ), ), - ); - }, + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Native: ', + style: TextStyle(fontSize: 12.0), + ), + Expanded( + child: Text( + '${widget.appKitModal.appKit!.metadata.redirect?.native}', + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.bold, + color: ReownAppKitModalTheme.colorsOf(context) + .foreground100, + ), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Universal: ', + style: TextStyle(fontSize: 12.0), + ), + Expanded( + child: Text( + '${widget.appKitModal.appKit!.metadata.redirect?.universal}', + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.bold, + color: ReownAppKitModalTheme.colorsOf(context) + .foreground100, + ), + ), + ), + ], + ), + Row( + children: [ + const Text( + 'Link Mode: ', + style: TextStyle(fontSize: 12.0), + ), + Text( + '${widget.appKitModal.appKit!.metadata.redirect?.linkMode}', + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.bold, + color: ReownAppKitModalTheme.colorsOf(context) + .foreground100, + ), + ), + ], + ), + const Divider(height: 10.0), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + final versionText = + '${snapshot.data?.packageName} v${snapshot.data?.version ?? ''} (${snapshot.data?.buildNumber})\n' + 'AppKit v${apkt_v.packageVersion}\n' + 'Sign v${sign_v.packageVersion}\n' + 'Core v${core_v.packageVersion}'; + return InkWell( + onTap: () => Clipboard.setData(ClipboardData( + text: versionText, + )), + child: Text( + versionText, + style: TextStyle( + fontSize: 12.0, + color: ReownAppKitModalTheme.colorsOf(context) + .foreground100, + ), + ), + ); + }, + ), + ], + ), ), const SizedBox.square(dimension: 10.0), const Divider(height: 1.0, indent: 12.0, endIndent: 12.0), diff --git a/packages/reown_appkit/example/modal/lib/widgets/logger_widget.dart b/packages/reown_appkit/example/modal/lib/widgets/logger_widget.dart index 8bb0855..cdbd8f5 100644 --- a/packages/reown_appkit/example/modal/lib/widgets/logger_widget.dart +++ b/packages/reown_appkit/example/modal/lib/widgets/logger_widget.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; class DraggableCard extends StatefulWidget { final OverlayController overlayController; + // const DraggableCard({ super.key, required this.overlayController, @@ -19,14 +20,20 @@ class _DraggableCardState extends State { @override void initState() { super.initState(); - analyticsService.instance.events.listen(_eventsListener); + widget.overlayController.appKitModal.appKit!.core.addLogListener( + _eventsListener, + ); } - void _eventsListener(event) { + void _eventsListener(String event) { if (!mounted) return; + if (!event.toString().contains('[AnalyticsService]')) return; + String message = event.replaceAll('[AnalyticsService] ', ''); + message = message.replaceAll('send event 202: ', ''); + message = message.replaceAll('info: ', ''); _logs.add( Text( - '=> $event', + '=> $message', style: const TextStyle( color: Colors.white, fontSize: 12.0, @@ -38,6 +45,9 @@ class _DraggableCardState extends State { @override void dispose() { + widget.overlayController.appKitModal.appKit!.core.removeLogListener( + _eventsListener, + ); widget.overlayController.remove(); super.dispose(); } @@ -55,12 +65,12 @@ class _DraggableCardState extends State { children: [ SizedBox( width: MediaQuery.of(context).size.width, - height: 200.0, + height: 300.0, child: Row( children: [ Expanded( child: SizedBox( - height: 200.0, + height: 300.0, child: ListView( reverse: true, padding: const EdgeInsets.all(6.0), @@ -99,7 +109,11 @@ class _DraggableCardState extends State { } class OverlayController extends AnimatedOverlay { - OverlayController(super.duration); + OverlayController( + super.duration, { + required this.appKitModal, + }); + final ReownAppKitModal appKitModal; OverlayEntry? _entry; final _defaultAlign = const Alignment(0.0, -30.0); Alignment align = const Alignment(0.0, -30.0); diff --git a/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart b/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart index ed8fb45..c1b5a4d 100644 --- a/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart +++ b/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart @@ -3,13 +3,14 @@ import 'dart:convert'; import 'package:fl_toast/fl_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:reown_appkit/modal/utils/core_utils.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/services/methods_service.dart'; import 'package:reown_appkit_example/widgets/method_dialog.dart'; class SessionWidget extends StatefulWidget { @@ -38,7 +39,12 @@ class SessionWidgetState extends State { children: [ CircleAvatar( radius: 18.0, - backgroundImage: NetworkImage(iconImage), + backgroundImage: NetworkImage( + iconImage, + headers: CoreUtils.getAPIHeaders( + widget.appKit.appKit!.core.projectId, + ), + ), ), const SizedBox(width: 8.0), ], @@ -102,15 +108,17 @@ class SessionWidgetState extends State { ), ), Column( - children: _buildSupportedChainsWidget(), + children: _buildSupportedChainsWidget( + widget.appKit.selectedChain!.chainId, + ), ), const SizedBox(height: StyleConstants.linear8), ]; // Get current active account - final accounts = session.getAccounts() ?? []; final chainId = widget.appKit.selectedChain?.chainId ?? ''; final namespace = ReownAppKitModalNetworks.getNamespaceForChainId(chainId); + final accounts = session.getAccounts(namespace: namespace) ?? []; final chainsNamespaces = NamespaceUtils.getChainsFromAccounts(accounts); if (chainsNamespaces.contains('$namespace:$chainId')) { final account = accounts.firstWhere( @@ -165,7 +173,7 @@ class SessionWidgetState extends State { } Widget _buildAccountWidget(String account) { - // final chainId = NamespaceUtils.getChainFromAccount(account); + final chainId = NamespaceUtils.getChainFromAccount(account); // final chainMetadata = ChainDataWrapper.getChainMetadataFromChain( // chainId.split(':').first, // chainId.split(':').last, @@ -173,7 +181,6 @@ class SessionWidgetState extends State { final List children = [ Text( - // chainMetadata.appKitNetworkInfo.name, widget.appKit.selectedChain?.name ?? 'Unsupported chain', style: ReownAppKitModalTheme.getDataOf(context) .textStyles @@ -220,7 +227,7 @@ class SessionWidgetState extends State { ), ), ]); - children.add(_buildChainEventsTiles()); + children.add(_buildChainEventsTiles(chainId)); return Container( padding: const EdgeInsets.all(StyleConstants.linear8), @@ -239,12 +246,14 @@ class SessionWidgetState extends State { ); } - List _buildChainMethodButtons( - String address, - ) { + List _buildChainMethodButtons(String address) { // Add Methods - final approvedMethods = widget.appKit.getApprovedMethods() ?? []; - if (approvedMethods.isEmpty) { + final chainId = NamespaceUtils.getChainFromAccount(address); + final namespace = NamespaceUtils.getNamespaceFromChain(chainId); + final approvedMethods = widget.appKit.getApprovedMethods( + namespace: namespace, + ); + if ((approvedMethods ?? []).isEmpty) { return [ Text( 'No methods approved', @@ -257,10 +266,10 @@ class SessionWidgetState extends State { ) ]; } - final usableMethods = EIP155UIMethods.values.map((e) => e.name).toList(); + final usableMethods = SupportedMethods.values.map((e) => e.name).toList(); // final List children = []; - for (final method in approvedMethods) { + for (final method in (approvedMethods ?? [])) { final implemented = usableMethods.contains(method); children.add( Container( @@ -271,12 +280,7 @@ class SessionWidgetState extends State { onPressed: implemented ? () async { widget.appKit.launchConnectedWallet(); - final future = callChainMethod( - // chainMetadata.type, - EIP155.methodFromName(method) - // chainMetadata, - // address, - ); + final future = callChainMethod(method); MethodDialog.show(context, method, future); } : null, @@ -287,6 +291,16 @@ class SessionWidgetState extends State { ); } + if (namespace == 'eip155') { + children.addAll(_addSmartContractButtons()); + } + + return children; + } + + List _addSmartContractButtons() { + final List children = []; + children.add(const Divider()); final onSepolia = widget.appKit.selectedChain?.chainId == '11155111'; if (!onSepolia) { @@ -314,8 +328,8 @@ class SessionWidgetState extends State { child: ElevatedButton( onPressed: onSepolia ? () async { - final future = EIP155.callTestSmartContract( - appKit: widget.appKit, + final future = MethodsService.callTestSmartContract( + appKitModal: widget.appKit, action: 'read', ); MethodDialog.show( @@ -326,8 +340,8 @@ class SessionWidgetState extends State { } : onMainnet ? () async { - final future = EIP155.callUSDTSmartContract( - appKit: widget.appKit, + final future = MethodsService.callUSDTSmartContract( + appKitModal: widget.appKit, action: 'read', ); MethodDialog.show( @@ -351,8 +365,8 @@ class SessionWidgetState extends State { onPressed: onSepolia ? () async { widget.appKit.launchConnectedWallet(); - final future = EIP155.callTestSmartContract( - appKit: widget.appKit, + final future = MethodsService.callTestSmartContract( + appKitModal: widget.appKit, action: 'write', ); MethodDialog.show(context, 'Test Contract (Write)', future); @@ -360,8 +374,8 @@ class SessionWidgetState extends State { : onMainnet ? () async { widget.appKit.launchConnectedWallet(); - final future = EIP155.callUSDTSmartContract( - appKit: widget.appKit, + final future = MethodsService.callUSDTSmartContract( + appKitModal: widget.appKit, action: 'write', ); MethodDialog.show( @@ -379,7 +393,7 @@ class SessionWidgetState extends State { return children; } - List _buildSupportedChainsWidget() { + List _buildSupportedChainsWidget(String chainId) { List children = []; children.addAll( [ @@ -395,10 +409,11 @@ class SessionWidgetState extends State { ), ], ); - final approvedChains = widget.appKit.getApprovedChains() ?? []; + final ns = ReownAppKitModalNetworks.getNamespaceForChainId(chainId); + final approvedChains = widget.appKit.getApprovedChains(namespace: ns); children.add( Text( - approvedChains.join(', '), + (approvedChains ?? []).join(', '), style: ReownAppKitModalTheme.getDataOf(context) .textStyles .small400 @@ -410,10 +425,13 @@ class SessionWidgetState extends State { return children; } - Widget _buildChainEventsTiles() { + Widget _buildChainEventsTiles(String chainId) { // Add Events - final approvedEvents = widget.appKit.getApprovedEvents() ?? []; - if (approvedEvents.isEmpty) { + final namespace = NamespaceUtils.getNamespaceFromChain(chainId); + final approvedEvents = widget.appKit.getApprovedEvents( + namespace: namespace, + ); + if ((approvedEvents ?? []).isEmpty) { return Text( 'No events approved', style: ReownAppKitModalTheme.getDataOf(context) @@ -425,7 +443,7 @@ class SessionWidgetState extends State { ); } final List children = []; - for (final event in approvedEvents) { + for (final event in (approvedEvents ?? [])) { children.add( Container( margin: const EdgeInsets.symmetric( @@ -457,19 +475,17 @@ class SessionWidgetState extends State { ); } - Future callChainMethod( - // ChainType type, - EIP155UIMethods method, - // ChainMetadata chainMetadata, - // String address, - ) { + Future callChainMethod(String method) { final session = widget.appKit.session!; - return EIP155.callMethod( - appKit: widget.appKit, + final chainId = widget.appKit.selectedChain!.chainId; + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId(chainId); + final address = session.getAddress(namespace)!; + return MethodsService.callMethod( + appKitModal: widget.appKit, topic: session.topic ?? '', method: method, chainId: widget.appKit.selectedChain!.chainId, - address: session.address!, + address: address, ); } } diff --git a/packages/reown_appkit/generate_files.sh b/packages/reown_appkit/generate_files.sh index aa02878..b1b0287 100644 --- a/packages/reown_appkit/generate_files.sh +++ b/packages/reown_appkit/generate_files.sh @@ -20,8 +20,9 @@ dart run dependency_validator cd ios -pod deintegrate -pod cache clean -all +# rm Podfile.lock +# pod deintegrate +# pod cache clean -all pod install cd .. @@ -42,8 +43,9 @@ dart run dependency_validator cd ios -pod deintegrate -pod cache clean -all +# rm Podfile.lock +# pod deintegrate +# pod cache clean -all pod install cd .. diff --git a/packages/reown_appkit/lib/appkit_modal.dart b/packages/reown_appkit/lib/appkit_modal.dart index 04bc671..a58eeb6 100644 --- a/packages/reown_appkit/lib/appkit_modal.dart +++ b/packages/reown_appkit/lib/appkit_modal.dart @@ -5,20 +5,20 @@ // platforms in the `pubspec.yaml` at // https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms. -/// Models +// Models export 'modal/models/public/appkit_modal_models.dart'; -/// Theme +// Theme export 'modal/theme/public/appkit_modal_theme.dart'; /// Utils -export 'modal/utils/public/appkit_modal_utils.dart'; +export 'modal/utils/public/appkit_modal_networks_utils.dart'; -/// Widgets +// Widgets export 'modal/widgets/public/appkit_modal_widgets.dart'; -/// Pages +// Pages export 'modal/pages/public/appkit_modal_pages.dart'; -/// Services +// 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 index bc82c2e..a4ce1ec 100644 --- a/packages/reown_appkit/lib/base/appkit_base_impl.dart +++ b/packages/reown_appkit/lib/base/appkit_base_impl.dart @@ -3,19 +3,23 @@ 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'; +/// Base class that containes Core and SIgn used dapps developers to create UI-less interaction with WalletConnect protocol class ReownAppKit implements IReownAppKit { + /// static const List> DEFAULT_METHODS = [ [ MethodConstants.WC_SESSION_PROPOSE, MethodConstants.WC_SESSION_REQUEST, ], // [ + // // Deprecated method but still supported for retrocompatibility // MethodConstants.WC_AUTH_REQUEST, // ] ]; bool _initialized = false; + /// Creates a instance of ReownAppKit to be used alone or to pass to ReownAppKitModal static Future createInstance({ required String projectId, String relayUrl = ReownConstants.DEFAULT_RELAY_URL, @@ -53,6 +57,7 @@ class ReownAppKit implements IReownAppKit { @override final PairingMetadata metadata; + /// ReownAppKit({ required this.core, required this.metadata, diff --git a/packages/reown_appkit/lib/base/i_appkit_base_impl.dart b/packages/reown_appkit/lib/base/i_appkit_base_impl.dart index 929e00a..9bfb963 100644 --- a/packages/reown_appkit/lib/base/i_appkit_base_impl.dart +++ b/packages/reown_appkit/lib/base/i_appkit_base_impl.dart @@ -1,9 +1,14 @@ 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 index 32b6fbb..eb4f700 100644 --- a/packages/reown_appkit/lib/modal/appkit_modal_impl.dart +++ b/packages/reown_appkit/lib/modal/appkit_modal_impl.dart @@ -1,50 +1,50 @@ 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:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/analytics_service/i_analytics_service.dart'; +import 'package:reown_appkit/modal/services/explorer_service/i_explorer_service.dart'; +import 'package:reown_appkit/modal/services/network_service/i_network_service.dart'; +import 'package:reown_appkit/modal/services/siwe_service/i_siwe_service.dart'; +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service.dart'; +import 'package:reown_appkit/modal/services/uri_service/i_url_utils.dart'; + +import 'package:reown_core/store/i_store.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_appkit/modal/services/blockchain_service/i_blockchain_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/network_service/network_service.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 @@ -55,6 +55,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { Map _optionalNamespaces = {}; String? _lastChainEmitted; bool _supportsOneClickAuth = false; + bool _relayConnected = false; ReownAppKitModalStatus _status = ReownAppKitModalStatus.idle; @override @@ -64,8 +65,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { @override ReownAppKitModalNetworkInfo? get selectedChain { if (_currentSelectedChainId != null) { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + _currentSelectedChainId!, + ); return ReownAppKitModalNetworks.getNetworkById( - CoreConstants.namespace, + namespace, _currentSelectedChainId!, ); } @@ -93,10 +97,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { String? get avatarUrl => _avatarUrl; double? _chainBalance; + @Deprecated('Use balanceNotifier') @override - String get chainBalance { - return CoreUtils.formatChainBalance(_chainBalance); - } + String get chainBalance => CoreUtils.formatChainBalance(_chainBalance); @override final balanceNotifier = ValueNotifier('-.--'); @@ -113,8 +116,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { @override ReownAppKitModalSession? get session => _currentSession; - Logger get _logger => _appKit.core.logger; - IStore> get _storage => _appKit.core.storage; bool _disconnectOnClose = false; @@ -128,20 +129,27 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { return null; } + @override + late final FeaturesConfig featuresConfig; + + /// + late final Future Function()? _getBalance; + Completer _awaitRelayOnce = Completer(); + ReownAppKitModal({ required BuildContext context, IReownAppKit? appKit, String? projectId, PairingMetadata? metadata, + bool? enableAnalytics, SIWEConfig? siweConfig, + FeaturesConfig? featuresConfig, Map? requiredNamespaces, Map? optionalNamespaces, Set? featuredWalletIds, Set? includedWalletIds, Set? excludedWalletIds, - bool? enableAnalytics, - bool enableEmail = false, - List blockchains = const [], + Future Function()? getBalanceFallback, LogLevel logLevel = LogLevel.nothing, }) { if (appKit == null) { @@ -156,79 +164,117 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { ); } } - // if (siweConfig?.enabled == true && context == null) { - // throw ReownAppKitModalException( - // '`context:` parameter is required if using `siweConfig:`. Also, `context:` parameter will be enforced in future versions.', - // ); - // } + + this.featuresConfig = featuresConfig ?? FeaturesConfig(email: false); _context = context; + _getBalance = getBalanceFallback; _appKit = appKit ?? ReownAppKit( - core: ReownCore(projectId: projectId!), + core: ReownCore( + projectId: projectId!, + logLevel: logLevel, + ), metadata: metadata!, ); _projectId = _appKit.core.projectId; + // TODO should be moved to init() _setRequiredNamespaces(requiredNamespaces); _setOptionalNamespaces(optionalNamespaces); - uriService.instance = UriService( - core: _appKit.core, + GetIt.I.registerSingletonIfAbsent( + () => 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, + GetIt.I.registerSingletonIfAbsent( + () => AnalyticsService( + core: _appKit.core, + enableAnalytics: enableAnalytics, + ), ); - - blockchainService.instance = BlockChainService( - core: _appKit.core, + // TODO should be moved to init() + _analyticsService.init().then( + (_) => _analyticsService.sendEvent(ModalLoadedEvent()), + ); + GetIt.I.registerSingletonIfAbsent( + () => ExplorerService( + core: _appKit.core, + referer: _appKit.metadata.name.replaceAll(' ', ''), + featuredWalletIds: featuredWalletIds, + includedWalletIds: includedWalletIds, + excludedWalletIds: excludedWalletIds, + namespaces: {..._requiredNamespaces, ..._optionalNamespaces}, + ), ); - - magicService.instance = MagicService( - core: _appKit.core, - metadata: _appKit.metadata, - enabled: enableEmail, + GetIt.I.registerSingletonIfAbsent(() => NetworkService()); + GetIt.I.registerSingletonIfAbsent(() => ToastService()); + GetIt.I.registerSingletonIfAbsent( + () => BlockChainService( + core: _appKit.core, + ), ); - - coinbaseService.instance = CoinbaseService( - core: _appKit.core, - metadata: _appKit.metadata, - enabled: _initializeCoinbaseSDK, + GetIt.I.registerSingletonIfAbsent( + () => MagicService( + core: _appKit.core, + metadata: _appKit.metadata, + featuresConfig: this.featuresConfig, + ), ); - - siweService.instance = SiweService( - appKit: _appKit, - siweConfig: siweConfig, + GetIt.I.registerSingletonIfAbsent( + () => CoinbaseService( + core: _appKit.core, + metadata: _appKit.metadata, + enabled: _initializeCoinbaseSDK, + ), + ); + GetIt.I.registerSingletonIfAbsent( + () => SiweService( + appKit: _appKit, + siweConfig: siweConfig, + namespaces: {..._requiredNamespaces, ..._optionalNamespaces}, + ), ); } + IUriService get _uriService => GetIt.I(); + IToastService get _toastService => GetIt.I(); + IAnalyticsService get _analyticsService => GetIt.I(); + IExplorerService get _explorerService => GetIt.I(); + INetworkService get _networkService => GetIt.I(); + IMagicService get _magicService => GetIt.I(); + IBlockChainService get _blockchainService => GetIt.I(); + ICoinbaseService get _coinbaseService => GetIt.I(); + ISiweService get _siweService => GetIt.I(); + ////////* PUBLIC METHODS *///////// - bool _serviceInitialized = false; + @override + Future dispatchEnvelope(String url) async { + _appKit.core.logger.d('[$runtimeType] dispatchEnvelope $url'); + final envelope = ReownCoreUtils.getSearchParamFromURL(url, 'wc_ev'); + if (envelope.isNotEmpty) { + await _appKit.dispatchEnvelope(url); + return true; + } + + final state = ReownCoreUtils.getSearchParamFromURL(url, 'state'); + if (state.isNotEmpty) { + _magicService.completeSocialLogin(url: url); + return true; + } + + return false; + } @override Future init() async { - _serviceInitialized = false; + _relayConnected = false; + _awaitRelayOnce = Completer(); + 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; + throw 'Please provide a valid projectId ($_projectId).' + 'See ${UrlConstants.docsUrl}/appkit/flutter/core/usage for details.'; } if (_status == ReownAppKitModalStatus.initializing || _status == ReownAppKitModalStatus.initialized) { @@ -240,10 +286,10 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _registerListeners(); await _appKit.init(); - await networkService.instance.init(); - await explorerService.instance.init(); - await coinbaseService.instance.init(); - await blockchainService.instance.init(); + await _networkService.init(); + await _explorerService.init(); + await _coinbaseService.init(); + await _blockchainService.init(); _currentSession = await _getStoredSession(); _currentSelectedChainId = _getStoredChainId(null); @@ -253,10 +299,13 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _currentSelectedChainId ??= _currentSession!.chainId; await _setSesionAndChainData(_currentSession!); if (isMagic) { - await magicService.instance.init(); + final caip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + _currentSelectedChainId!, + ); + await _magicService.init(chainId: caip2Chain); } } else { - magicService.instance.init(); + _magicService.init(); } await expirePreviousInactivePairings(); @@ -265,13 +314,17 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final wcSessions = _appKit.sessions.getAll(); // Loop through all the chain data - final allNetworks = ReownAppKitModalNetworks.getNetworks( - CoreConstants.namespace, - ); + final allNetworks = ReownAppKitModalNetworks.getAllSupportedNetworks(); for (final chain in allNetworks) { - for (final event in EventsConstants.allEvents) { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chain.chainId, + ); + final requiredEvents = _requiredNamespaces[namespace]?.events ?? []; + final optionalEvents = _optionalNamespaces[namespace]?.events ?? []; + final events = [...requiredEvents, ...optionalEvents]; + for (final event in events) { _appKit.registerEventHandler( - chainId: '${CoreConstants.namespace}:${chain.chainId}', + chainId: '$namespace:${chain.chainId}', event: event, ); } @@ -279,8 +332,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { // There's a session stored if (wcSessions.isNotEmpty) { - await _storeSession( - ReownAppKitModalSession(sessionData: wcSessions.first)); + await _storeSession(ReownAppKitModalSession( + sessionData: wcSessions.first, + )); // session should not outlive the pairing if (wcPairings.isEmpty) { await disconnect(); @@ -291,7 +345,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { // Check for other type of sessions stored if (_currentSession != null) { if (_currentSession!.sessionService.isCoinbase) { - final isCbConnected = await coinbaseService.instance.isConnected(); + final isCbConnected = await _coinbaseService.isConnected(); if (!isCbConnected) { await _cleanSession(); } @@ -299,7 +353,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { // 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); + _magicService.setEmail(email); + final provider = _currentSession!.socialProvider; + _magicService.setProvider(provider); } else { await _cleanSession(); } @@ -310,23 +366,30 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { await _selectChainFromStoredId(); onModalNetworkChange.subscribe(_onNetworkChainRequireSIWE); + onModalConnect.subscribe(_loadAccountData); - final connected = _appKit.core.relayClient.isConnected; - _serviceInitialized = connected; - _status = connected + _relayConnected = _appKit.core.relayClient.isConnected; + if (!_relayConnected) { + _relayConnected = await _awaitRelayOnce.future; + } else { + if (!_awaitRelayOnce.isCompleted) { + _awaitRelayOnce.complete(_relayConnected); + } + } + _status = _relayConnected ? ReownAppKitModalStatus.initialized - : ReownAppKitModalStatus.error; - _logger.i('[$runtimeType] initialized'); + : ReownAppKitModalStatus.initializing; + _appKit.core.logger.i('[$runtimeType] status $_status'); _notify(); } Future _checkSIWEStatus() async { // check if SIWE is enabled and should still sign the message - if (siweService.instance!.enabled) { + if (_siweService.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 siweSession = await _siweService.getSession(); final session = _currentSession!.copyWith(siweSession: siweSession); await _setSesionAndChainData(session); _notify(); @@ -339,17 +402,13 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } - Future _setSesionAndChainData( - ReownAppKitModalSession modalSession) async { + Future _setSesionAndChainData(ReownAppKitModalSession mSession) async { try { - await _storeSession(modalSession); - _currentSelectedChainId = _currentSelectedChainId ?? modalSession.chainId; + await _storeSession(mSession); + _currentSelectedChainId = _currentSelectedChainId ?? mSession.chainId; await _setLocalEthChain(_currentSelectedChainId!, logEvent: false); - } catch (e, s) { - _logger.e( - '[$runtimeType] _setSesionAndChainData error $e', - stackTrace: s, - ); + } catch (e) { + _appKit.core.logger.e('[$runtimeType] _setSesionAndChainData error $e'); } } @@ -357,12 +416,15 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { try { if (_storage.has(StorageConstants.modalSession)) { final storedSession = _storage.get(StorageConstants.modalSession); + _appKit.core.logger.i( + '[$runtimeType] _getStoredSession, storedSession: $storedSession, key: ${StorageConstants.modalSession}', + ); if (storedSession != null) { return ReownAppKitModalSession.fromMap(storedSession); } } } catch (e) { - _logger.e('[$runtimeType] _getStoredSession error: $e'); + _appKit.core.logger.e('[$runtimeType] _getStoredSession error: $e'); } return null; } @@ -375,7 +437,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _currentSession!.toMap(), ); } catch (e) { - _logger.e('[$runtimeType] _storeSession error: $e'); + _appKit.core.logger.e('[$runtimeType] _storeSession error: $e'); } // _isConnected shoudl probably go at the very end of the connection _isConnected = true; @@ -385,8 +447,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { if (_currentSession != null) { final chainId = _getStoredChainId(null); if (chainId != null) { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); final chain = ReownAppKitModalNetworks.getNetworkById( - CoreConstants.namespace, + namespace, chainId, ); if (chain != null) { @@ -416,20 +481,23 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { return; } - _chainBalance = null; + try { + _chainBalance = null; + final formattedBalance = CoreUtils.formatChainBalance(_chainBalance); + balanceNotifier.value = '$formattedBalance ${chainInfo.currency}'; - 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}', + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainInfo.chainId, + ); + final approvedChains = _currentSession!.getApprovedChains( + namespace: namespace, + ); + final newCaip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + chainInfo.chainId, ); + final hasChainAlready = (approvedChains ?? []).contains(newCaip2Chain); if (!hasChainAlready) { requestSwitchToChain(chainInfo); final hasSwitchMethod = _currentSession!.hasSwitchMethod(); @@ -442,6 +510,12 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } else { await _setLocalEthChain(chainInfo.chainId, logEvent: logEvent); } + } on JsonRpcError catch (e) { + onModalError.broadcast(ModalError(e.message ?? 'An error occurred')); + } on ReownAppKitModalException catch (e) { + onModalError.broadcast(ModalError(e.message)); + } catch (e) { + onModalError.broadcast(ModalError('An error occurred')); } } @@ -449,53 +523,80 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { @override List? getAvailableChains() { // if there's no session or if supportsAddChain method then every chain can be used - if (_currentSession == null || _currentSession!.hasSwitchMethod()) { + if (_currentSession == null) { + // meaning all chains in the list are available return null; } - return getApprovedChains(); + // Valid only for EVM chains + final hasSwitchMethod = _currentSession!.hasSwitchMethod(); + if (!hasSwitchMethod) { + return getApprovedChains(); + } + + final isMagic = _currentSession!.sessionService.isMagic; + final isCoinbase = _currentSession!.sessionService.isCoinbase; + if (isMagic || isCoinbase) { + return getApprovedChains(); + } + + List availableChains = []; + final namespaces = ReownAppKitModalNetworks.getAllSupportedNamespaces(); + for (var ns in namespaces) { + final chains = + ReownAppKitModalNetworks.getAllSupportedNetworks(namespace: ns) + .map((e) => '$ns:${e.chainId}') + .toList(); + availableChains.addAll(chains); + } + if (availableChains.isEmpty) { + return getApprovedChains(); + } + + return availableChains; } /// Will get the list of already approved chains by the wallet (to switch to) @override - List? getApprovedChains() { + List? getApprovedChains({String? namespace}) { if (_currentSession == null) { return null; } - return _currentSession!.getApprovedChains(); + return _currentSession!.getApprovedChains(namespace: namespace); } @override - List? getApprovedMethods() { + List? getApprovedMethods({String? namespace}) { if (_currentSession == null) { return null; } - return _currentSession!.getApprovedMethods(); + return _currentSession!.getApprovedMethods(namespace: namespace); } @override - List? getApprovedEvents() { + List? getApprovedEvents({String? namespace}) { if (_currentSession == null) { return null; } - return _currentSession!.getApprovedEvents(); + return _currentSession!.getApprovedEvents(namespace: namespace); } Future _setLocalEthChain(String chainId, {bool? logEvent}) async { _currentSelectedChainId = chainId; - final caip2Chain = '${CoreConstants.namespace}:$_currentSelectedChainId'; - _logger.i('[$runtimeType] set local chain $caip2Chain'); - _currentSelectedChainId = chainId; + final caip2Chain = ReownAppKitModalNetworks.getCaip2Chain(chainId); + _appKit.core.logger.d('[$runtimeType] _setLocalEthChain $caip2Chain'); _notify(); try { - await _storage.set( - StorageConstants.selectedChainId, - {'chainId': _currentSelectedChainId!}, - ); + if (isConnected) { + await _storage.set( + StorageConstants.selectedChainId, + {'chainId': _currentSelectedChainId ?? '1'}, + ); + } } catch (e) { - _logger.e('[$runtimeType] _setLocalEthChain error: $e'); + _appKit.core.logger.e('[$runtimeType] _setLocalEthChain error: $e'); } if (_isConnected && logEvent == true) { - analyticsService.instance.sendEvent(SwitchNetworkEvent( + _analyticsService.sendEvent(SwitchNetworkEvent( network: _currentSelectedChainId!, )); } @@ -550,6 +651,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { KeyConstants.confirmEmailPage, KeyConstants.selectNetworkPage, KeyConstants.accountPage, + KeyConstants.socialLoginPage, ]; final List _allowedScreensWhenDisconnected = [ @@ -564,7 +666,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { Widget? startWidget, }) async { _checkInitialized(); - ApproveTransactionPage(); if (_isOpen) { closeModal(); @@ -574,24 +675,24 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _context = context ?? modalContext; if (_context == null) { - _logger.e( + _appKit.core.logger.e( 'No context was found. ' 'Try adding `context:` parameter in ReownAppKitModal class', ); return; } - analyticsService.instance.sendEvent(ModalOpenEvent( + _analyticsService.sendEvent(ModalOpenEvent( connected: _isConnected, )); // Reset the explorer - explorerService.instance.search(query: null); + _explorerService.search(query: null); widgetStack.instance.clear(); final isBottomSheet = PlatformUtils.isBottomSheet(); final theme = ReownAppKitModalTheme.maybeOf(_context!); - await magicService.instance.syncTheme(theme); + await _magicService.syncTheme(theme); final themeData = theme?.themeData ?? const ReownAppKitModalThemeData(); Widget? showWidget = startWidget; @@ -680,14 +781,14 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { name: walletName, platform: inBrowser ? AnalyticsPlatform.web : AnalyticsPlatform.mobile, ); - analyticsService.instance.sendEvent(event); + _analyticsService.sendEvent(event); } @override Future connectSelectedWallet({bool inBrowser = false}) async { _checkInitialized(); - final walletRedirect = explorerService.instance.getWalletRedirect( + final walletRedirect = _explorerService.getWalletRedirect( selectedWallet, ); @@ -704,15 +805,20 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } try { if (_selectedWallet!.isCoinbase) { - await coinbaseService.instance.getAccount(); - await explorerService.instance.storeConnectedWallet(_selectedWallet); + await _coinbaseService.getAccount(); + await _explorerService.storeConnectedWallet(_selectedWallet); } else { await buildConnectionUri(); - await uriService.instance.openRedirect( - walletRedirect, - wcURI: wcUri!, - pType: pType, - ); + final linkMode = walletRedirect.linkMode ?? ''; + if (linkMode.isNotEmpty && _wcUri.startsWith(linkMode)) { + await ReownCoreUtils.openURL(_wcUri); + } else { + await _uriService.openRedirect( + walletRedirect, + wcURI: _wcUri, + pType: pType, + ); + } } } on LaunchUrlException catch (e) { if (e is CanNotLaunchUrl) { @@ -733,28 +839,25 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { onModalError.broadcast(WalletNotInstalled()); } else { if (_isUserRejectedError(e)) { - _logger.i('[$runtimeType] User declined connection'); onModalError.broadcast(UserRejectedConnection()); - analyticsService.instance.sendEvent(ConnectErrorEvent( + _analyticsService.sendEvent(ConnectErrorEvent( message: 'User declined connection', )); } else { onModalError.broadcast(ErrorOpeningWallet()); - analyticsService.instance.sendEvent(ConnectErrorEvent( + _analyticsService.sendEvent(ConnectErrorEvent( message: e.message, )); } } } else if (_isUserRejectedError(e)) { - _logger.i('[$runtimeType] User declined connection'); onModalError.broadcast(UserRejectedConnection()); - analyticsService.instance.sendEvent(ConnectErrorEvent( + _analyticsService.sendEvent(ConnectErrorEvent( message: 'User declined connection', )); } else { - _logger.e( - '[$runtimeType] Error connecting wallet', - error: e, + _appKit.core.logger.e( + '[$runtimeType] connectSelectedWallet error: $e', stackTrace: s, ); onModalError.broadcast(ErrorOpeningWallet()); @@ -765,27 +868,31 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { @override Future buildConnectionUri() async { if (!_isConnected) { + /// TODO Qs: How do I handle SIWE if non-EVM chains are included? + /// TODO Qs: How do I handle switch to Solana from EVM chain? try { - if (siweService.instance!.enabled) { - final walletRedirect = explorerService.instance.getWalletRedirect( + if (_siweService.enabled) { + final walletRedirect = _explorerService.getWalletRedirect( selectedWallet, ); - final nonce = await siweService.instance!.getNonce(); - final p1 = await siweService.instance!.config!.getMessageParams(); - final methods = p1.methods ?? MethodsConstants.allMethods; + final nonce = await _siweService.getNonce(); + final p1 = await _siweService.config!.getMessageParams(); + final methods = + p1.methods ?? NetworkUtils.defaultNetworkMethods['eip155']; // - final supportedNetworks = ReownAppKitModalNetworks.getNetworks( - CoreConstants.namespace, - ); - final chains = supportedNetworks - .map((e) => '${CoreConstants.namespace}:${e.chainId}') - .toList(); + final networks = ReownAppKitModalNetworks.getAllSupportedNetworks(); + final chains = networks.map((chain) { + return ReownAppKitModalNetworks.getCaip2Chain(chain.chainId); + }).toList(); final p2 = {'nonce': nonce, 'chains': chains, 'methods': methods}; final authParams = SessionAuthRequestParams.fromJson({ ...p1.toJson(), ...p2, }); // One-Click Auth + _appKit.core.logger.d( + '[$runtimeType] authenticate ${jsonEncode(authParams.toJson())}, ${walletRedirect?.linkMode}', + ); final authResponse = await _appKit.authenticate( params: authParams, walletUniversalLink: walletRedirect?.linkMode, @@ -804,7 +911,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _awaitConnectionCallback(connectResponse); } } catch (e) { - _logger.e('[$runtimeType] buildConnectionUri $e'); + _appKit.core.logger.e('[$runtimeType] buildConnectionUri error: $e'); } } } @@ -813,7 +920,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { try { final _ = await connectResponse.session.future; } on TimeoutException { - _logger.i('[$runtimeType] Rebuilding session, ending future'); + _appKit.core.logger.i('[$runtimeType] Rebuilding session, ending future'); return; } catch (e) { await _connectionErrorHandler(e); @@ -838,7 +945,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } } on TimeoutException { - _logger.i('[$runtimeType] Rebuilding session, ending future'); + _appKit.core.logger.i('[$runtimeType] Rebuilding session, ending future'); return; } catch (e) { await disconnect(); @@ -848,9 +955,8 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { Future _connectionErrorHandler(dynamic e) async { if (_isUserRejectedError(e)) { - _logger.i('[$runtimeType] User declined connection'); onModalError.broadcast(UserRejectedConnection()); - analyticsService.instance.sendEvent(ConnectErrorEvent( + _analyticsService.sendEvent(ConnectErrorEvent( message: 'User declined connection', )); } else { @@ -863,17 +969,15 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { return; } onModalError.broadcast(ModalError('Error connecting to wallet')); - analyticsService.instance.sendEvent(ConnectErrorEvent( + _analyticsService.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( + _analyticsService.sendEvent(ConnectErrorEvent( message: e.message, )); - _logger.e('[$runtimeType] ${e.message}', error: e); } } return await expirePreviousInactivePairings(); @@ -883,7 +987,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { void launchConnectedWallet() async { _checkInitialized(); - final walletInfo = explorerService.instance.getConnectedWallet(); + final walletInfo = _explorerService.getConnectedWallet(); if (walletInfo == null) { // if walletInfo is null could mean that either // 1. There's no wallet connected (shouldn't happen) @@ -905,7 +1009,15 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final metadataRedirect = _currentSession!.peer?.metadata.redirect; - final walletRedirect = explorerService.instance.getWalletRedirect( + final appLink = (metadataRedirect?.universal ?? ''); + final supportedApps = _appKit.core.getLinkModeSupportedApps(); + final isLinkMode = appLink.isNotEmpty && supportedApps.contains(appLink); + if (isLinkMode) { + // Opening peers during Link Mode requests is handled in Sign Engine + return; + } + + final walletRedirect = _explorerService.getWalletRedirect( walletInfo, ); @@ -915,7 +1027,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { try { final link = metadataRedirect?.native ?? metadataRedirect?.universal; - uriService.instance.openRedirect( + _uriService.openRedirect( walletRedirect.copyWith(mobile: link), pType: PlatformUtils.getPlatformType(), ); @@ -940,17 +1052,20 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { if (_currentSession?.sessionService.isCoinbase == true) { try { - await coinbaseService.instance.resetSession(); - } catch (_) { + await _coinbaseService.resetSession(); + } catch (e) { + _appKit.core.logger.d('[$runtimeType] disconnect coinbase $e'); _status = ReownAppKitModalStatus.initialized; _notify(); return; } } if (_currentSession?.sessionService.isMagic == true) { - await Future.delayed(Duration(milliseconds: 300)); - final disconnected = await magicService.instance.disconnect(); - if (!disconnected) { + try { + await Future.delayed(Duration(milliseconds: 300)); + await _magicService.disconnect(); + } catch (e) { + _appKit.core.logger.d('[$runtimeType] disconnect magic $e'); _status = ReownAppKitModalStatus.initialized; _notify(); return; @@ -971,19 +1086,19 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { ); } try { - if (siweService.instance!.signOutOnDisconnect) { - await siweService.instance!.signOut(); + if (_siweService.signOutOnDisconnect) { + await _siweService.signOut(); } } catch (_) {} - analyticsService.instance.sendEvent(DisconnectSuccessEvent()); + _analyticsService.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()); + _analyticsService.sendEvent(DisconnectErrorEvent()); _status = ReownAppKitModalStatus.initialized; _notify(); } @@ -1008,19 +1123,19 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { return; } _isOpen = false; + final currentKey = widgetStack.instance.getCurrent().key; if (_disconnectOnClose) { _disconnectOnClose = false; - final currentKey = widgetStack.instance.getCurrent().key; if (currentKey == KeyConstants.approveSiwePageKey) { - analyticsService.instance.sendEvent(ClickCancelSiwe( + _analyticsService.sendEvent(ClickCancelSiwe( network: _currentSelectedChainId ?? '', )); } await disconnect(); selectWallet(null); } - toastService.instance.clear(); - analyticsService.instance.sendEvent(ModalCloseEvent( + _toastService.clear(); + _analyticsService.sendEvent(ModalCloseEvent( connected: _isConnected, )); } @@ -1034,12 +1149,12 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { 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, + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + selectedChain!.chainId, ); + final address = _currentSession?.getAddress(namespace) ?? ''; + final explorerUrl = '$blockExplorer/address/$address'; + await ReownCoreUtils.openURL(explorerUrl); } } @@ -1059,7 +1174,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final isValidChainId = NamespaceUtils.isValidChainId(chainId); if (!isValidChainId) { if (selectedChain!.chainId == chainId) { - reqChainId = '${CoreConstants.namespace}:$chainId'; + reqChainId = ReownAppKitModalNetworks.getCaip2Chain(chainId); } else { throw Errors.getSdkError( Errors.UNSUPPORTED_CHAINS, @@ -1068,7 +1183,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } // - _logger.d('[$runtimeType] requestWriteContract, chainId: $reqChainId'); + _appKit.core.logger.i( + '[$runtimeType] requestReadContract, chainId: $reqChainId', + ); final networkInfo = ReownAppKitModalNetworks.getNetworkById( reqChainId.split(':').first, @@ -1110,7 +1227,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final isValidChainId = NamespaceUtils.isValidChainId(chainId); if (!isValidChainId) { if (selectedChain!.chainId == chainId) { - reqChainId = '${CoreConstants.namespace}:$chainId'; + reqChainId = ReownAppKitModalNetworks.getCaip2Chain(chainId); } else { throw Errors.getSdkError( Errors.UNSUPPORTED_CHAINS, @@ -1119,7 +1236,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } // - _logger.d('[$runtimeType] requestWriteContract, chainId: $reqChainId'); + _appKit.core.logger.i( + '[$runtimeType] requestWriteContract, chainId: $reqChainId', + ); try { return await _appKit.requestWriteContract( @@ -1150,7 +1269,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final isValidChainId = NamespaceUtils.isValidChainId(chainId); if (!isValidChainId) { if (selectedChain!.chainId == chainId) { - reqChainId = '${CoreConstants.namespace}:$chainId'; + reqChainId = ReownAppKitModalNetworks.getCaip2Chain(chainId); } else { throw Errors.getSdkError( Errors.UNSUPPORTED_CHAINS, @@ -1159,19 +1278,19 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } // - _logger.d( + _appKit.core.logger.d( '[$runtimeType] request, chainId: $reqChainId, ' '${jsonEncode(request.toJson())}', ); try { if (_currentSession!.sessionService.isMagic) { - return await magicService.instance.request( + return await _magicService.request( chainId: reqChainId, request: request, ); } if (_currentSession!.sessionService.isCoinbase) { - return await await coinbaseService.instance.request( + return await await _coinbaseService.request( chainId: switchToChainId ?? reqChainId, request: request, ); @@ -1183,7 +1302,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { ); } catch (e) { if (_isUserRejectedError(e)) { - _logger.i('[$runtimeType] User declined request'); onModalError.broadcast(UserRejectedConnection()); if (request.method == MethodsConstants.walletSwitchEthChain || request.method == MethodsConstants.walletAddEthChain) { @@ -1205,11 +1323,20 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { @override Future dispose() async { if (_status == ReownAppKitModalStatus.initialized) { - await disconnect(); - await expirePreviousInactivePairings(); _unregisterListeners(); + await expirePreviousInactivePairings(); + await disconnect(); + await _appKit.core.relayClient.disconnect(); + _relayConnected = false; + _isConnected = false; + _currentSelectedChainId = null; + _requiredNamespaces = {}; + _optionalNamespaces = {}; + _lastChainEmitted = null; + _supportsOneClickAuth = false; _status = ReownAppKitModalStatus.idle; - _logger.d('[$runtimeType] dispose'); + await Future.delayed(Duration(milliseconds: 500)); + _notify(); } super.dispose(); } @@ -1244,8 +1371,8 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { bool get _initializeCoinbaseSDK { final cbId = CoinbaseService.defaultWalletData.listing.id; - final included = (explorerService.instance.includedWalletIds ?? {}); - final excluded = (explorerService.instance.excludedWalletIds ?? {}); + final included = (_explorerService.includedWalletIds ?? {}); + final excluded = (_explorerService.excludedWalletIds ?? {}); if (included.isNotEmpty) { return included.contains(cbId); @@ -1274,15 +1401,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { // Set the required namespaces to everything in our chain presets _requiredNamespaces = {}; } - - final wrongNamespace = _requiredNamespaces.keys.firstWhereOrNull( - (k) => k != CoreConstants.namespace, + _appKit.core.logger.d( + '[$runtimeType] required namespaces ${jsonEncode(_requiredNamespaces)}', ); - if (wrongNamespace != null) { - throw ReownAppKitModalException('Only eip155 blockains are supported'); - } - - _logger.d('[$runtimeType] _requiredNamespaces $_requiredNamespaces'); } void _setOptionalNamespaces(Map? optionalNSpaces) { @@ -1300,27 +1421,22 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { ); } else { // Set the optional namespaces to everything in our chain presets - final namespaces = ReownAppKitModalNetworks.supported.keys; + final namespaces = ReownAppKitModalNetworks.getAllSupportedNamespaces(); for (var ns in namespaces) { - final chains = ReownAppKitModalNetworks.supported[ns] ?? []; + final networks = ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: ns, + ); _optionalNamespaces[ns] = RequiredNamespace( - chains: chains.map((e) => '$ns:${e.chainId}').toList(), - methods: MethodsConstants.allMethods.toSet().toList(), - events: EventsConstants.allEvents.toSet().toList(), + chains: networks.map((e) => '$ns:${e.chainId}').toList(), + methods: NetworkUtils.defaultNetworkMethods[ns] ?? [], + events: NetworkUtils.defaultNetworkEvents[ns] ?? [], ); } } - final wrongNamespace = _optionalNamespaces.keys.firstWhereOrNull( - (k) => k != CoreConstants.namespace, + _appKit.core.logger.d( + '[$runtimeType] optional namespaces ${jsonEncode(_optionalNamespaces)}', ); - if (wrongNamespace != null) { - throw ReownAppKitModalException( - 'Only ${CoreConstants.namespace} networks are supported', - ); - } - - _logger.d('[$runtimeType] _optionalNamespaces $_optionalNamespaces'); } /// Loads account balance and avatar. @@ -1328,51 +1444,74 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { @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) { + if (_currentSelectedChainId == null || _currentSession == 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 namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + _currentSelectedChainId!, ); - 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!, + _chainBalance = await _blockchainService.getBalance( + address: _currentSession!.getAddress(namespace)!, + namespace: namespace, + chainId: _currentSelectedChainId!, ); - _avatarUrl = blockchainId.avatar; - _logger.i('[$runtimeType] loadAccountData'); - } catch (e) { - _logger.e('[$runtimeType] loadAccountData $e'); + final tokenName = selectedChain?.currency ?? ''; + final formattedBalance = CoreUtils.formatChainBalance(_chainBalance); + balanceNotifier.value = '$formattedBalance $tokenName'; + } catch (_) { + // Calling getBalanceFallback defined by user + _chainBalance = await _getBalance?.call(); + final tokenName = selectedChain?.currency ?? ''; + final formattedBalance = CoreUtils.formatChainBalance(_chainBalance); + balanceNotifier.value = '$formattedBalance $tokenName'; + } + + if (namespace == NetworkUtils.eip155) { + // Get the avatar, each chainId is just a number in string form. + try { + final blockchainId = await _blockchainService.getIdentity( + _currentSession!.getAddress(namespace)!, + ); + _avatarUrl = blockchainId.avatar; + } catch (_) {} } _notify(); } @override Future requestSwitchToChain( - ReownAppKitModalNetworkInfo newChain) async { + ReownAppKitModalNetworkInfo newChain, + ) async { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + newChain.chainId, + ); + if (namespace != NetworkUtils.eip155) { + // If chain is not EVM then there's no need to request a switch since it doesn't exist such method for non-EVM chains + // Therefor at this point the selected non-EVM chain is either already approved, invalidating the need of a switch call, or not approved, failing with the following error. + throw ReownAppKitModalException('Chain namespace is not supported'); + } 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'); + final currentCaip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + _currentSelectedChainId!, + ); + final newCaip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + newChain.chainId, + ); + _appKit.core.logger.i( + '[$runtimeType] requesting switch to chain $newCaip2Chain', + ); try { await request( topic: _currentSession?.topic ?? '', - chainId: currentChain, - switchToChainId: newChainId, + chainId: currentCaip2Chain, + switchToChainId: newCaip2Chain, request: SessionRequestParams( method: MethodsConstants.walletSwitchEthChain, params: [ @@ -1384,7 +1523,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { 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 @@ -1394,7 +1532,22 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { try { // Otherwise it meas chain has to be added. return await requestAddChain(newChain); - } catch (e) { + } on JsonRpcError catch (e) { + _appKit.core.logger.e( + '[$runtimeType] Switch to chain error: ${e.toJson()}', + ); + rethrow; + } on ReownAppKitModalException catch (e) { + _appKit.core.logger.e( + '[$runtimeType] Switch to chain error: ${e.message}', + stackTrace: e.stackTrace, + ); + rethrow; + } catch (e, s) { + _appKit.core.logger.e( + '[$runtimeType] Switch to chain error: ${e.toString()}', + stackTrace: s, + ); rethrow; } } @@ -1404,14 +1557,18 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { @override Future requestAddChain(ReownAppKitModalNetworkInfo 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'); + final currentCaip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + _currentSelectedChainId!, + ); + final newCaip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + newChain.chainId, + ); + _appKit.core.logger.i('[$runtimeType] requesting add chain $newCaip2Chain'); try { await request( topic: topic, - chainId: chainId, - switchToChainId: newChainId, + chainId: currentCaip2Chain, + switchToChainId: newCaip2Chain, request: SessionRequestParams( method: MethodsConstants.walletAddEthChain, params: [newChain.toJson()], @@ -1420,10 +1577,12 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _currentSelectedChainId = newChain.chainId; await _setSesionAndChainData(_currentSession!); return; - } catch (e) { - _logger.i('[$runtimeType] requestAddChain error $e'); + } on JsonRpcError { await _setLocalEthChain(_currentSelectedChainId!); - throw JsonRpcError(code: 5002, message: 'User rejected methods.'); + rethrow; + } catch (e, s) { + await _setLocalEthChain(_currentSelectedChainId!); + throw ReownAppKitModalException(e.toString(), s); } } @@ -1455,18 +1614,23 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } + Future _deleteStorage() async { + await _storage.delete(StorageConstants.selectedChainId); + await _storage.delete(StorageConstants.modalSession); + } + 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); + await _deleteStorage(); + await _explorerService.storeRecentWalletId(walletId); } catch (_) { - await _storage.deleteAll(); + await _deleteStorage(); } if (event) { onModalDisconnect.broadcast(ModalDisconnect( - topic: args?.topic, + topic: args?.topic ?? _currentSession?.topic, id: args?.id, )); } @@ -1479,6 +1643,40 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _notify(); } + void _onModalError(ModalError? event) { + _toastService.show(ToastMessage( + type: ToastType.error, + text: event?.message ?? 'An error occurred.', + )); + } + + void _onNetworkChainRequireSIWE(ModalNetworkChange? args) async { + if (_siweService.config?.enabled != true) return; + try { + if (_siweService.signOutOnNetworkChange) { + final caip2chain = ReownAppKitModalNetworks.getCaip2Chain( + _currentSelectedChainId!, + ); + await _magicService.getUser(chainId: caip2chain, isUpdate: true); + await _siweService.signOut(); + _disconnectOnClose = true; + widgetStack.instance.push(ApproveSIWEPage( + onSiweFinish: _oneSIWEFinish, + )); + } + } catch (e, s) { + _appKit.core.logger.e( + '[$runtimeType] _onNetworkChainRequireSIWE', + error: e, + stackTrace: s, + ); + } + } + + void _loadAccountData(ModalConnect? event) { + loadAccountData(); + } + void _checkInitialized() { if (_status != ReownAppKitModalStatus.initialized && _status != ReownAppKitModalStatus.initializing) { @@ -1489,21 +1687,18 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } void _registerListeners() { + onModalError.subscribe(_onModalError); // 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); + _magicService.onMagicConnect.subscribe(_onMagicConnectEvent); + _magicService.onMagicLoginSuccess.subscribe(_onMagicLoginEvent); + _magicService.onMagicError.subscribe(_onMagicErrorEvent); + _magicService.onMagicUpdate.subscribe(_onMagicSessionUpdateEvent); + _magicService.onMagicRpcRequest.subscribe(_onMagicRequest); // // Coinbase - coinbaseService.instance.onCoinbaseConnect.subscribe( - _onCoinbaseConnectEvent, - ); - coinbaseService.instance.onCoinbaseError.subscribe( - _onCoinbaseErrorEvent, - ); - coinbaseService.instance.onCoinbaseSessionUpdate.subscribe( + _coinbaseService.onCoinbaseConnect.subscribe(_onCoinbaseConnectEvent); + _coinbaseService.onCoinbaseError.subscribe(_onCoinbaseErrorEvent); + _coinbaseService.onCoinbaseSessionUpdate.subscribe( _onCoinbaseSessionUpdateEvent, ); // @@ -1525,35 +1720,19 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { ); } - 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() { + onModalError.unsubscribe(_onModalError); + // Magic - magicService.instance.onMagicLoginSuccess.unsubscribe(_onMagicLoginEvent); - magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); - magicService.instance.onMagicUpdate.unsubscribe(_onMagicSessionUpdateEvent); - magicService.instance.onMagicRpcRequest.unsubscribe(_onMagicRequest); + _magicService.onMagicLoginSuccess.unsubscribe(_onMagicLoginEvent); + _magicService.onMagicError.unsubscribe(_onMagicErrorEvent); + _magicService.onMagicUpdate.unsubscribe(_onMagicSessionUpdateEvent); + _magicService.onMagicRpcRequest.unsubscribe(_onMagicRequest); // // Coinbase - coinbaseService.instance.onCoinbaseConnect.unsubscribe( - _onCoinbaseConnectEvent, - ); - coinbaseService.instance.onCoinbaseError.unsubscribe( - _onCoinbaseErrorEvent, - ); - coinbaseService.instance.onCoinbaseSessionUpdate.unsubscribe( + _coinbaseService.onCoinbaseConnect.unsubscribe(_onCoinbaseConnectEvent); + _coinbaseService.onCoinbaseError.unsubscribe(_onCoinbaseErrorEvent); + _coinbaseService.onCoinbaseSessionUpdate.unsubscribe( _onCoinbaseSessionUpdateEvent, ); // @@ -1577,7 +1756,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { 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; @@ -1587,13 +1765,22 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { extension _EmailConnectorExtension on ReownAppKitModal { // 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}')!; + _appKit.core.logger.d( + '[$runtimeType] _onMagicLoginEvent ${args.data?.toJson()}', + ); + final newChainId = _getStoredChainId(args.data!.chainId)!; _currentSelectedChainId = newChainId; // - final magicData = args.data?.copytWith(chainId: int.tryParse(newChainId)); + final email = args.data?.email ?? _currentSession?.toRawJson()['email']; + final userName = + args.data?.userName ?? _currentSession?.toRawJson()['userName']; + final magicData = args.data?.copytWith( + chainId: newChainId, + email: email, + userName: userName, + ); + final session = ReownAppKitModalSession(magicData: magicData); await _setSesionAndChainData(session); onModalConnect.broadcast(ModalConnect(session)); @@ -1602,14 +1789,14 @@ extension _EmailConnectorExtension on ReownAppKitModal { await _storage.delete(StorageConstants.connectedWalletData); } // - if (siweService.instance!.enabled) { + if (_siweService.enabled) { if (!_isOpen) { await _checkSIWEStatus(); onModalUpdate.broadcast(ModalConnect(_currentSession!)); } else { _disconnectOnClose = true; final theme = ReownAppKitModalTheme.maybeOf(_context!); - await magicService.instance.syncTheme(theme); + await _magicService.syncTheme(theme); widgetStack.instance.push(ApproveSIWEPage( onSiweFinish: _oneSIWEFinish, )); @@ -1623,31 +1810,36 @@ extension _EmailConnectorExtension on ReownAppKitModal { } Future _onMagicSessionUpdateEvent(MagicSessionEvent? args) async { - _logger.d('[$runtimeType] _onMagicUpdateEvent: $args'); if (args != null) { + _appKit.core.logger.d( + '[$runtimeType] _onMagicSessionUpdateEvent ${args.toJson()}', + ); try { - final newEmail = args.email ?? _currentSession!.email; - final address = args.address ?? _currentSession!.address!; - final chainId = args.chainId?.toString() ?? _currentSession!.chainId; - _currentSelectedChainId = chainId; + final currentUsername = _currentSession?.userName; + final currentEmail = _currentSession?.email; + final newEmail = args.email ?? currentEmail ?? currentUsername; + final newUsername = args.userName ?? currentUsername; + final newProvider = args.provider ?? _currentSession?.socialProvider; + final newChainId = args.chainId?.toString() ?? _currentSession!.chainId; + final ns = ReownAppKitModalNetworks.getNamespaceForChainId(newChainId); + final newAddress = args.address ?? _currentSession!.getAddress(ns)!; + _currentSelectedChainId = newChainId; // - final session = _currentSession!.copyWith( - magicData: MagicData( - email: newEmail, - address: address, - chainId: int.parse(chainId), - peer: magicService.instance.metadata, - self: ConnectionMetadata( - metadata: _appKit.metadata, - publicKey: '', - ), - ), + final magicData = MagicData( + email: newEmail, + address: newAddress, + userName: newUsername, + provider: newProvider, + chainId: newChainId, ); + final session = (_currentSession != null) + ? _currentSession!.copyWith(magicData: magicData) + : ReownAppKitModalSession(magicData: magicData); await _setSesionAndChainData(session); onModalUpdate.broadcast(ModalConnect(session)); } catch (e, s) { - _logger.d( - '[$runtimeType] _onMagicUpdateEvent: $e', + _appKit.core.logger.e( + '[$runtimeType] _onMagicUpdateEvent error: $e', stackTrace: s, ); } @@ -1655,7 +1847,7 @@ extension _EmailConnectorExtension on ReownAppKitModal { } Future _onMagicErrorEvent(MagicErrorEvent? args) async { - _logger.d('[$runtimeType] _onMagicErrorEvent ${args?.error}'); + _appKit.core.logger.d('[$runtimeType] _onMagicErrorEvent: $args'); final errorMessage = args?.error ?? 'Something went wrong'; if (!errorMessage.toLowerCase().contains('user denied')) { onModalError.broadcast(ModalError(errorMessage)); @@ -1664,7 +1856,6 @@ extension _EmailConnectorExtension on ReownAppKitModal { } 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(); @@ -1685,8 +1876,7 @@ extension _EmailConnectorExtension on ReownAppKitModal { extension _CoinbaseConnectorExtension on ReownAppKitModal { void _onCoinbaseConnectEvent(CoinbaseConnectEvent? args) async { - final debugString = jsonEncode(args?.data?.toJson()); - _logger.i('[$runtimeType] _onCoinbaseConnectEvent: $debugString'); + _appKit.core.logger.d('[$runtimeType] _onCoinbaseConnectEvent: $args'); if (args?.data != null) { final newChainId = _getStoredChainId('${args!.data!.chainId}')!; _currentSelectedChainId = newChainId; @@ -1695,7 +1885,7 @@ extension _CoinbaseConnectorExtension on ReownAppKitModal { await _setSesionAndChainData(session); onModalConnect.broadcast(ModalConnect(session)); // - if (siweService.instance!.enabled) { + if (_siweService.enabled) { _disconnectOnClose = true; widgetStack.instance.push(ApproveSIWEPage( onSiweFinish: _oneSIWEFinish, @@ -1709,34 +1899,33 @@ extension _CoinbaseConnectorExtension on ReownAppKitModal { } void _onCoinbaseSessionUpdateEvent(CoinbaseSessionEvent? args) async { - _logger.i('[$runtimeType] _onCoinbaseSessionUpdateEvent $args'); + _appKit.core.logger.d( + '[$runtimeType] _onCoinbaseSessionUpdateEvent: $args', + ); if (args != null) { try { - final address = args.address ?? _currentSession!.address!; final chainId = args.chainId ?? _currentSession!.chainId; + final ns = ReownAppKitModalNetworks.getNamespaceForChainId(chainId); + final address = args.address ?? _currentSession!.getAddress(ns)!; + final chainInfo = ReownAppKitModalNetworks.getNetworkById(ns, chainId); _currentSelectedChainId = chainId; - // - final chain = ReownAppKitModalNetworks.getNetworkById( - CoreConstants.namespace, - chainId, - ); final session = _currentSession!.copyWith( coinbaseData: CoinbaseData( address: address, - chainName: chain?.name ?? '', + chainName: chainInfo?.name ?? '', chainId: int.parse(chainId), - peer: coinbaseService.instance.metadata, + peer: _coinbaseService.metadata, self: ConnectionMetadata( metadata: _appKit.metadata, - publicKey: await coinbaseService.instance.ownPublicKey, + publicKey: await _coinbaseService.ownPublicKey, ), ), ); await _setSesionAndChainData(session); onModalUpdate.broadcast(ModalConnect(session)); } catch (e, s) { - _logger.d( - '[$runtimeType] _onCoinbaseSessionUpdateEvent: $e', + _appKit.core.logger.e( + '[$runtimeType] _onCoinbaseSessionUpdateEvent error: $e', stackTrace: s, ); } @@ -1744,7 +1933,7 @@ extension _CoinbaseConnectorExtension on ReownAppKitModal { } void _onCoinbaseErrorEvent(CoinbaseErrorEvent? args) async { - _logger.d('[$runtimeType] _onCoinbaseErrorEvent ${args?.error}'); + _appKit.core.logger.d('[$runtimeType] _onCoinbaseErrorEvent: $args'); final errorMessage = args?.error ?? 'Something went wrong'; if (!errorMessage.toLowerCase().contains('user denied')) { onModalError.broadcast(ModalError(errorMessage)); @@ -1755,7 +1944,9 @@ extension _CoinbaseConnectorExtension on ReownAppKitModal { extension _AppKitModalExtension on ReownAppKitModal { void _onSessionAuthResponse(SessionAuthResponse? args) async { final debugString = jsonEncode(args?.toJson()); - dev.log('[$runtimeType] _onSessionAuthResponse: $debugString'); + _appKit.core.logger.d( + '[$runtimeType] _onSessionAuthResponse: $debugString', + ); if (args?.session != null) { // IF 1-CA SUPPORTED WE SHOULD CALL SIWECONGIF METHODS HERE final session = await _settleSession(args!.session!); @@ -1768,18 +1959,21 @@ extension _AppKitModalExtension on ReownAppKitModal { cacaoPayload: CacaoRequestPayload.fromCacaoPayload(cacao.p), ); final clientId = await _appKit.core.crypto.getClientId(); - await siweService.instance!.verifyMessage( + await _siweService.verifyMessage( message: message, signature: cacao.s.s, clientId: clientId, ); - } catch (e) { - _logger.e('[$runtimeType] _onSessionAuthResponse $e'); + } catch (e, s) { + _appKit.core.logger.e( + '[$runtimeType] onSessionAuthResponse $e', + stackTrace: s, + ); await disconnect(); return; } // - final siweSession = await siweService.instance!.getSession(); + final siweSession = await _siweService.getSession(); final newSession = session.copyWith(siweSession: siweSession); // await _storeSession(newSession); @@ -1793,15 +1987,16 @@ extension _AppKitModalExtension on ReownAppKitModal { 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; + _appKit.core.logger.d('[$runtimeType] _onSessionConnect: $debugString'); + if (_supportsOneClickAuth && _siweService.enabled) { + 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) { + if (_siweService.enabled) { _disconnectOnClose = true; widgetStack.instance.push(ApproveSIWEPage( onSiweFinish: _oneSIWEFinish, @@ -1815,28 +2010,27 @@ extension _AppKitModalExtension on ReownAppKitModal { } // HAS TO BE CALLED JUST ONCE ON CONNECTION - Future _settleSession( - SessionData sessionData) async { + Future _settleSession(SessionData mSession) async { if (_currentSelectedChainId == null) { final chains = NamespaceUtils.getChainIdsFromNamespaces( - namespaces: sessionData.namespaces, - )..sort((a, b) => a.compareTo(b)); + namespaces: mSession.namespaces, + ); final chainId = chains.first.split(':').last.toString(); _currentSelectedChainId = chainId; } - final session = ReownAppKitModalSession(sessionData: sessionData); + final session = ReownAppKitModalSession(sessionData: mSession); await _setSesionAndChainData(session); if (_selectedWallet == null) { - analyticsService.instance.sendEvent(ConnectSuccessEvent( + _analyticsService.sendEvent(ConnectSuccessEvent( name: 'WalletConnect', method: AnalyticsPlatform.qrcode, )); await _storage.delete(StorageConstants.recentWalletId); await _storage.delete(StorageConstants.connectedWalletData); } else { - explorerService.instance.storeConnectedWallet(_selectedWallet); + _explorerService.storeConnectedWallet(_selectedWallet); final walletName = _selectedWallet!.listing.name; - analyticsService.instance.sendEvent(ConnectSuccessEvent( + _analyticsService.sendEvent(ConnectSuccessEvent( name: walletName, method: AnalyticsPlatform.mobile, )); @@ -1845,41 +2039,51 @@ extension _AppKitModalExtension on ReownAppKitModal { } void _oneSIWEFinish(ReownAppKitModalSession updatedSession) async { + _appKit.core.logger.d( + '[$runtimeType] _oneSIWEFinish ${updatedSession.toJson()}', + ); await _storeSession(updatedSession); try { await _storage.set( StorageConstants.selectedChainId, {'chainId': _currentSelectedChainId!}, ); - debugPrint('_oneSIWEFinish {\'chainId\': $_currentSelectedChainId}'); - } catch (e) { - _logger.e('[$runtimeType] _setLocalEthChain error: $e'); + } catch (e, s) { + _appKit.core.logger.e( + '[$runtimeType] _oneSIWEFinish error: $e', + stackTrace: s, + ); } onModalUpdate.broadcast(ModalConnect(updatedSession)); closeModal(); - analyticsService.instance.sendEvent(SiweAuthSuccess( + _analyticsService.sendEvent(SiweAuthSuccess( network: _currentSelectedChainId!, )); } void _onSessionEvent(SessionEvent? args) async { - _logger.i('[$runtimeType] session event $args'); + _appKit.core.logger.d('[$runtimeType] _onSessionEvent $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(); + } + if (args?.name == EventsConstants.accountsChanged) { + if (_siweService.enabled && _siweService.signOutOnAccountChange) { + try { + await _siweService.signOut(); + } catch (e, s) { + _appKit.core.logger.e( + '[$runtimeType] _onSessionEvent error: $e', + stackTrace: s, + ); } - } catch (_) {} + } } _notify(); } void _onSessionUpdate(SessionUpdate? args) async { - _logger.i('[$runtimeType] session update $args'); + _appKit.core.logger.d('[$runtimeType] _onSessionUpdate $args'); if (args != null) { final wcSessions = _appKit.sessions.getAll(); if (wcSessions.isEmpty) return; @@ -1897,37 +2101,38 @@ extension _AppKitModalExtension on ReownAppKitModal { } 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'); + _appKit.core.logger.i('[$runtimeType] relay client connected'); final service = _currentSession?.sessionService ?? ReownAppKitModalConnector.wc; - if (service.isWC && _serviceInitialized) { + if (service.isWC && _relayConnected) { _status = ReownAppKitModalStatus.initialized; _notify(); } + if (!_awaitRelayOnce.isCompleted) { + _awaitRelayOnce.complete(true); + } } void _onRelayClientDisconnect(EventArgs? args) { - _logger.i('[$runtimeType] relay client disconnected'); + _appKit.core.logger.i('[$runtimeType] relay client disconnected'); final service = _currentSession?.sessionService ?? ReownAppKitModalConnector.wc; - if (service.isWC && _serviceInitialized) { + if (service.isWC && _relayConnected) { _status = ReownAppKitModalStatus.idle; _notify(); } } void _onRelayClientError(ErrorEvent? args) { - _logger.i('[$runtimeType] relay client error: ${args?.error}'); + _appKit.core.logger.i('[$runtimeType] relay client error: ${args?.error}'); final service = _currentSession?.sessionService ?? ReownAppKitModalConnector.wc; if (service.isWC) { diff --git a/packages/reown_appkit/lib/modal/assets/dark/apple_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/apple_logo.svg new file mode 100644 index 0000000..b9c0a52 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/apple_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/discord_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/discord_logo.svg new file mode 100644 index 0000000..d3e8e7d --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/discord_logo.svg @@ -0,0 +1,32 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/facebook_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/facebook_logo.svg new file mode 100644 index 0000000..e100f2b --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/facebook_logo.svg @@ -0,0 +1,40 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/farcaster_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/farcaster_logo.svg new file mode 100644 index 0000000..6c82bf8 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/farcaster_logo.svg @@ -0,0 +1,43 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/github_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/github_logo.svg new file mode 100644 index 0000000..66bf7fc --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/github_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/google_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/google_logo.svg new file mode 100644 index 0000000..f2607cd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/google_logo.svg @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/more_social_icon.svg b/packages/reown_appkit/lib/modal/assets/dark/more_social_icon.svg new file mode 100644 index 0000000..f4f3afd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/more_social_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/telegram_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/telegram_logo.svg new file mode 100644 index 0000000..c694fcd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/telegram_logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/twitch_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/twitch_logo.svg new file mode 100644 index 0000000..706f022 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/twitch_logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/x_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/x_logo.svg new file mode 100644 index 0000000..11ff04c --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/x_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/apple_logo.svg b/packages/reown_appkit/lib/modal/assets/light/apple_logo.svg new file mode 100644 index 0000000..b9c0a52 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/apple_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/discord_logo.svg b/packages/reown_appkit/lib/modal/assets/light/discord_logo.svg new file mode 100644 index 0000000..d3e8e7d --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/discord_logo.svg @@ -0,0 +1,32 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/facebook_logo.svg b/packages/reown_appkit/lib/modal/assets/light/facebook_logo.svg new file mode 100644 index 0000000..e100f2b --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/facebook_logo.svg @@ -0,0 +1,40 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/farcaster_logo.svg b/packages/reown_appkit/lib/modal/assets/light/farcaster_logo.svg new file mode 100644 index 0000000..6c82bf8 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/farcaster_logo.svg @@ -0,0 +1,43 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/github_logo.svg b/packages/reown_appkit/lib/modal/assets/light/github_logo.svg new file mode 100644 index 0000000..66bf7fc --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/github_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/google_logo.svg b/packages/reown_appkit/lib/modal/assets/light/google_logo.svg new file mode 100644 index 0000000..f2607cd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/google_logo.svg @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/more_social_icon.svg b/packages/reown_appkit/lib/modal/assets/light/more_social_icon.svg new file mode 100644 index 0000000..8411858 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/more_social_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/telegram_logo.svg b/packages/reown_appkit/lib/modal/assets/light/telegram_logo.svg new file mode 100644 index 0000000..c694fcd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/telegram_logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/twitch_logo.svg b/packages/reown_appkit/lib/modal/assets/light/twitch_logo.svg new file mode 100644 index 0000000..706f022 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/twitch_logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/x_logo.svg b/packages/reown_appkit/lib/modal/assets/light/x_logo.svg new file mode 100644 index 0000000..11ff04c --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/x_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/png/2.0x/farcaster.png b/packages/reown_appkit/lib/modal/assets/png/2.0x/farcaster.png new file mode 100644 index 0000000..f178425 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/2.0x/farcaster.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/3.0x/farcaster.png b/packages/reown_appkit/lib/modal/assets/png/3.0x/farcaster.png new file mode 100644 index 0000000..dd5b0ed Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/3.0x/farcaster.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/farcaster.png b/packages/reown_appkit/lib/modal/assets/png/farcaster.png new file mode 100644 index 0000000..368c362 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/farcaster.png differ diff --git a/packages/reown_appkit/lib/modal/constants/key_constants.dart b/packages/reown_appkit/lib/modal/constants/key_constants.dart index 1ecbb7b..697e3ee 100644 --- a/packages/reown_appkit/lib/modal/constants/key_constants.dart +++ b/packages/reown_appkit/lib/modal/constants/key_constants.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + import 'package:flutter/material.dart'; class KeyConstants { @@ -11,7 +13,9 @@ class KeyConstants { static const Key upgradeWalletPage = Key('upgradeWalletPage'); static const Key helpPageKey = Key('helpPageKey'); static const Key qrCodePageKey = Key('qrCodePageKey'); + static const Key farcasterQrCodePageKey = Key('farcasterQrCodePageKey'); static const Key walletListShortPageKey = Key('walletListShortPageKey'); + static const Key allSocialLoginPageKey = Key('allSocialLoginPageKey'); static const Key walletListLongPageKey = Key('walletListLongPageKey'); static const Key connectWalletPageKey = Key('connectWalletPageKey'); static const Key connecNetworkPageKey = Key('connecNetworkPageKey'); @@ -21,6 +25,7 @@ class KeyConstants { static const Key approveTransactionPage = Key('approveTransactionPage'); static const Key confirmEmailPage = Key('confirmEmailPage'); static const Key approveSiwePageKey = Key('approveSiwePageKey'); + static const Key socialLoginPage = Key('socialLoginPage'); // Buttons static const Key helpButtonKey = Key('helpButtonKey'); diff --git a/packages/reown_appkit/lib/modal/constants/string_constants.dart b/packages/reown_appkit/lib/modal/constants/string_constants.dart index b129e1a..e29b447 100644 --- a/packages/reown_appkit/lib/modal/constants/string_constants.dart +++ b/packages/reown_appkit/lib/modal/constants/string_constants.dart @@ -1,12 +1,15 @@ +// ignore_for_file: public_member_api_docs + import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_core/version.dart' as reown_core; import 'package:reown_sign/version.dart' as reown_sign; class CoreConstants { // Request Headers static const X_SDK_TYPE = 'appkit'; - static const X_SDK_VERSION = packageVersion; - static const X_CORE_SDK_VERSION = 'flutter_${reown_sign.packageVersion}'; - static const String namespace = 'eip155'; + static const X_SDK_VERSION = 'flutter-$packageVersion'; + static const X_CORE_SDK_VERSION = 'core-${reown_core.packageVersion}'; + static const X_SIGN_SDK_VERSION = 'sign-${reown_sign.packageVersion}'; } class UIConstants { @@ -30,13 +33,12 @@ class UIConstants { class StorageConstants { // Storage - static const String recentWalletId = - '${CoreConstants.X_SDK_TYPE}_recentWallet'; - static const String connectedWalletData = - '${CoreConstants.X_SDK_TYPE}_walletData'; - static const String selectedChainId = - '${CoreConstants.X_SDK_TYPE}_selectedChainId'; - static const String modalSession = '${CoreConstants.X_SDK_TYPE}_session'; + static const prefix = '${CoreConstants.X_SDK_TYPE}:$packageVersion//'; + + static const String recentWalletId = '${prefix}recentWallet'; + static const String connectedWalletData = '${prefix}walletData'; + static const String selectedChainId = '${prefix}selectedChainId'; + static const String modalSession = '${prefix}session'; } class UrlConstants { @@ -46,9 +48,10 @@ class UrlConstants { static const exploreWallets = 'https://explorer.walletconnect.com/?type=wallet'; // - static const secureService = - 'https://secure-mobile.walletconnect.com/mobile-sdk'; - static const secureDashboard = 'https://secure.walletconnect.com/dashboard'; + static const secureOrigin1 = 'secure-mobile.walletconnect.com'; + static const secureOrigin2 = 'secure.walletconnect.com'; + static const secureService = 'https://$secureOrigin1/mobile-sdk'; + static const secureDashboard = 'https://$secureOrigin2/dashboard'; // static const learnMoreUrl = 'https://ethereum.org/en/developers/docs/networks'; diff --git a/packages/reown_appkit/lib/modal/constants/style_constants.dart b/packages/reown_appkit/lib/modal/constants/style_constants.dart index d2adf3b..da3af94 100644 --- a/packages/reown_appkit/lib/modal/constants/style_constants.dart +++ b/packages/reown_appkit/lib/modal/constants/style_constants.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + const kListItemHeight = 56.0; const kGridItemWidth = 76.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 index 6372545..d1db8e7 100644 --- a/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart +++ b/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:reown_appkit/reown_appkit.dart'; @@ -23,6 +25,8 @@ abstract class IReownAppKitModal with ChangeNotifier { bool get hasNamespaces; + FeaturesConfig get featuresConfig; + /// The object that manages sessions, authentication, events, and requests for WalletConnect. IReownAppKit? get appKit; @@ -44,8 +48,10 @@ abstract class IReownAppKitModal with ChangeNotifier { String? get avatarUrl; /// Returns the balance of the currently connected wallet on the selected chain. + @Deprecated('Use balanceNotifier') String get chainBalance; + /// Returns the balance of the currently connected wallet on the selected chain. ValueNotifier get balanceNotifier; /// The currently selected chain. @@ -57,10 +63,7 @@ abstract class IReownAppKitModal with ChangeNotifier { /// Sets up the explorer and appKit if they already been initialized. Future init(); - // @Deprecated( - // 'Add context param to ReownAppKitModal and use openNetworksView() instead') - // Future openNetworks(BuildContext context); - + /// Opens modal on Network Selection Screen Future openNetworksView(); /// Opens the modal with the provided [startWidget] (if any). @@ -74,7 +77,7 @@ abstract class IReownAppKitModal with ChangeNotifier { /// Sets the [selectedWallet] to be connected void selectWallet(ReownAppKitModalWalletInfo? walletInfo); - /// Sets the [selectedChain] and gets the [chainBalance]. + /// Sets the [selectedChain] /// 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( @@ -101,14 +104,15 @@ abstract class IReownAppKitModal with ChangeNotifier { List? getAvailableChains(); /// List of approved chains by connected wallet - List? getApprovedChains(); + List? getApprovedChains({String? namespace}); /// List of approved methods by connected wallet - List? getApprovedMethods(); + List? getApprovedMethods({String? namespace}); /// List of approved events by connected wallet - List? getApprovedEvents(); + List? getApprovedEvents({String? namespace}); + /// Loads/Refresh account balance and identity Future loadAccountData(); /// Disconnects the session and pairing, if any. @@ -145,6 +149,8 @@ abstract class IReownAppKitModal with ChangeNotifier { Future requestSwitchToChain(ReownAppKitModalNetworkInfo newChain); Future requestAddChain(ReownAppKitModalNetworkInfo newChain); + Future dispatchEnvelope(String url); + /// Closes the modal. void closeModal({bool disconnectSession = false}); diff --git a/packages/reown_appkit/lib/modal/models/grid_item.dart b/packages/reown_appkit/lib/modal/models/grid_item.dart index 92366fc..f4c5975 100644 --- a/packages/reown_appkit/lib/modal/models/grid_item.dart +++ b/packages/reown_appkit/lib/modal/models/grid_item.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + class GridItem { final String image; final String id; 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 index 338d13a..ce716f2 100644 --- a/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart @@ -1,5 +1,6 @@ import 'package:reown_appkit/reown_appkit.dart'; +/// Event fired when connection is done class ModalConnect extends EventArgs { final ReownAppKitModalSession session; ModalConnect(this.session); @@ -10,6 +11,7 @@ class ModalConnect extends EventArgs { } } +/// Event fired when network is changed through the modal class ModalNetworkChange extends EventArgs { final String chainId; ModalNetworkChange({required this.chainId}); @@ -20,6 +22,7 @@ class ModalNetworkChange extends EventArgs { } } +/// Event fired when disconnect happens, either from the wallet of the modal class ModalDisconnect extends EventArgs { final String? topic; final int? id; @@ -31,6 +34,7 @@ class ModalDisconnect extends EventArgs { } } +/// Event fired every time an error occurs class ModalError extends EventArgs { final String message; ModalError(this.message); @@ -41,14 +45,17 @@ class ModalError extends EventArgs { } } +/// Event fired when trying to opening a wallet that is not installed class WalletNotInstalled extends ModalError { WalletNotInstalled() : super('Wallet app not installed'); } +/// Error opening wallet class ErrorOpeningWallet extends ModalError { ErrorOpeningWallet() : super('Unable to open Wallet app'); } +/// Event fired when user rejects connection in the wallet class UserRejectedConnection extends ModalError { UserRejectedConnection() : super('User rejected connection'); } diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_features_config.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_features_config.dart new file mode 100644 index 0000000..47c0292 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_features_config.dart @@ -0,0 +1,14 @@ +import 'package:reown_appkit/modal/models/public/appkit_social_options.dart'; + +/// Object to pass to [featuresConfig:] parameter of ReownAppKitModal to enable or disable these extra features +class FeaturesConfig { + final bool email; + final List socials; + final bool showMainWallets; + + FeaturesConfig({ + this.email = true, + this.socials = const [], + this.showMainWallets = true, + }); +} 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 index 8698749..0f3b11b 100644 --- a/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart @@ -1,6 +1,8 @@ -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'; +export 'appkit_modal_features_config.dart'; +export 'appkit_modal_session.dart'; +export 'appkit_network_info.dart'; +export 'appkit_siwe_config.dart'; +export 'appkit_social_options.dart'; +export 'appkit_wallet_info.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 index cd94285..e9f1849 100644 --- a/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart @@ -1,7 +1,8 @@ -import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/services/coinbase_service/coinbase_service.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/magic_service/magic_service.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/reown_appkit.dart'; @@ -18,6 +19,7 @@ enum ReownAppKitModalConnector { bool get noSession => this == ReownAppKitModalConnector.none; } +/// Session object of the modal when connected class ReownAppKitModalSession { SessionData? _sessionData; CoinbaseData? _coinbaseData; @@ -60,14 +62,33 @@ class ReownAppKitModalSession { MagicData? magicData, SIWESession? siweSession, }) { + final newCoinbaseData = _coinbaseData?.copytWith( + address: coinbaseData?.address, + chainName: coinbaseData?.chainName, + chainId: coinbaseData?.chainId, + self: coinbaseData?.self, + peer: coinbaseData?.peer, + ); + final newMagicData = _magicData?.copytWith( + email: magicData?.email, + address: magicData?.address, + chainId: magicData?.chainId, + userName: magicData?.userName, + smartAccountDeployed: magicData?.smartAccountDeployed, + preferredAccountType: magicData?.preferredAccountType, + self: magicData?.self, + peer: magicData?.peer, + provider: magicData?.provider, + ); return ReownAppKitModalSession( sessionData: sessionData ?? _sessionData, - coinbaseData: coinbaseData ?? _coinbaseData, - magicData: magicData ?? _magicData, + coinbaseData: newCoinbaseData ?? _coinbaseData, + magicData: newMagicData ?? _magicData, siweSession: siweSession ?? _siweSession, ); } + /// Indicates the connected service ReownAppKitModalConnector get sessionService { if (_sessionData != null) { return ReownAppKitModalConnector.wc; @@ -76,6 +97,7 @@ class ReownAppKitModalSession { return ReownAppKitModalConnector.coinbase; } if (_magicData != null) { + // TODO rename to ReownAppKitModalConnector.socials return ReownAppKitModalConnector.magic; } @@ -89,75 +111,129 @@ class ReownAppKitModalSession { if (sessionService.isCoinbase) { return true; } + if (sessionService.isMagic) { + return true; + } - final nsMethods = getApprovedMethods() ?? []; + final nsMethods = getApprovedMethods(namespace: NetworkUtils.eip155) ?? []; final supportsAddChain = nsMethods.contains( MethodsConstants.walletAddEthChain, ); return supportsAddChain; } - List? getApprovedMethods() { + List? getApprovedMethods({String? namespace}) { + final methodsList = []; + if (sessionService.noSession) { return null; } if (sessionService.isCoinbase) { - return CoinbaseService.supportedMethods; + return GetIt.I().supportedMethods; } if (sessionService.isMagic) { - return MagicService.supportedMethods; + final ns = namespace ?? NetworkUtils.eip155; + return GetIt.I().supportedMethods[ns]; } final sessionNamespaces = _sessionData!.namespaces; - final namespace = sessionNamespaces[CoreConstants.namespace]; - final methodsList = namespace?.methods.toSet().toList(); - return methodsList ?? []; + if ((namespace ?? '').isEmpty) { + for (var namespace in sessionNamespaces.keys) { + final events = sessionNamespaces[namespace]?.methods ?? []; + methodsList.addAll(events); + } + + return methodsList; + } + + return sessionNamespaces[namespace]?.methods ?? []; } - List? getApprovedEvents() { + List? getApprovedEvents({String? namespace}) { + final eventsList = []; + if (sessionService.noSession) { return null; } if (sessionService.isCoinbase) { - return []; + return eventsList; } if (sessionService.isMagic) { - return []; + return eventsList; } final sessionNamespaces = _sessionData!.namespaces; - final namespace = sessionNamespaces[CoreConstants.namespace]; - final eventsList = namespace?.events.toSet().toList(); - return eventsList ?? []; + if ((namespace ?? '').isEmpty) { + for (var namespace in sessionNamespaces.keys) { + final events = sessionNamespaces[namespace]?.events ?? []; + eventsList.addAll(events); + } + + return eventsList; + } + + return sessionNamespaces[namespace]?.events ?? []; } - List? getApprovedChains() { + List? getApprovedChains({String? namespace}) { if (sessionService.noSession) { return null; } - // We can not know which chains are approved from Coinbase or Magic - if (!sessionService.isWC) { - return [chainId]; + // Coinbase only support EIP155 but since we can not know which chains are actually approved... + // Magic only support EIP155 and Solana but since we can not know which chains are actually approved... + + final allEIP155 = ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: NetworkUtils.eip155, + ).map((e) => '${NetworkUtils.eip155}:${e.chainId}').toList(); + + if (sessionService.isCoinbase) { + return [...allEIP155]; + } + + final allSolana = ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: NetworkUtils.solana, + ).map((e) => '${NetworkUtils.solana}:${e.chainId}').toList(); + + if (sessionService.isMagic) { + return [...allEIP155, ...allSolana]; } - final accounts = getAccounts() ?? []; - final approvedChains = NamespaceUtils.getChainsFromAccounts(accounts); - return approvedChains; + final accounts = getAccounts(namespace: namespace) ?? []; + return NamespaceUtils.getChainsFromAccounts(accounts); } - List? getAccounts() { + List? getAccounts({String? namespace}) { + final accountList = []; + if (sessionService.noSession) { return null; } + if (sessionService.isCoinbase) { - return ['${CoreConstants.namespace}:$chainId:$address']; + final ns = NetworkUtils.eip155; + return ReownAppKitModalNetworks.getAllSupportedNetworks(namespace: ns) + .map((e) => '$ns:${e.chainId}:${getAddress(ns)}') + .toList(); } + if (sessionService.isMagic) { - return ['${CoreConstants.namespace}:$chainId:$address']; + final ns = namespace ?? NetworkUtils.eip155; + return ReownAppKitModalNetworks.getAllSupportedNetworks(namespace: ns) + .map((e) => '$ns:${e.chainId}:${getAddress(ns)}') + .toList(); } final sessionNamespaces = _sessionData!.namespaces; - return sessionNamespaces[CoreConstants.namespace]?.accounts ?? []; + if ((namespace ?? '').isEmpty) { + for (var namespace in sessionNamespaces.keys) { + final accounts = sessionNamespaces[namespace]?.accounts ?? []; + accountList.addAll(accounts); + } + + return accountList; + } + + return sessionNamespaces[namespace]?.accounts ?? []; } Redirect? getSessionRedirect() { @@ -170,21 +246,29 @@ class ReownAppKitModalSession { // toJson() would convert ReownAppKitModalSession 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(), - }; + Map toJson() { + if (_sessionData != null) { + return _sessionData!.toJson(); + } + + final sessionData = SessionData( + topic: topic ?? '', + pairingTopic: pairingTopic ?? '', + relay: relay ?? Relay(ReownConstants.RELAYER_DEFAULT_PROTOCOL), + expiry: expiry ?? 0, + acknowledged: acknowledged ?? false, + controller: controller ?? '', + namespaces: _namespaces() ?? {}, + self: self!, + peer: peer!, + requiredNamespaces: _sessionData?.requiredNamespaces, + optionalNamespaces: _sessionData?.optionalNamespaces, + sessionProperties: _sessionData?.sessionProperties, + authentication: _sessionData?.authentication, + transportType: _sessionData?.transportType ?? TransportType.relay, + ); + return sessionData.toJson(); + } } extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { @@ -195,18 +279,22 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { 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 _magicData?.self ?? + ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata( + name: 'Email Wallet', + description: '', + url: '', + icons: [], + ), + ); } return _sessionData?.self; } @@ -216,7 +304,16 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { return _coinbaseData?.peer; } if (sessionService.isMagic) { - return _magicData?.peer; + return _magicData?.peer ?? + ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata( + name: 'Email Wallet', + description: '', + url: '', + icons: [], + ), + ); } return _sessionData?.peer; } @@ -224,8 +321,12 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { // String get email => _magicData?.email ?? ''; + String get userName => _magicData?.userName ?? ''; + + AppKitSocialOption? get socialProvider => _magicData?.provider; + // - String? get address { + String? getAddress(String namespace) { if (sessionService.noSession) { return null; } @@ -235,8 +336,8 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { if (sessionService.isMagic) { return _magicData!.address; } - final namespace = namespaces?[CoreConstants.namespace]; - final accounts = namespace?.accounts ?? []; + final ns = namespaces?[namespace]; + final accounts = ns?.accounts ?? []; if (accounts.isNotEmpty) { return NamespaceUtils.getAccount(accounts.first); } @@ -265,9 +366,6 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { if (sessionService.isCoinbase) { return CoinbaseService.defaultWalletData.listing.name; } - if (sessionService.isMagic) { - return MagicService.defaultWalletData.listing.name; - } if (sessionService.isWC) { return peer?.metadata.name; } @@ -284,25 +382,40 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { Map? _namespaces() { if (sessionService.isCoinbase) { + // Coinbase only supports eip155 chains + final eip155 = NetworkUtils.eip155; + final allEIP155 = ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: eip155, + ).map((e) => '$eip155:${e.chainId}').toList(); return { - CoreConstants.namespace: Namespace( - chains: ['${CoreConstants.namespace}:$chainId'], - accounts: ['${CoreConstants.namespace}:$chainId:$address'], - methods: [...CoinbaseService.supportedMethods], + eip155: Namespace( + chains: [...allEIP155], + accounts: [...getAccounts(namespace: eip155)!], + methods: [...GetIt.I().supportedMethods], + // Coinbase does not have events as it doesn't use WC protocol events: [], ), }; } + if (sessionService.isMagic) { + final ns = ReownAppKitModalNetworks.getNamespaceForChainId( + _magicData!.chainId, + ); + final allChains = ReownAppKitModalNetworks.getAllSupportedNetworks( + namespace: ns, + ).map((e) => '$ns:${e.chainId}').toList(); return { - CoreConstants.namespace: Namespace( - chains: ['${CoreConstants.namespace}:$chainId'], - accounts: ['${CoreConstants.namespace}:$chainId:$address'], - methods: [...MagicService.supportedMethods], + ns: Namespace( + chains: [...allChains], + accounts: [...getAccounts(namespace: ns)!], + methods: [...NetworkUtils.defaultNetworkMethods[ns]!], + // Magic does not have events as it doesn't use WC protocol events: [], ), }; } + return namespaces; } 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 index 8ea32e1..d82b54b 100644 --- a/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart +++ b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart @@ -5,6 +5,7 @@ import 'package:reown_appkit/reown_appkit.dart'; part 'appkit_siwe_config.g.dart'; part 'appkit_siwe_config.freezed.dart'; +/// Object to pass to [siweConfig:] parameter of ReownAppKitModal to enable or disable One-Click Auth + SIWE class SIWEConfig { final Future Function() getNonce; final Future Function() getMessageParams; @@ -46,6 +47,11 @@ class SIWEConfig { this.nonceRefetchIntervalMs = 300000, this.sessionRefetchIntervalMs = 300000, }); + + @override + String toString() { + return 'SIWEConfig(enabled: $enabled, signOutOnDisconnect: $signOutOnDisconnect, signOutOnAccountChange: $signOutOnAccountChange, signOutOnNetworkChange: $signOutOnNetworkChange, nonceRefetchIntervalMs: $nonceRefetchIntervalMs, sessionRefetchIntervalMs: $sessionRefetchIntervalMs)'; + } } @freezed diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_social_options.dart b/packages/reown_appkit/lib/modal/models/public/appkit_social_options.dart new file mode 100644 index 0000000..d6ad2a2 --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_social_options.dart @@ -0,0 +1,17 @@ +enum AppKitSocialOption { + X, + Apple, + Discord, + Farcaster; + // GitHub, + // Facebook, + // Google, + // Twitch, + // Telegram, + + factory AppKitSocialOption.fromString(String value) { + return AppKitSocialOption.values.firstWhere( + (e) => e.name.toLowerCase() == value.toLowerCase(), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/about_networks.dart b/packages/reown_appkit/lib/modal/pages/about_networks.dart index 9205d85..952645e 100644 --- a/packages/reown_appkit/lib/modal/pages/about_networks.dart +++ b/packages/reown_appkit/lib/modal/pages/about_networks.dart @@ -1,6 +1,5 @@ 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'; @@ -46,10 +45,7 @@ class AboutNetworks extends StatelessWidget { ), const SizedBox(height: 8), SimpleIconButton( - onTap: () => launchUrlString( - UrlConstants.learnMoreUrl, - mode: LaunchMode.externalApplication, - ), + onTap: () => ReownCoreUtils.openURL(UrlConstants.learnMoreUrl), rightIcon: 'lib/modal/assets/icons/arrow_top_right.svg', title: 'Learn more', size: BaseButtonSize.small, diff --git a/packages/reown_appkit/lib/modal/pages/account_page.dart b/packages/reown_appkit/lib/modal/pages/account_page.dart index 00f7786..b1cf3e7 100644 --- a/packages/reown_appkit/lib/modal/pages/account_page.dart +++ b/packages/reown_appkit/lib/modal/pages/account_page.dart @@ -1,12 +1,17 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get_it/get_it.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/services/explorer_service/i_explorer_service.dart'; +import 'package:reown_appkit/modal/utils/asset_util.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'; @@ -88,7 +93,6 @@ class _DefaultAccountView extends StatelessWidget { Widget build(BuildContext context) { final themeData = ReownAppKitModalTheme.getDataOf(context); final themeColors = ReownAppKitModalTheme.colorsOf(context); - final radiuses = ReownAppKitModalTheme.radiusesOf(context); final isEmailLogin = _service.session?.sessionService.isMagic ?? false; return Column( mainAxisSize: MainAxisSize.min, @@ -121,60 +125,16 @@ class _DefaultAccountView extends StatelessWidget { 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()), - ), - ], - ), + child: _UpgradeWalletButton(), ), 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()); - }, - ), - ], - ), + child: _EmailAndSocialLoginButton(), ), + // Visibility( + // visible: !isEmailLogin, + // child: _ConnectedWalletButton(), + // ), const SizedBox.square(dimension: kPadding8), _SelectNetworkButton(), const SizedBox.square(dimension: kPadding8), @@ -201,6 +161,157 @@ class _DefaultAccountView extends StatelessWidget { } } +class _UpgradeWalletButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + return 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()), + ), + ], + ); + } +} + +class _EmailAndSocialLoginButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + final service = ModalProvider.of(context).instance; + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final provider = AppKitSocialOption.values.firstWhereOrNull( + (e) => e.name == service.session!.peer?.metadata.name, + ); + final title = + provider != null ? service.session!.userName : service.session!.email; + return Column( + children: [ + const SizedBox.square(dimension: kPadding8), + AccountListItem( + iconWidget: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: provider == null + ? RoundedIcon( + assetPath: 'lib/modal/assets/icons/mail.svg', + assetColor: themeColors.foreground100, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ) + : ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(34), + child: SvgPicture.asset( + AssetUtils.getThemedAsset( + context, + '${provider.name.toLowerCase()}_logo.svg', + ), + package: 'reown_appkit', + height: 34, + width: 34, + ), + ), + ), + title: title, + titleStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + onTap: provider == null + ? () => widgetStack.instance.push(EditEmailPage()) + : null, + trailing: provider != null ? const SizedBox.shrink() : null, + ), + ], + ); + } +} + +// ignore: unused_element +class _ConnectedWalletButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + final service = ModalProvider.of(context).instance; + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + String iconImage = ''; + if ((service.session!.peer?.metadata.icons ?? []).isNotEmpty) { + iconImage = service.session!.peer?.metadata.icons.first ?? ''; + } + final walletInfo = GetIt.I().getConnectedWallet(); + return Column( + children: [ + const SizedBox.square(dimension: kPadding8), + AccountListItem( + iconWidget: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: iconImage.isEmpty + ? RoundedIcon( + assetPath: 'lib/modal/assets/icons/wallet.svg', + assetColor: themeColors.inverse100, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ) + : ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(34), + child: CachedNetworkImage( + imageUrl: iconImage, + height: 34, + width: 34, + errorWidget: (context, url, error) { + return RoundedIcon( + assetPath: 'lib/modal/assets/icons/wallet.svg', + assetColor: themeColors.inverse100, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ); + }, + ), + ), + ), + title: service.session!.peer?.metadata.name ?? 'Connected Wallet', + titleStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + onTap: + walletInfo != null ? () => service.launchConnectedWallet() : null, + ), + ], + ); + } +} + class _SelectNetworkButton extends StatelessWidget { @override Widget build(BuildContext context) { @@ -209,7 +320,7 @@ class _SelectNetworkButton extends StatelessWidget { final themeColors = ReownAppKitModalTheme.colorsOf(context); final chainId = service.selectedChain?.chainId ?? ''; final imageId = ReownAppKitModalNetworks.getNetworkIconId(chainId); - final tokenImage = explorerService.instance.getAssetImageUrl(imageId); + final tokenImage = GetIt.I().getAssetImageUrl(imageId); final radiuses = ReownAppKitModalTheme.radiusesOf(context); return AccountListItem( iconWidget: Padding( @@ -230,12 +341,10 @@ class _SelectNetworkButton extends StatelessWidget { titleStyle: themeData.textStyles.paragraph500.copyWith( color: themeColors.foreground100, ), - onTap: () { - widgetStack.instance.push( - ReownAppKitModalSelectNetworkPage(), - event: ClickNetworksEvent(), - ); - }, + onTap: () => widgetStack.instance.push( + ReownAppKitModalSelectNetworkPage(), + event: ClickNetworksEvent(), + ), ); } } diff --git a/packages/reown_appkit/lib/modal/pages/all_social_logins.dart b/packages/reown_appkit/lib/modal/pages/all_social_logins.dart new file mode 100644 index 0000000..a7f8c95 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/all_social_logins.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; +import 'package:reown_appkit/modal/widgets/buttons/social_login_button.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AllSocialLoginsPage extends StatefulWidget { + const AllSocialLoginsPage({ + required this.onSelect, + }) : super(key: KeyConstants.allSocialLoginPageKey); + final Function(AppKitSocialOption) onSelect; + + @override + State createState() => + _AppKitModalMainWalletsPageState(); +} + +class _AppKitModalMainWalletsPageState extends State { + @override + Widget build(BuildContext context) { + final listItems = GetIt.I() + .socials + .map((item) => SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + '${item.name.toLowerCase()}_logo.svg', + ), + textAlign: TextAlign.left, + onTap: () => widget.onSelect(item), + title: item.name, + )) + .toList(); + return ModalNavbar( + title: 'All socials', + safeAreaLeft: true, + safeAreaRight: true, + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: kPadding12), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox.square(dimension: kListViewSeparatorHeight), + const SizedBox.square(dimension: kListViewSeparatorHeight), + ..._buttonsWithDivider(listItems), + ], + ), + ), + ), + ); + } + + List _buttonsWithDivider(List widgets) { + List spacedWidgets = []; + for (int i = 0; i < widgets.length; i++) { + spacedWidgets.add(widgets[i]); + if (i < widgets.length - 1) { + spacedWidgets.add( + const SizedBox.square(dimension: kListViewSeparatorHeight), + ); + } + } + return spacedWidgets; + } +} 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 index 52d01d9..f249353 100644 --- a/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart +++ b/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; @@ -15,9 +16,6 @@ class ApproveTransactionPage extends StatefulWidget { 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, @@ -27,7 +25,7 @@ class _ApproveTransactionPageState extends State { constraints: BoxConstraints( maxHeight: ResponsiveData.maxHeightOf(context), ), - child: magicService.instance.webview, + child: GetIt.I().webview, ), ); } diff --git a/packages/reown_appkit/lib/modal/pages/approve_siwe.dart b/packages/reown_appkit/lib/modal/pages/approve_siwe.dart index 8ad66ba..2a6b2dd 100644 --- a/packages/reown_appkit/lib/modal/pages/approve_siwe.dart +++ b/packages/reown_appkit/lib/modal/pages/approve_siwe.dart @@ -3,16 +3,16 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:get_it/get_it.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/i_analytics_service.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/siwe_service/i_siwe_service.dart'; +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.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'; @@ -34,6 +34,9 @@ class ApproveSIWEPage extends StatefulWidget { } class _ApproveSIWEPageState extends State { + ISiweService get _siweService => GetIt.I(); + IAnalyticsService get _analyticsService => GetIt.I(); + IReownAppKitModal? _appKitModal; double _position = 0.0; static const _duration = Duration(milliseconds: 1500); @@ -68,34 +71,36 @@ class _ApproveSIWEPageState extends State { 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 namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + final address = _appKitModal!.session!.getAddress(namespace)!; + _analyticsService.sendEvent(ClickSignSiweMessage(network: chainId)); + chainId = ReownAppKitModalNetworks.getCaip2Chain(chainId); // - final message = await siweService.instance!.createMessage( + final message = await _siweService.createMessage( chainId: chainId, address: address, ); // _appKitModal!.launchConnectedWallet(); - final signature = await siweService.instance!.signMessageRequest( + final signature = await _siweService.signMessageRequest( message, session: _appKitModal!.session!, ); // final clientId = await _appKitModal!.appKit!.core.crypto.getClientId(); - await siweService.instance!.verifyMessage( + await _siweService.verifyMessage( message: message, signature: signature, clientId: clientId, ); // - final siweSession = await siweService.instance!.getSession(); - final newSession = - _appKitModal!.session!.copyWith(siweSession: siweSession); + final siweSession = await _siweService.getSession(); + final newSession = _appKitModal!.session!.copyWith( + siweSession: siweSession, + ); // widget.onSiweFinish(newSession); // @@ -109,10 +114,9 @@ class _ApproveSIWEPageState extends State { } void _handleError(String? error) { - debugPrint('[$runtimeType] _handleError $error'); - String chainId = _appKitModal!.selectedChain?.chainId ?? '1'; - analyticsService.instance.sendEvent(SiweAuthError(network: chainId)); - toastService.instance.show(ToastMessage( + final chainId = _appKitModal!.selectedChain?.chainId ?? '1'; + _analyticsService.sendEvent(SiweAuthError(network: chainId)); + GetIt.I().show(ToastMessage( type: ToastType.error, text: error ?? 'Something went wrong.', )); diff --git a/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart b/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart index f76f71b..79b27c7 100644 --- a/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart +++ b/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.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/services/toast_service/i_toast_service.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'; @@ -22,24 +22,22 @@ class ConfirmEmailPage extends StatefulWidget { } class _ConfirmEmailPageState extends State { + IMagicService get _magicService => GetIt.I(); + @override void initState() { super.initState(); - magicService.instance.onMagicError.subscribe(_onMagicErrorEvent); + _magicService.onMagicError.subscribe(_onMagicErrorEvent); } @override void dispose() { - magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); - magicService.instance.step.value = EmailLoginStep.idle; + _magicService.onMagicError.unsubscribe(_onMagicErrorEvent); + _magicService.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 { @@ -48,8 +46,8 @@ class _ConfirmEmailPageState extends State { } void _goBack() { - magicService.instance.step.value = EmailLoginStep.idle; - magicService.instance.setEmail(''); + _magicService.step.value = EmailLoginStep.idle; + _magicService.setEmail(''); FocusManager.instance.primaryFocus?.unfocus(); widgetStack.instance.pop(); } @@ -57,7 +55,7 @@ class _ConfirmEmailPageState extends State { @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: magicService.instance.step, + valueListenable: _magicService.step, builder: (context, action, _) { final title = (action == EmailLoginStep.verifyDevice) ? 'Register device' @@ -74,9 +72,9 @@ class _ConfirmEmailPageState extends State { } if (action == EmailLoginStep.verifyOtp) { return VerifyOtpView( - currentEmail: magicService.instance.email.value, - resendEmail: magicService.instance.connectEmail, - verifyOtp: magicService.instance.connectOtp, + currentEmail: _magicService.email.value, + resendEmail: _magicService.connectEmail, + verifyOtp: _magicService.connectOtp, ); } return ContentLoading(viewHeight: 200.0); @@ -95,6 +93,7 @@ class _VerifyDeviceView extends StatefulWidget { class __VerifyDeviceViewState extends State<_VerifyDeviceView> { late DateTime _resendEnabledAt; + IMagicService get _magicService => GetIt.I(); @override void initState() { @@ -105,15 +104,15 @@ class __VerifyDeviceViewState extends State<_VerifyDeviceView> { void _resendEmail() async { final diff = DateTime.now().difference(_resendEnabledAt).inSeconds; if (diff < 0) { - toastService.instance.show(ToastMessage( + GetIt.I().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); + final email = _magicService.email.value; + await _magicService.connectEmail(value: email); _resendEnabledAt = DateTime.now().add(Duration(seconds: 30)); - toastService.instance.show(ToastMessage( + GetIt.I().show(ToastMessage( type: ToastType.success, text: 'Link email resent', )); @@ -155,7 +154,7 @@ class __VerifyDeviceViewState extends State<_VerifyDeviceView> { ), ), Text( - magicService.instance.email.value, + _magicService.email.value, textAlign: TextAlign.center, style: textStyles.paragraph500.copyWith( color: themeColors.foreground100, diff --git a/packages/reown_appkit/lib/modal/pages/connect_wallet_page.dart b/packages/reown_appkit/lib/modal/pages/connect_wallet_page.dart index eb5a6bf..526a510 100644 --- a/packages/reown_appkit/lib/modal/pages/connect_wallet_page.dart +++ b/packages/reown_appkit/lib/modal/pages/connect_wallet_page.dart @@ -2,14 +2,15 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:get_it/get_it.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/services/explorer_service/i_explorer_service.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/siwe_service/i_siwe_service.dart'; +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.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'; @@ -32,6 +33,9 @@ class ConnectWalletPage extends StatefulWidget { class _ConnectWalletPageState extends State with WidgetsBindingObserver { + IExplorerService get _explorerService => GetIt.I(); + ISiweService get _siweService => GetIt.I(); + IReownAppKitModal? _service; SegmentOption _selectedSegment = SegmentOption.mobile; ModalError? errorEvent; @@ -56,7 +60,7 @@ class _ConnectWalletPageState extends State if (state == AppLifecycleState.resumed) { final isOpen = _service?.isOpen ?? false; final isConnected = _service?.isConnected ?? false; - if (isOpen && isConnected && !siweService.instance!.enabled) { + if (isOpen && isConnected && !_siweService.enabled) { Future.delayed(Duration(seconds: 1), () { if (!mounted) return; _service?.closeModal(); @@ -88,7 +92,7 @@ class _ConnectWalletPageState extends State kNavbarHeight - (kPadding16 * 2); // - final walletRedirect = explorerService.instance.getWalletRedirect( + final walletRedirect = _explorerService.getWalletRedirect( _service!.selectedWallet, ); final webOnlyWallet = walletRedirect?.webOnly == true; @@ -97,7 +101,7 @@ class _ConnectWalletPageState extends State final selectedWallet = _service!.selectedWallet; final walletName = selectedWallet?.listing.name ?? 'Wallet'; final imageId = selectedWallet?.listing.imageId ?? ''; - final imageUrl = explorerService.instance.getWalletImageUrl(imageId); + final imageUrl = _explorerService.getWalletImageUrl(imageId); // return ModalNavbar( title: walletName, @@ -297,9 +301,10 @@ class _ConnectWalletPageState extends State Future _copyToClipboard(BuildContext context) async { final service = ModalProvider.of(context).instance; await Clipboard.setData(ClipboardData(text: service.wcUri!)); - toastService.instance.show( - ToastMessage(type: ToastType.success, text: 'Link copied'), - ); + GetIt.I().show(ToastMessage( + type: ToastType.success, + text: 'Link copied', + )); } } diff --git a/packages/reown_appkit/lib/modal/pages/connet_network_page.dart b/packages/reown_appkit/lib/modal/pages/connet_network_page.dart index aaad1bc..7882485 100644 --- a/packages/reown_appkit/lib/modal/pages/connet_network_page.dart +++ b/packages/reown_appkit/lib/modal/pages/connet_network_page.dart @@ -1,13 +1,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/services/explorer_service/i_explorer_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.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/siwe_service/i_siwe_service.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'; @@ -19,18 +20,21 @@ import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; import 'package:reown_appkit/reown_appkit.dart'; class ConnectNetworkPage extends StatefulWidget { - final ReownAppKitModalNetworkInfo chainInfo; const ConnectNetworkPage({ required this.chainInfo, + this.isMagic = false, }) : super(key: KeyConstants.connecNetworkPageKey); + final ReownAppKitModalNetworkInfo chainInfo; + final bool isMagic; + @override State createState() => _ConnectNetworkPageState(); } class _ConnectNetworkPageState extends State with WidgetsBindingObserver { - IReownAppKitModal? _service; + IReownAppKitModal? _appKitModal; ModalError? errorEvent; @override @@ -38,41 +42,75 @@ class _ConnectNetworkPageState extends State super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { - _service = ModalProvider.of(context).instance; - _service?.onModalError.subscribe(_errorListener); + _appKitModal = ModalProvider.of(context).instance; + _appKitModal?.onModalError.subscribe(_errorListener); setState(() {}); Future.delayed(const Duration(milliseconds: 300), () => _connect()); }); } + IMagicService get _magicService => GetIt.I(); + IExplorerService get _explorerService => GetIt.I(); + ISiweService get _siweService => GetIt.I(); + void _connect() async { errorEvent = null; - _service!.launchConnectedWallet(); - try { - await _service!.requestSwitchToChain(widget.chainInfo); - final chainId = widget.chainInfo.chainId; - final chainInfo = ReownAppKitModalNetworks.getNetworkById( - CoreConstants.namespace, - chainId, + if (widget.isMagic) { + final newCaip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + widget.chainInfo.chainId, ); - if (chainInfo != null) { - Future.delayed(const Duration(milliseconds: 300), () { - if (!siweService.instance!.enabled) { - widgetStack.instance.pop(); - } - }); + final success = await _magicService.switchNetwork(chainId: newCaip2Chain); + if (success) { + final siweEnabled = _siweService.config?.enabled != true; + final signOutOnNetworkChange = _siweService.signOutOnNetworkChange; + if (!siweEnabled || !signOutOnNetworkChange) { + final newCaip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + widget.chainInfo.chainId, + ); + await _magicService.getUser( + chainId: newCaip2Chain, + isUpdate: true, + ); + widgetStack.instance.pop(); + } + } + } else { + try { + _appKitModal!.launchConnectedWallet(); + await _appKitModal!.requestSwitchToChain(widget.chainInfo); + final chainId = widget.chainInfo.chainId; + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + final chainInfo = ReownAppKitModalNetworks.getNetworkById( + namespace, + chainId, + ); + if (chainInfo != null) { + Future.delayed(const Duration(milliseconds: 300), () { + if (!_siweService.enabled) { + widgetStack.instance.pop(); + } + }); + } + } on JsonRpcError catch (e) { + setState( + () => errorEvent = ModalError(e.message ?? 'An error occurred'), + ); + } on ReownAppKitModalException catch (e) { + setState(() => errorEvent = ModalError(e.message)); + } catch (e) { + setState(() => errorEvent = ModalError('An error occurred')); } - } 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) { + if (_appKitModal?.session?.sessionService.isCoinbase == true) { + if (_appKitModal?.selectedChain?.chainId == widget.chainInfo.chainId) { + if (!_siweService.enabled) { widgetStack.instance.pop(); } } @@ -84,14 +122,14 @@ class _ConnectNetworkPageState extends State @override void dispose() { - _service?.onModalError.unsubscribe(_errorListener); + _appKitModal?.onModalError.unsubscribe(_errorListener); WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { - if (_service == null) { + if (_appKitModal == null) { return ContentLoading(); } final themeData = ReownAppKitModalTheme.getDataOf(context); @@ -105,7 +143,7 @@ class _ConnectNetworkPageState extends State // final chainId = widget.chainInfo.chainId; final imageId = ReownAppKitModalNetworks.getNetworkIconId(chainId); - final imageUrl = explorerService.instance.getAssetImageUrl(imageId); + final imageUrl = _explorerService.getAssetImageUrl(imageId); // return ModalNavbar( title: widget.chainInfo.name, @@ -138,14 +176,16 @@ class _ConnectNetworkPageState extends State const SizedBox.square(dimension: 20.0), errorEvent != null ? Text( - 'Switch declined', + errorEvent?.message ?? 'Switch declined', textAlign: TextAlign.center, style: themeData.textStyles.paragraph500.copyWith( color: themeColors.error100, ), ) : Text( - 'Continue in ${_service?.session?.peer?.metadata.name ?? 'wallet'}', + widget.isMagic + ? 'Switching to ${widget.chainInfo.name}' + : 'Continue in ${_appKitModal?.session?.peer?.metadata.name ?? 'wallet'}', textAlign: TextAlign.center, style: themeData.textStyles.paragraph500.copyWith( color: themeColors.foreground100, @@ -154,14 +194,16 @@ class _ConnectNetworkPageState extends State const SizedBox.square(dimension: 8.0), errorEvent != null ? Text( - 'Switch can be declined by the user or if a previous request is still active', + 'Switch can be declined by the user or if the wallet doesn\'t support the selected chain.', textAlign: TextAlign.center, style: themeData.textStyles.small500.copyWith( color: themeColors.foreground200, ), ) : Text( - 'Accept switch request in your wallet', + widget.isMagic + ? 'Wait until it\'s completed' + : 'Accept switch request in your wallet', textAlign: TextAlign.center, style: themeData.textStyles.small500.copyWith( color: themeColors.foreground200, diff --git a/packages/reown_appkit/lib/modal/pages/edit_email_page.dart b/packages/reown_appkit/lib/modal/pages/edit_email_page.dart index 29e7e4c..4204001 100644 --- a/packages/reown_appkit/lib/modal/pages/edit_email_page.dart +++ b/packages/reown_appkit/lib/modal/pages/edit_email_page.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/i_magic_service.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'; @@ -25,15 +24,16 @@ class EditEmailPage extends StatefulWidget { class _EditEmailPageState extends State { late final String _currentEmailValue; + IMagicService get _magicService => GetIt.I(); @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); + _magicService.onMagicError.subscribe(_onMagicErrorEvent); + _currentEmailValue = _magicService.email.value; + if (!_magicService.isConnected.value) { + _magicService.connectEmail(value: _currentEmailValue); widgetStack.instance.popAllAndPush(ConfirmEmailPage()); } }); @@ -41,30 +41,26 @@ class _EditEmailPageState extends State { @override void dispose() { - magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); + _magicService.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(''); + _magicService.setEmail(_currentEmailValue); + _magicService.setNewEmail(''); widgetStack.instance.pop(); - magicService.instance.step.value = EmailLoginStep.idle; + _magicService.step.value = EmailLoginStep.idle; } @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: magicService.instance.step, + valueListenable: _magicService.step, builder: (context, action, _) { String title = 'Edit Email'; if (action == EmailLoginStep.verifyOtp) { @@ -87,12 +83,12 @@ class _EditEmailPageState extends State { action == EmailLoginStep.verifyOtp2) { return VerifyOtpView( currentEmail: (action == EmailLoginStep.verifyOtp2) - ? magicService.instance.newEmail.value - : magicService.instance.email.value, + ? _magicService.newEmail.value + : _magicService.email.value, resendEmail: _resendEmail, verifyOtp: (action == EmailLoginStep.verifyOtp2) - ? magicService.instance.updateEmailSecondaryOtp - : magicService.instance.updateEmailPrimaryOtp, + ? _magicService.updateEmailSecondaryOtp + : _magicService.updateEmailPrimaryOtp, ); } return _EditEmailView(); @@ -104,8 +100,8 @@ class _EditEmailPageState extends State { } Future _resendEmail({String? value}) async { - final email = magicService.instance.newEmail.value; - magicService.instance.updateEmail(value: email); + final email = _magicService.newEmail.value; + _magicService.updateEmail(value: email); } } @@ -115,6 +111,7 @@ class _EditEmailView extends StatefulWidget { } class __EditEmailViewState extends State<_EditEmailView> { + IMagicService get _magicService => GetIt.I(); String _newEmailValue = ''; late final String _currentEmailValue; bool _isValidEmail = false; @@ -122,12 +119,12 @@ class __EditEmailViewState extends State<_EditEmailView> { @override void initState() { super.initState(); - _currentEmailValue = magicService.instance.email.value; + _currentEmailValue = _magicService.email.value; _newEmailValue = _currentEmailValue; } void _onValueChange(String value) { - magicService.instance.setNewEmail(value); + _magicService.setNewEmail(value); _newEmailValue = value; final valid = CoreUtils.isValidEmail(value); setState(() { @@ -137,14 +134,14 @@ class __EditEmailViewState extends State<_EditEmailView> { void _onSubmittedEmail(String value) { FocusManager.instance.primaryFocus?.unfocus(); - // magicService.instance.setNewEmail(value); - magicService.instance.updateEmail(value: value); + // _magicService.setNewEmail(value); + _magicService.updateEmail(value: value); } void _goBack() { FocusManager.instance.primaryFocus?.unfocus(); - magicService.instance.setEmail(_currentEmailValue); - magicService.instance.setNewEmail(''); + _magicService.setEmail(_currentEmailValue); + _magicService.setNewEmail(''); widgetStack.instance.pop(); } diff --git a/packages/reown_appkit/lib/modal/pages/farcaster_qrcode_page.dart b/packages/reown_appkit/lib/modal/pages/farcaster_qrcode_page.dart new file mode 100644 index 0000000..b59dde5 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/farcaster_qrcode_page.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/pages/social_login_page.dart'; +import 'package:reown_appkit/modal/widgets/buttons/primary_button.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/reown_appkit.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/qr_code_view.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:shimmer/shimmer.dart'; + +class FarcasterQRCodePage extends StatefulWidget { + const FarcasterQRCodePage({ + required this.farcasterUri, + required this.farcasterCompleter, + }) : super(key: KeyConstants.farcasterQrCodePageKey); + final String farcasterUri; + final Future? farcasterCompleter; + + @override + State createState() => _FarcasterQRCodePageState(); +} + +class _FarcasterQRCodePageState extends State { + Widget? _qrQodeWidget; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + _qrQodeWidget = QRCodeView( + uri: widget.farcasterUri, + logoPath: 'lib/modal/assets/png/farcaster.png', + ); + setState(() {}); + widget.farcasterCompleter?.then((value) { + widgetStack.instance.push( + SocialLoginPage( + socialOption: AppKitSocialOption.Farcaster, + farcasterCompleter: widget.farcasterCompleter, + ), + replace: true, + ); + }); + }); + } + + @override + Widget build(BuildContext context) { + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final isPortrait = ResponsiveData.isPortrait(context); + + return ModalNavbar( + title: 'Farcaster', + 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 or', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: kPadding12), + child: PrimaryButton( + title: 'Open Farcaster', + color: Color(0xFF855DCD), + borderRadius: (radiuses.isSquare() + ? BorderRadius.all(Radius.zero) + : BorderRadius.circular( + BaseButtonSize.big.height / 2)), + onTap: () async { + await ReownCoreUtils.openURL(widget.farcasterUri); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart b/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart index 478378a..c3dc344 100644 --- a/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart +++ b/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart @@ -3,10 +3,7 @@ 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'; @@ -53,30 +50,31 @@ class GetWalletPage extends StatelessWidget { ? data.listing.appStore : data.listing.playStore; if ((url ?? '').isNotEmpty) { - uriService.instance.launchUrl( - Uri.parse(url!), - mode: LaunchMode.externalApplication, - ); + ReownCoreUtils.openURL(url!); } }, bottomItems: [ - AllWalletsItem( - title: 'Explore all', - onTap: () => uriService.instance.launchUrl( - Uri.parse(UrlConstants.exploreWallets), - mode: LaunchMode.externalApplication, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, ), - 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, + child: AllWalletsItem( + title: 'Explore all', + onTap: () => ReownCoreUtils.openURL( + UrlConstants.exploreWallets, + ), + 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, ), - 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 index 0c953f9..832fd61 100644 --- 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 @@ -1,11 +1,12 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/services/explorer_service/i_explorer_service.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'; @@ -25,6 +26,7 @@ class ReownAppKitModalAllWalletsPage extends StatefulWidget { class _AppKitModalAllWalletsPageState extends State { + IExplorerService get _explorerService => GetIt.I(); bool _paginating = false; final _controller = ScrollController(); @@ -47,14 +49,14 @@ class _AppKitModalAllWalletsPageState } Future _paginate() { - setState(() => _paginating = explorerService.instance.canPaginate); - return explorerService.instance.paginate(); + setState(() => _paginating = _explorerService.canPaginate); + return _explorerService.paginate(); } @override Widget build(BuildContext context) { final service = ModalProvider.of(context).instance; - final totalListings = explorerService.instance.totalListings.value; + final totalListings = _explorerService.totalListings.value; final rows = (totalListings / 4.0).ceil(); final isSearchAvailable = totalListings >= kShortWalletListCount; final maxHeight = (rows * kGridItemHeight) + @@ -70,7 +72,7 @@ class _AppKitModalAllWalletsPageState ), onBack: () { FocusManager.instance.primaryFocus?.unfocus(); - explorerService.instance.search(query: null); + _explorerService.search(query: null); widgetStack.instance.pop(); }, safeAreaBottom: false, 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 index 30b6b00..6b33996 100644 --- 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 @@ -1,18 +1,18 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/services/explorer_service/i_explorer_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.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/buttons/email_login_input_field.dart'; +import 'package:reown_appkit/modal/widgets/buttons/social_login_buttons_view.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.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'; @@ -35,31 +35,41 @@ class ReownAppKitModalMainWalletsPage extends StatefulWidget { class _AppKitModalMainWalletsPageState extends State { + IMagicService get _magicService => GetIt.I(); + IExplorerService get _explorerService => GetIt.I(); + @override void initState() { super.initState(); - magicService.instance.isEnabled.addListener(_mailEnabledListener); + _magicService.isEmailEnabled.addListener(_enabledListener); + _magicService.isSocialEnabled.addListener(_enabledListener); } - void _mailEnabledListener() { + void _enabledListener() { setState(() {}); } @override void dispose() { - magicService.instance.isEnabled.removeListener(_mailEnabledListener); + _magicService.isSocialEnabled.removeListener(_enabledListener); + _magicService.isEmailEnabled.removeListener(_enabledListener); super.dispose(); } @override Widget build(BuildContext context) { + final modalInstance = ModalProvider.of(context).instance; + final themeColors = ReownAppKitModalTheme.colorsOf(context); final service = ModalProvider.of(context).instance; final isPortrait = ResponsiveData.isPortrait(context); double maxHeight = isPortrait ? (kListItemHeight * 6) : ResponsiveData.maxHeightOf(context); + + final isSignIn = _magicService.isEmailEnabled.value || + _magicService.isSocialEnabled.value; return ModalNavbar( - title: 'Connect wallet', + title: isSignIn ? 'Sign in' : 'Connect wallet', leftAction: NavbarActionButton( asset: 'lib/modal/assets/icons/help.svg', action: () { @@ -82,13 +92,31 @@ class _AppKitModalMainWalletsPageState ), ); } + final emailEnabled = _magicService.isEmailEnabled.value; + final socials = _magicService.socials; + if (!modalInstance.featuresConfig.showMainWallets && + (emailEnabled || socials.isNotEmpty)) { + items.clear(); + } 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); + maxHeight += kListItemHeight; + } else { + maxHeight -= 10.0; + } + final socialEnabled = _magicService.isSocialEnabled.value; + if (socialEnabled) { + final length = socials.length; + if (length <= 4) { + maxHeight += (kListItemHeight * 2); + } else { + maxHeight += (kListItemHeight * 3); + } + } else { + maxHeight += 30.0; } final itemsToShow = items.getRange(0, itemsCount); return ConstrainedBox( @@ -98,35 +126,78 @@ class _AppKitModalMainWalletsPageState service.selectWallet(data); widgetStack.instance.push(const ConnectWalletPage()); }, - firstItem: _EmailLoginWidget(), + firstItem: Column( + children: [ + EmailLoginInputField(), + Visibility( + visible: emailEnabled || socialEnabled, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, + ), + child: Column( + children: [ + SocialLoginButtonsView(), + _LoginDivider(), + ], + ), + ), + ), + ], + ), itemList: itemsToShow.toList(), bottomItems: [ - AllWalletsItem( - trailing: (items.length <= kShortWalletListCount) - ? null - : ValueListenableBuilder( - valueListenable: - explorerService.instance.totalListings, - builder: (context, value, _) { - return WalletItemChip(value: value.lazyCount); + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: (!modalInstance.featuresConfig.showMainWallets && + (emailEnabled || socials.isNotEmpty)) + ? AllWalletsItem( + title: 'Continue with a wallet', + titleAlign: TextAlign.center, + leading: RoundedIcon( + padding: 10.0, + assetPath: + 'lib/modal/assets/icons/regular/wallet.svg', + assetColor: themeColors.foreground100, + circleColor: Colors.transparent, + borderColor: Colors.transparent, + ), + onTap: () { + widgetStack.instance.push( + const ReownAppKitModalAllWalletsPage(), + event: ClickAllWalletsEvent(), + ); + }, + ) + : AllWalletsItem( + trailing: (items.length <= kShortWalletListCount) + ? null + : ValueListenableBuilder( + valueListenable: + _explorerService.totalListings, + builder: (context, value, _) { + return WalletItemChip( + value: value.lazyCount, + ); + }, + ), + onTap: () { + if (items.length <= kShortWalletListCount) { + widgetStack.instance.push( + const ReownAppKitModalQRCodePage(), + event: SelectWalletEvent( + name: 'WalletConnect', + platform: AnalyticsPlatform.qrcode, + ), + ); + } else { + widgetStack.instance.push( + const ReownAppKitModalAllWalletsPage(), + event: ClickAllWalletsEvent(), + ); + } }, ), - onTap: () { - if (items.length <= kShortWalletListCount) { - widgetStack.instance.push( - const ReownAppKitModalQRCodePage(), - event: SelectWalletEvent( - name: 'WalletConnect', - platform: AnalyticsPlatform.qrcode, - ), - ); - } else { - widgetStack.instance.push( - const ReownAppKitModalAllWalletsPage(), - event: ClickAllWalletsEvent(), - ); - } - }, ), ], ), @@ -173,75 +244,3 @@ extension on int { 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).instance; - 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_qrcode_page.dart b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_qrcode_page.dart index 5ddf1ca..6b154bc 100644 --- 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 @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.dart'; import 'package:reown_appkit/reown_appkit.dart'; import 'package:shimmer/shimmer.dart'; @@ -12,7 +14,6 @@ import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.da 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 ReownAppKitModalQRCodePage extends StatefulWidget { const ReownAppKitModalQRCodePage() : super(key: KeyConstants.qrCodePageKey); @@ -23,7 +24,7 @@ class ReownAppKitModalQRCodePage extends StatefulWidget { } class _AppKitModalQRCodePageState extends State { - IReownAppKitModal? _service; + IReownAppKitModal? _appKitModal; Widget? _qrQodeWidget; // @@ -31,46 +32,34 @@ class _AppKitModalQRCodePageState extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { - _service = ModalProvider.of(context).instance; - _service!.addListener(_buildWidget); - _service!.appKit!.core.pairing.onPairingExpire.subscribe( + _appKitModal = ModalProvider.of(context).instance; + _appKitModal!.addListener(_buildWidget); + _appKitModal!.appKit!.core.pairing.onPairingExpire.subscribe( _onPairingExpire, ); - _service?.onModalError.subscribe(_onError); - await _service!.buildConnectionUri(); + await _appKitModal!.buildConnectionUri(); }); } void _buildWidget() => setState(() { _qrQodeWidget = QRCodeView( - uri: _service!.wcUri!, + uri: _appKitModal!.wcUri!, logoPath: 'lib/modal/assets/png/logo_wc.png', ); }); void _onPairingExpire(EventArgs? args) async { - await _service!.buildConnectionUri(); + await _appKitModal!.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( + _appKitModal!.appKit!.core.pairing.onPairingExpire.unsubscribe( _onPairingExpire, ); - _service!.removeListener(_buildWidget); - _service!.expirePreviousInactivePairings(); + _appKitModal!.removeListener(_buildWidget); + _appKitModal!.expirePreviousInactivePairings(); super.dispose(); } @@ -152,8 +141,9 @@ class _AppKitModalQRCodePageState extends State { Future _copyToClipboard(BuildContext context) async { final service = ModalProvider.of(context).instance; await Clipboard.setData(ClipboardData(text: service.wcUri!)); - toastService.instance.show( - ToastMessage(type: ToastType.success, text: 'Link copied'), - ); + GetIt.I().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 index 52a4bb1..b29468b 100644 --- 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 @@ -1,14 +1,15 @@ +import 'dart:math'; + 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/utils/core_utils.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'; @@ -24,17 +25,45 @@ class ReownAppKitModalSelectNetworkPage extends StatelessWidget { final Function(ReownAppKitModalNetworkInfo)? onTapNetwork; void _onSelectNetwork( - BuildContext context, ReownAppKitModalNetworkInfo chainInfo) async { - final service = ModalProvider.of(context).instance; - 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(); + BuildContext context, + ReownAppKitModalNetworkInfo chainInfo, + ) async { + final appKitModal = ModalProvider.of(context).instance; + if (appKitModal.isConnected) { + final tokenName = chainInfo.currency; + final formattedBalance = CoreUtils.formatChainBalance(null); + appKitModal.balanceNotifier.value = '$formattedBalance $tokenName'; + + final chainId = chainInfo.chainId; + final caip2Chain = ReownAppKitModalNetworks.getCaip2Chain(chainId); + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + final approvedChains = appKitModal.session!.getApprovedChains( + namespace: namespace, + ); + final isMagic = appKitModal.session!.sessionService.isMagic; + final isChainApproved = (approvedChains ?? []).contains(caip2Chain); + if (chainInfo.chainId == appKitModal.selectedChain?.chainId) { + if (widgetStack.instance.canPop()) { + widgetStack.instance.pop(); + } else { + appKitModal.closeModal(); + } + } else if (isChainApproved || isMagic) { + if (isMagic) { + widgetStack.instance.push(ConnectNetworkPage( + chainInfo: chainInfo, + isMagic: true, + )); + } else { + await appKitModal.selectChain(chainInfo, switchChain: true); + if (widgetStack.instance.canPop()) { + widgetStack.instance.pop(); + } else { + appKitModal.closeModal(); + } + } } else { widgetStack.instance.push(ConnectNetworkPage(chainInfo: chainInfo)); } @@ -50,8 +79,6 @@ class ReownAppKitModalSelectNetworkPage extends StatelessWidget { final service = ModalProvider.of(context).instance; 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', @@ -62,22 +89,25 @@ class ReownAppKitModalSelectNetworkPage extends StatelessWidget { 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( + child: NetworkServiceItemsListener( + builder: (context, initialised, items) { + if (!initialised) return const ContentLoading(); + // + final rows = min((items.length ~/ 4) + 1, 3); + final height = + (ResponsiveData.gridItemSzieOf(context).height * rows); + final maxHeight = height + (kPadding12 * (rows + 1)); + return ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxHeight), + child: NetworksGrid( onTapNetwork: (chainInfo) => _onSelectNetwork( context, chainInfo, ), itemList: items, - ); - }, - ), + ), + ); + }, ), ), Divider(color: themeColors.grayGlass005, height: 0.0), diff --git a/packages/reown_appkit/lib/modal/pages/social_login_page.dart b/packages/reown_appkit/lib/modal/pages/social_login_page.dart new file mode 100644 index 0000000..070d9b1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/social_login_page.dart @@ -0,0 +1,520 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/string_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/pages/farcaster_qrcode_page.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'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; +import 'package:reown_appkit/modal/utils/platform_utils.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/miscellaneous/content_loading.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/loading_border.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar_action_button.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; + +class SocialLoginPage extends StatefulWidget { + const SocialLoginPage({ + required this.socialOption, + this.farcasterCompleter, + }) : super(key: KeyConstants.socialLoginPage); + final AppKitSocialOption socialOption; + final Future? farcasterCompleter; + + @override + State createState() => _SocialLoginPageState(); +} + +class _SocialLoginPageState extends State { + IMagicService get _magicService => GetIt.I(); + IAnalyticsService get _analyticsService => GetIt.I(); + + IReownAppKitModal? _service; + ModalError? errorEvent; + bool _retrievingData = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + _service = ModalProvider.of(context).instance; + _service?.onModalError.subscribe(_errorListener); + if (widget.farcasterCompleter == null) { + _initSocialLogin(widget.socialOption); + setState(() {}); + } else { + widget.farcasterCompleter!.then((success) async { + await _completeFarcasterLogin(success); + }); + } + }); + } + + @override + void dispose() { + _service?.onModalError.unsubscribe(_errorListener); + super.dispose(); + } + + void _errorListener(ModalError? event) { + setState(() => errorEvent = event); + } + + Future _initSocialLogin(AppKitSocialOption option) async { + try { + setState(() => errorEvent = null); + _analyticsService.sendEvent(SocialLoginStarted( + provider: widget.socialOption.name.toLowerCase(), + )); + if (option == AppKitSocialOption.Farcaster) { + final farcasterUri = await _magicService.getFarcasterUri( + chainId: _service?.selectedChain?.chainId, + ); + + if (farcasterUri != null) { + await _continueInFarcaster(farcasterUri); + } + } else { + final schema = null; // _service?.appKit?.metadata.redirect?.universal; + final redirectUri = await _magicService.getSocialRedirectUri( + provider: option, + schema: schema, + chainId: _service?.selectedChain?.chainId, + ); + + if (redirectUri != null) { + if (schema == null && option.supportsWebView) { + await _continueInWebview(redirectUri); + // final result = await ReownSocialLogin.login(initialUrl: redirectUri); + // debugPrint('ReownSocialLogin $result'); + // if (result == null) { + // _cancelSocialLogin(); + // } else { + // await _completeSocialLogin(result); + // } + } else { + _magicService.onCompleteSocialLogin.subscribe( + _onCompleteSocialLogin, + ); + await ReownCoreUtils.openURL(redirectUri); + } + } + } + } catch (e) { + debugPrint('[$runtimeType] _initSocialLogin error $e'); + } + } + + Future _continueInWebview(String redirectUri) async { + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.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)); + final result = await showModalBottomSheet( + backgroundColor: Colors.black.withOpacity(0.5), + isDismissible: false, + isScrollControlled: true, + enableDrag: false, + useRootNavigator: false, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.9, + ), + context: context, + builder: (context) => Container( + decoration: BoxDecoration( + color: themeColors.background125, + borderRadius: innerContainerBorderRadius, + ), + child: _WebViewLoginWidget( + url: redirectUri, + onCancel: () { + Navigator.of(context).pop(false); + }, + ), + ), + ); + + if (result == false) { + _cancelSocialLogin(); + } else { + await _completeSocialLogin(result); + } + } + + void _onCompleteSocialLogin(CompleteSocialLoginEvent? event) async { + if (event != null) { + await _completeSocialLogin(event.url); + } else { + _cancelSocialLogin(); + } + } + + Future _completeSocialLogin(String url) async { + try { + setState(() => _retrievingData = true); + final success = await _magicService.connectSocial( + uri: '?${Uri.parse(url).query}', + ); + if (success == true) { + final caip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + _service?.selectedChain?.chainId ?? '1', + ); + await _magicService.getUser(chainId: caip2Chain, isUpdate: false); + _analyticsService.sendEvent(SocialLoginSuccess( + provider: widget.socialOption.name.toLowerCase(), + )); + _magicService.onCompleteSocialLogin.unsubscribe( + _onCompleteSocialLogin, + ); + } else { + _cancelSocialLogin(); + } + } catch (e) { + debugPrint('[$runtimeType] _completeSocialLogin error $e'); + _cancelSocialLogin(); + setState(() => _retrievingData = false); + } + } + + Future _continueInFarcaster(String farcasterUri) async { + widgetStack.instance.push( + replace: true, + FarcasterQRCodePage( + farcasterUri: farcasterUri, + farcasterCompleter: _magicService.awaitFarcasterResponse(), + ), + ); + } + + Future _completeFarcasterLogin(bool success) async { + if (success == false) { + _cancelSocialLogin(); + } else { + setState(() => _retrievingData = true); + final caip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + _service?.selectedChain?.chainId ?? '1', + ); + await _magicService.getUser(chainId: caip2Chain, isUpdate: false); + } + } + + void _cancelSocialLogin() { + debugPrint('[$runtimeType] _cancelSocialLogin'); + errorEvent = ModalError('User canceled'); + setState(() => _retrievingData = false); + _analyticsService.sendEvent(SocialLoginError( + provider: widget.socialOption.name.toLowerCase(), + )); + } + + @override + Widget build(BuildContext context) { + if (_service == null) { + return ContentLoading(); + } + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final isPortrait = ResponsiveData.isPortrait(context); + final maxWidth = isPortrait + ? ResponsiveData.maxWidthOf(context) + : ResponsiveData.maxHeightOf(context) - + kNavbarHeight - + (kPadding16 * 2); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + return ModalNavbar( + title: widget.socialOption.name, + noBack: 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), + errorEvent == null + ? LoadingBorder( + animate: errorEvent == null, + borderRadius: kSelectedWalletIconHeight, + child: ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(maxWidth), + child: SvgPicture.asset( + AssetUtils.getThemedAsset( + context, + '${widget.socialOption.name.toLowerCase()}_logo.svg', + ), + package: 'reown_appkit', + ), + ), + ) + : Stack( + children: [ + SizedBox.square( + dimension: kSelectedWalletIconHeight, + child: ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(maxWidth), + child: SvgPicture.asset( + AssetUtils.getThemedAsset( + context, + '${widget.socialOption.name.toLowerCase()}_logo.svg', + ), + package: 'reown_appkit', + ), + ), + ), + Positioned( + bottom: 0, + right: 0, + 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, + ), + ), + ), + ], + ), + const SizedBox.square(dimension: 20.0), + Text( + 'Log in with ${widget.socialOption.name}', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: 8.0), + errorEvent == null + ? Text( + _retrievingData + ? 'Retrieving user data' + : 'Connect in the provider window', + textAlign: TextAlign.center, + style: themeData.textStyles.small500.copyWith( + color: themeColors.foreground200, + ), + ) + : SimpleIconButton( + onTap: () { + _initSocialLogin(widget.socialOption); + }, + leftIcon: 'lib/modal/assets/icons/refresh_back.svg', + title: 'Try again', + backgroundColor: Colors.transparent, + foregroundColor: themeColors.accent100, + ), + const SizedBox.square(dimension: kPadding16), + ], + ), + ), + if (!isPortrait) const SizedBox.square(dimension: kPadding16), + ], + ), + ), + ); + } +} + +class _WebViewLoginWidget extends StatefulWidget { + const _WebViewLoginWidget({ + required this.url, + required this.onCancel, + }); + final String url; + final VoidCallback onCancel; + + @override + State<_WebViewLoginWidget> createState() => __WebViewLoginWidgetState(); +} + +class __WebViewLoginWidgetState extends State<_WebViewLoginWidget> { + final _webViewController = WebViewController(); + final _cookieManager = WebViewCookieManager(); + + @override + void initState() { + super.initState(); + _init(); + } + + // ignore: unused_element + Future _clearCookies() async { + if (!kDebugMode) return; + try { + if (WebViewPlatform.instance is WebKitWebViewPlatform) { + final webKitManager = + _cookieManager.platform as WebKitWebViewCookieManager; + webKitManager.clearCookies(); + } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + final androidManager = + _cookieManager.platform as AndroidWebViewCookieManager; + androidManager.clearCookies(); + androidManager.setAcceptThirdPartyCookies( + _webViewController.platform as AndroidWebViewController, + kDebugMode, + ); + } + } catch (e) { + debugPrint('[$runtimeType] _clearCookies error $e'); + } + } + + void _setDebugMode() { + if (!kDebugMode) return; + if (Platform.isIOS) { + final wkCtrl = _webViewController.platform as WebKitWebViewController; + wkCtrl.setInspectable(true); + } + if (Platform.isAndroid) { + if (_webViewController.platform is AndroidWebViewController) { + final aCtrl = _webViewController.platform as AndroidWebViewController; + aCtrl.setMediaPlaybackRequiresUserGesture(false); + AndroidWebViewController.enableDebugging(true); + + final cookieManager = + _cookieManager.platform as AndroidWebViewCookieManager; + cookieManager.setAcceptThirdPartyCookies( + _webViewController.platform as AndroidWebViewController, + true, + ); + } + } + } + + Future _fitToScreen() async { + return await _webViewController.runJavaScript(''' + if (document.querySelector('meta[name="viewport"]') === null) { + var meta = document.createElement('meta'); + meta.name = 'viewport'; + meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; + document.head.appendChild(meta); + } else { + document.querySelector('meta[name="viewport"]').setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); + } + '''); + } + + Future _init() async { + _setDebugMode(); + // await _clearCookies(); + // await _webViewController.clearCache(); + // await _webViewController.clearLocalStorage(); + await _webViewController.enableZoom(false); + await _webViewController.setJavaScriptMode(JavaScriptMode.unrestricted); + await _fitToScreen(); + await _webViewController.setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (NavigationRequest request) { + final uri = Uri.parse(request.url); + final params = uri.queryParameters; + final secureOrigin1 = UrlConstants.secureOrigin1; + final secureOrigin2 = UrlConstants.secureOrigin2; + final origin1 = uri.authority == secureOrigin1; + final origin2 = uri.authority == secureOrigin2; + final hasState = params.containsKey('state'); + if ((origin1 || origin2) && hasState) { + Future.delayed(Duration(milliseconds: 500), () { + Navigator.of(context).pop(request.url); + }); + } + return NavigationDecision.navigate; + }, + ), + ); + await _webViewController.loadRequest(Uri.parse(widget.url)); + } + + @override + Widget build(BuildContext context) { + return ModalNavbar( + title: '', + noBack: true, + noClose: true, + rightActions: [ + NavbarActionButton( + asset: 'lib/modal/assets/icons/disconnect.svg', + dimension: 48.0, + action: () async { + await _clearCookies(); + await _webViewController.clearCache(); + await _webViewController.clearLocalStorage(); + await _webViewController.reload(); + }, + ), + NavbarActionButton( + asset: 'lib/modal/assets/icons/close.svg', + action: widget.onCancel, + ), + ], + body: WebViewWidget(controller: _webViewController), + ); + } +} + +extension _AppKitSocialOptionExtension on AppKitSocialOption { + bool get supportsWebView { + switch (this) { + case AppKitSocialOption.X: + return true; + case AppKitSocialOption.Apple: + return true; + case AppKitSocialOption.Discord: + return true; + case AppKitSocialOption.Farcaster: + return true; + // case AppKitSocialOption.GitHub: + // return true; + // case AppKitSocialOption.Facebook: + // return true; + // case AppKitSocialOption.Twitch: + // return true; + // case AppKitSocialOption.Telegram: + // return true; + // case AppKitSocialOption.Google: + // return 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 index acfa922..9178feb 100644 --- a/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart +++ b/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/analytics_service/i_analytics_service.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'; @@ -38,11 +37,8 @@ class UpgradeWalletPage extends StatelessWidget { SimpleIconButton( leftIcon: 'lib/modal/assets/icons/wc.svg', onTap: () { - analyticsService.instance.sendEvent(EmailUpgradeFromModal()); - launchUrlString( - UrlConstants.secureDashboard, - mode: LaunchMode.externalApplication, - ); + GetIt.I().sendEvent(EmailUpgradeFromModal()); + ReownCoreUtils.openURL(UrlConstants.secureDashboard); }, rightIcon: 'lib/modal/assets/icons/arrow_top_right.svg', title: Uri.parse(UrlConstants.secureDashboard).authority, 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 index 49ca02d..6ac2c0f 100644 --- a/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart +++ b/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart @@ -48,9 +48,9 @@ class AnalyticsService implements IAnalyticsService { _isEnabled = enableAnalytics!; } _bundleId = await ReownCoreUtils.getPackageName(); - _core.logger.d('[$runtimeType] enabled: $_isEnabled'); + _core.logger.i('[$runtimeType] enabled: $_isEnabled'); } catch (e, _) { - _core.logger.d('[$runtimeType] init error', error: e); + _core.logger.e('[$runtimeType] init error', error: e); } } @@ -63,9 +63,10 @@ class AnalyticsService implements IAnalyticsService { ); final json = jsonDecode(response.body) as Map; final enabled = json['isAnalyticsEnabled'] as bool?; + _core.logger.i('[$runtimeType] fetch result $enabled'); return enabled ?? false; - } catch (e, _) { - _core.logger.d('[$runtimeType] fetch error', error: e); + } catch (e, s) { + _core.logger.e('[$runtimeType] fetch error', error: e, stackTrace: s); return false; } } @@ -90,9 +91,9 @@ class AnalyticsService implements IAnalyticsService { if (code == 200 || code == 202) { _eventsController.sink.add(analyticsEvent.toMap()); } - _core.logger.d('[$runtimeType] send event $code: $body'); + _core.logger.i('[$runtimeType] send event $code: $body'); } catch (e, _) { - _core.logger.d('[$runtimeType] send event error', error: e); + _core.logger.e('[$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 deleted file mode 100644 index 83ccb30..0000000 --- a/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service_singleton.dart +++ /dev/null @@ -1,7 +0,0 @@ -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/models/analytics_event.dart b/packages/reown_appkit/lib/modal/services/analytics_service/models/analytics_event.dart index 2af5a77..1d60ef4 100644 --- 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 @@ -587,3 +587,72 @@ class SiweAuthError implements AnalyticsEvent { if (properties != null) 'properties': properties, }; } + +class SocialLoginStarted implements AnalyticsEvent { + final String _provider; + SocialLoginStarted({required String provider}) : _provider = provider; + + @override + String get type => 'track'; + + @override + String get event => 'LOGIN_STARTED'; + + @override + Map? get properties => { + 'provider': _provider, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class SocialLoginSuccess implements AnalyticsEvent { + final String _provider; + SocialLoginSuccess({required String provider}) : _provider = provider; + + @override + String get type => 'track'; + + @override + String get event => 'LOGIN_SUCCESS'; + + @override + Map? get properties => { + 'provider': _provider, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class SocialLoginError implements AnalyticsEvent { + final String _provider; + SocialLoginError({required String provider}) : _provider = provider; + + @override + String get type => 'track'; + + @override + String get event => 'LOGIN_ERROR'; + + @override + Map? get properties => { + 'provider': _provider, + }; + + @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 index 6923eed..60079da 100644 --- a/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart +++ b/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart @@ -22,7 +22,7 @@ class BlockChainService implements IBlockChainService { Map get _requiredHeaders => { 'x-sdk-type': CoreConstants.X_SDK_TYPE, - 'x-sdk-version': 'flutter-${CoreConstants.X_SDK_VERSION}', + 'x-sdk-version': CoreConstants.X_SDK_VERSION, }; @override @@ -35,74 +35,73 @@ class BlockChainService implements IBlockChainService { 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) { + _core.logger.i('[$runtimeType] getIdentity $address => ${response.body}'); + if (response.statusCode == 200 && response.body.isNotEmpty) { return BlockchainIdentity.fromJson(jsonDecode(response.body)); + } + if (response.statusCode == 400) { + final errorData = jsonDecode(response.body) as Map; + final reasons = errorData['reasons'] as List; + final reason = reasons.isNotEmpty + ? reasons.first['description'] ?? '' + : response.body; + throw Exception(reason); } else { throw Exception('Failed to load avatar'); } } catch (e) { - _core.logger.e('[$runtimeType] getIdentity: $e'); + _core.logger.e('[$runtimeType] getIdentity $address error => $e'); rethrow; } } - int _retries = 1; @override - Future rpcRequest({ - // required String? topic, + Future getBalance({ + required String address, + required String namespace, 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 queryParams = {..._requiredParams, 'chainId': '$namespace:$chainId'}; final response = await http.post( uri.replace(queryParameters: queryParams), - headers: { - ..._requiredHeaders, - 'Content-Type': 'application/json', - }, + headers: {..._requiredHeaders, 'Content-Type': 'application/json'}, body: jsonEncode({ 'id': 1, 'jsonrpc': '2.0', - 'method': request.method, - 'params': request.params, + 'method': _balanceMetod(namespace), + 'params': [ + address, + if (namespace == NetworkUtils.eip155) 'latest', + ], }), ); + _core.logger.i( + '[$runtimeType] getBalance $namespace, $chainId, $address => ${response.body}', + ); 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); + return _parseBalanceResult(namespace, response.body); } 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}', - ); + _core.logger.e('[$runtimeType] getBalance, parse result error => $e'); + throw Exception('Failed to load balance. $e'); } } + try { + final errorData = jsonDecode(response.body) as Map; + final reasons = errorData['reasons'] as List; + final reason = reasons.isNotEmpty + ? reasons.first['description'] ?? '' + : response.body; + throw Exception(reason); + } catch (e) { + _core.logger.e('[$runtimeType] getBalance, decode result error => $e'); + rethrow; + } } T _parseRpcResultAs(String body) { @@ -126,49 +125,28 @@ class BlockChainService implements IBlockChainService { } } - // @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 ''; - // } + String _balanceMetod(String namespace) { + if (namespace == NetworkUtils.eip155) { + return 'eth_getBalance'; + } else if (namespace == NetworkUtils.solana) { + return 'getBalance'; + } + return ''; + } - // @override - // Future fetchEnsAvatar(String rpcUrl, String address) async { - // return ''; - // } + double _parseBalanceResult(String namespace, String balanceResult) { + if (namespace == NetworkUtils.solana) { + final result = _parseRpcResultAs>(balanceResult); + final value = result['value'] as int; + return value / 1000000000.0; + } else if (namespace == NetworkUtils.eip155) { + final result = _parseRpcResultAs(balanceResult); + final amount = EtherAmount.fromBigInt( + EtherUnit.wei, + hexToInt(result), + ); + return amount.getValueInUnit(EtherUnit.ether); + } + return 0.0; + } } 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 deleted file mode 100644 index 3ea620c..0000000 --- a/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service_singleton.dart +++ /dev/null @@ -1,7 +0,0 @@ -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 index cdc0e8a..7744360 100644 --- 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 @@ -1,5 +1,4 @@ import 'package:reown_appkit/modal/services/blockchain_service/models/blockchain_identity.dart'; -import 'package:reown_appkit/reown_appkit.dart'; abstract class IBlockChainService { Future init(); @@ -7,9 +6,9 @@ abstract class IBlockChainService { /// Gets the name and avatar of a provided address on the given chain Future getIdentity(String address); - Future rpcRequest({ - // required String? topic, + Future getBalance({ + required String address, + required String namespace, required String chainId, - required SessionRequestParams request, }); } 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 index 0db48ce..286bbe1 100644 --- a/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service.dart +++ b/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service.dart @@ -1,12 +1,11 @@ import 'dart:convert'; import 'package:flutter/services.dart'; -import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:get_it/get_it.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'; @@ -14,6 +13,7 @@ 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/modal/services/explorer_service/i_explorer_service.dart'; import 'package:reown_appkit/reown_appkit.dart'; class CoinbaseService implements ICoinbaseService { @@ -53,16 +53,17 @@ class CoinbaseService implements ICoinbaseService { publicKey: '', ); - static const supportedMethods = [ - ...MethodsConstants.requiredMethods, - 'eth_requestAccounts', - 'eth_signTypedData_v3', - 'eth_signTypedData_v4', - 'eth_signTransaction', - MethodsConstants.walletSwitchEthChain, - MethodsConstants.walletAddEthChain, - 'wallet_watchAsset', - ]; + @override + List get supportedMethods => [ + ...MethodsConstants.requiredMethods, + 'eth_requestAccounts', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + 'eth_signTransaction', + MethodsConstants.walletSwitchEthChain, + MethodsConstants.walletAddEthChain, + 'wallet_watchAsset', + ]; @override Event onCoinbaseConnect = Event(); @@ -83,6 +84,8 @@ class CoinbaseService implements ICoinbaseService { late ReownAppKitModalWalletInfo _walletData; late final IReownCore _core; + IExplorerService get _explorerService => GetIt.I(); + CoinbaseService({ required PairingMetadata metadata, required IReownCore core, @@ -96,14 +99,17 @@ class CoinbaseService implements ICoinbaseService { if (!_enabled) return; // Configure SDK for each platform - _walletData = (await explorerService.instance.getCoinbaseWalletObject()) ?? - defaultWalletData; + _walletData = + (await _explorerService.getCoinbaseWalletObject()) ?? defaultWalletData; final imageId = defaultWalletData.listing.imageId; - _iconImage = explorerService.instance.getWalletImageUrl(imageId); + _iconImage = _explorerService.getWalletImageUrl(imageId); final walletLink = _walletData.listing.mobileLink ?? ''; final redirect = _metadata.redirect; final callback = redirect?.universal ?? redirect?.native ?? ''; + _core.logger.i( + '[$runtimeType] init with host: ${Uri.parse(walletLink)}, callback: ${Uri.parse(callback)}', + ); if (callback.isNotEmpty || walletLink.isNotEmpty) { try { final config = Configuration( @@ -121,6 +127,7 @@ class CoinbaseService implements ICoinbaseService { } } else { _enabled = false; + _core.logger.e('[$runtimeType] Initialization error'); throw CoinbaseServiceException('Initialization error'); } } @@ -170,13 +177,16 @@ class CoinbaseService implements ICoinbaseService { ), ); onCoinbaseConnect.broadcast(CoinbaseConnectEvent(data)); + _core.logger.i('[$runtimeType] getAccount ${data.toJson()}'); return; } on PlatformException catch (e, s) { + _core.logger.e('[$runtimeType] getAccount PlatformException $e'); // 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) { + _core.logger.e('[$runtimeType] getAccount $e'); onCoinbaseError.broadcast(CoinbaseErrorEvent('Initial handshake error')); throw CoinbaseServiceException('Initial handshake error', e, s); } @@ -189,6 +199,7 @@ class CoinbaseService implements ICoinbaseService { }) async { await _checkInstalled(); final cid = chainId.contains(':') ? chainId.split(':').last : chainId; + _core.logger.i('[$runtimeType] request $chainId, ${request.toJson()}'); try { final req = Request(actions: [request.toCoinbaseRequest(cid)]); final result = (await CoinbaseWalletSDK.shared.makeRequest(req)).first; @@ -222,11 +233,14 @@ class CoinbaseService implements ICoinbaseService { onCoinbaseResponse.broadcast(CoinbaseResponseEvent(data: value)); break; } + _core.logger.i('[$runtimeType] request result $value'); return value; } on CoinbaseServiceException catch (e) { + _core.logger.e('[$runtimeType] request CoinbaseServiceException $e'); onCoinbaseError.broadcast(CoinbaseErrorEvent(e.message)); rethrow; } on PlatformException catch (e, s) { + _core.logger.e('[$runtimeType] request PlatformException $e'); final message = 'Coinbase Wallet Error: (${e.code}) ${e.message}'; onCoinbaseError.broadcast(CoinbaseErrorEvent(message)); throw CoinbaseServiceException(message, e, s); @@ -238,6 +252,7 @@ class CoinbaseService implements ICoinbaseService { try { return await CoinbaseWalletSDK.shared.isAppInstalled(); } catch (e, s) { + _core.logger.e('[$runtimeType] isInstalled $e'); throw CoinbaseServiceException('Check is installed error', e, s); } } @@ -247,6 +262,7 @@ class CoinbaseService implements ICoinbaseService { try { return await CoinbaseWalletSDK.shared.isConnected(); } catch (e, s) { + _core.logger.e('[$runtimeType] isConnected $e'); throw CoinbaseServiceException('Check is connected error', e, s); } } @@ -256,6 +272,7 @@ class CoinbaseService implements ICoinbaseService { try { return CoinbaseWalletSDK.shared.resetSession(); } catch (e, s) { + _core.logger.e('[$runtimeType] resetSession $e'); throw CoinbaseServiceException('Reset session error', e, s); } } @@ -315,9 +332,12 @@ extension on SessionRequestParams { case MethodsConstants.walletSwitchEthChain: case MethodsConstants.walletAddEthChain: try { - final chainInfo = ReownAppKitModalNetworks.getNetworkById( - CoreConstants.namespace, + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( chainId!, + ); + final chainInfo = ReownAppKitModalNetworks.getNetworkById( + namespace, + chainId, )!; return AddEthereumChain( chainId: chainInfo.chainId, 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 deleted file mode 100644 index bc8a08b..0000000 --- a/packages/reown_appkit/lib/modal/services/coinbase_service/coinbase_service_singleton.dart +++ /dev/null @@ -1,7 +0,0 @@ -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 index 7a93b8c..22fe3c5 100644 --- 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 @@ -21,6 +21,8 @@ class CoinbaseNotEnabledException extends CoinbaseServiceException { } abstract class ICoinbaseService { + List get supportedMethods; + Future init(); Future isConnected(); Future getAccount(); 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 index 652def3..2366368 100644 --- a/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart +++ b/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.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'; @@ -11,7 +12,7 @@ import 'package:reown_appkit/modal/services/explorer_service/models/native_app_d 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/services/uri_service/i_url_utils.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'; @@ -22,6 +23,8 @@ import 'package:reown_appkit/reown_appkit.dart'; const int _defaultEntriesCount = 48; class ExplorerService implements IExplorerService { + IUriService get _uriService => GetIt.I(); + final http.Client _client; final String _referer; @@ -87,6 +90,8 @@ class ExplorerService implements IExplorerService { bool get canPaginate => _canPaginate; late final String _bundleId; + late final Set _chains; + late final Map namespaces; ExplorerService({ required IReownCore core, @@ -94,6 +99,7 @@ class ExplorerService implements IExplorerService { this.featuredWalletIds, this.includedWalletIds, this.excludedWalletIds, + this.namespaces = const {}, }) : _core = core, _referer = referer, _client = http.Client(); @@ -105,6 +111,10 @@ class ExplorerService implements IExplorerService { } _bundleId = await ReownCoreUtils.getPackageName(); + _chains = NamespaceUtils.getChainIdsFromRequiredNamespaces( + requiredNamespaces: namespaces, + ).map((chainId) => NamespaceUtils.getNamespaceFromChain(chainId)).toSet(); + // 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(); @@ -149,13 +159,12 @@ class ExplorerService implements IExplorerService { final schema = WCSampleWallets.getSampleWalletScheme( sampleWallet.listing.id, ); - final installed = await uriService.instance.isInstalled(schema); + final installed = await _uriService.isInstalled(schema); if (installed) { sampleWallet = sampleWallet.copyWith(installed: true); sampleWallets.add(sampleWallet); } } - _core.logger.d('[$runtimeType] sample wallets: ${sampleWallets.length}'); return sampleWallets; } @@ -215,10 +224,6 @@ class ExplorerService implements IExplorerService { ); return apiResponse.data.toList(); } else { - _core.logger.d( - '⛔ [$runtimeType] error fetching native data ${response.request?.url}', - error: response.statusCode, - ); return []; } } catch (e) { @@ -249,7 +254,7 @@ class ExplorerService implements IExplorerService { // this query gives me a count of installedWalletsParam.length final installedWallets = await _fetchListings(params: params); _core.logger.d( - '[$runtimeType] installed wallets: ${installedWallets.length}', + '[$runtimeType] ${installedWallets.length} installed wallets', ); return installedWallets.setInstalledFlag(); } @@ -282,16 +287,15 @@ class ExplorerService implements IExplorerService { RequestParams? params, bool updateCount = true, }) async { - final queryParams = params?.toJson() ?? {}; + params = params?.copyWith(chains: _chains.join(',')); final headers = CoreUtils.getAPIHeaders( _core.projectId, _referer, _bundleId, ); final uri = Uri.parse('${UrlConstants.apiService}/getWallets').replace( - queryParameters: queryParams, + queryParameters: params?.toJson(), ); - _core.logger.d('[$runtimeType] fetching $uri'); try { final response = await _client.get(uri, headers: headers); if (response.statusCode == 200 || response.statusCode == 202) { @@ -304,15 +308,10 @@ class ExplorerService implements IExplorerService { } 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( + _core.logger.e( '[$runtimeType] error fetching listings: $uri', error: e, ); @@ -353,7 +352,7 @@ class ExplorerService implements IExplorerService { } } catch (e, s) { _core.logger.e( - '[$runtimeType] error getConnectedWallet:', + '[$runtimeType] error get connected wallet:', error: e, stackTrace: s, ); @@ -382,8 +381,11 @@ class ExplorerService implements IExplorerService { } _listings = currentListings; listings.value = _listings; - } catch (e) { - _core.logger.e('[$runtimeType] updating recent wallet: $e'); + } catch (e, s) { + _core.logger.e( + '[$runtimeType] error updating recent wallet: $e', + stackTrace: s, + ); } } @@ -407,7 +409,6 @@ class ExplorerService implements IExplorerService { final excludedIds = (excludedWalletIds ?? {}); final exclude = excludedIds.isNotEmpty ? excludedIds.join(',') : null; - _core.logger.d('[$runtimeType] search $query'); _currentSearchValue = query; final newListins = await _fetchListings( params: RequestParams( @@ -423,7 +424,6 @@ class ExplorerService implements IExplorerService { listings.value = newListins; _debouncer.run(() => isSearching.value = false); - _core.logger.d('[$runtimeType] _searchListings $query'); } @override @@ -442,7 +442,7 @@ class ExplorerService implements IExplorerService { final wallet = ReownAppKitModalWalletInfo.fromJson(results.first.toJson()); final mobileLink = CoinbaseService.defaultWalletData.listing.mobileLink; - bool installed = await uriService.instance.isInstalled(mobileLink); + bool installed = await _uriService.isInstalled(mobileLink); return wallet.copyWith( listing: wallet.listing.copyWith(mobileLink: mobileLink), installed: installed, @@ -548,7 +548,7 @@ extension on List { Future> getInstalledApps() async { final installedApps = []; for (var appData in this) { - bool installed = await uriService.instance.isInstalled( + bool installed = await GetIt.I().isInstalled( appData.schema, id: appData.id, ); 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 deleted file mode 100644 index cbb5c09..0000000 --- a/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service_singleton.dart +++ /dev/null @@ -1,7 +0,0 @@ -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/models/request_params.dart b/packages/reown_appkit/lib/modal/services/explorer_service/models/request_params.dart index a495855..cd48d98 100644 --- 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 @@ -5,6 +5,7 @@ class RequestParams { final String? include; // eg. id1,id2,id3 final String? exclude; // eg. id1,id2,id3 final String? platform; // 'ios' | 'android' + final String? chains; // eip155,solana const RequestParams({ required this.page, @@ -13,6 +14,7 @@ class RequestParams { this.include, this.exclude, this.platform, + this.chains, }); Map toJson({bool short = false}) { @@ -32,6 +34,9 @@ class RequestParams { if ((platform ?? '').isNotEmpty) { params['platform'] = platform; } + if ((chains ?? '').isNotEmpty) { + params['chains'] = chains; + } return params; } @@ -43,6 +48,7 @@ class RequestParams { String? include, String? exclude, String? platform, + String? chains, }) => RequestParams( page: page ?? this.page, @@ -51,6 +57,7 @@ class RequestParams { include: include ?? this.include, exclude: exclude ?? this.exclude, platform: platform ?? this.platform, + chains: chains ?? this.chains, ); RequestParams nextPage() => RequestParams( @@ -60,5 +67,6 @@ class RequestParams { include: include, exclude: exclude, platform: platform, + chains: chains, ); } 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 index d8a8f7a..ccfa0b3 100644 --- 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 @@ -25,7 +25,7 @@ class WCSampleWallets { 'name': 'RN Wallet (internal)', 'platform': ['ios', 'android'], 'id': '1234567890123456789012345678922', - 'schema': 'rn-web3wallet://wc', + 'schema': 'rn-web3wallet-internal://', 'bundleId': 'com.walletconnect.web3wallet.rnsample.internal', 'universal': 'https://appkit-lab.reown.com/rn_walletkit_internal', }, @@ -34,9 +34,8 @@ class WCSampleWallets { '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', + 'bundleId': 'com.reown.sample.wallet.internal', + 'universal': 'https://appkit-lab.reown.com/wallet_internal', }, ]; @@ -61,7 +60,7 @@ class WCSampleWallets { 'name': 'RN Wallet', 'platform': ['ios', 'android'], 'id': '123456789012345678901234567892', - 'schema': 'rn-web3wallet://wc', + 'schema': 'rn-web3wallet://', 'bundleId': 'com.walletconnect.web3wallet.rnsample', 'universal': 'https://appkit-lab.reown.com/rn_walletkit', }, @@ -70,9 +69,8 @@ class WCSampleWallets { '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', + 'bundleId': 'com.reown.sample.wallet', + 'universal': 'https://appkit-lab.reown.com/wallet_release', }, ]; 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 index 9fc5354..48d9a3f 100644 --- 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 @@ -1,24 +1,47 @@ +import 'package:flutter/foundation.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; import 'package:reown_appkit/reown_appkit.dart'; +import 'package:webview_flutter/webview_flutter.dart'; abstract class IMagicService { - ConnectionMetadata get metadata; + Map> get supportedMethods; + List get socials; - Future init(); + WebViewWidget get webview; + ValueNotifier get isReady; + ValueNotifier get isConnected; + ValueNotifier get isTimeout; + ValueNotifier get isEmailEnabled; + ValueNotifier get isSocialEnabled; + ValueNotifier get email; + ValueNotifier get newEmail; + ValueNotifier get step; + + Future init({String? chainId}); void setEmail(String value); void setNewEmail(String value); + void setProvider(AppKitSocialOption? provider); - // ****** W3mFrameProvider public methods ******* // - Future connectEmail({required String value}); + Future getSocialRedirectUri({ + required AppKitSocialOption provider, + String? schema, + String? chainId, + }); + Future connectSocial({required String uri}); + void completeSocialLogin({required String url}); + Future getFarcasterUri({String? chainId}); + Future awaitFarcasterResponse(); + Future connectEmail({required String value, String? chainId}); Future updateEmail({required String value}); Future updateEmailPrimaryOtp({required String otp}); Future updateEmailSecondaryOtp({required String otp}); Future connectOtp({required String otp}); - Future getChainId(); Future syncTheme(ReownAppKitModalTheme? theme); - Future getUser({String? chainId}); - Future switchNetwork({required String chainId}); + Future getChainId(); + Future getUser({required String? chainId, required bool isUpdate}); + Future switchNetwork({required String chainId}); Future request({ String? chainId, required SessionRequestParams request, @@ -31,4 +54,5 @@ abstract class IMagicService { abstract final Event onMagicUpdate; abstract final Event onMagicError; abstract final Event onMagicRpcRequest; + abstract final Event onCompleteSocialLogin; } 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 index 800f397..7723e36 100644 --- a/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart +++ b/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart @@ -4,74 +4,96 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/analytics_service/i_analytics_service.dart'; +import 'package:reown_appkit/modal/utils/core_utils.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/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 = [ + static const _thirdPartySafeDomains = [ 'auth.magic.link', 'launchdarkly.com', ]; - static const supportedMethods = [ - 'personal_sign', - 'eth_sign', - 'eth_sendTransaction', - 'eth_signTypedData_v4', - 'wallet_switchEthereumChain', - 'wallet_addEthereumChain', - ]; - static const defaultWalletData = ReownAppKitModalWalletInfo( - listing: Listing( - id: '', - name: 'Email Wallet', - homepage: '', - imageId: '', - order: 10000, - ), - installed: false, - recent: false, - ); - - @override - ConnectionMetadata get metadata => ConnectionMetadata( + + ConnectionMetadata get _selfMetadata => ConnectionMetadata( + metadata: _metadata, + publicKey: '', + ); + + // TODO export this + ConnectionMetadata get _peerMetadata => ConnectionMetadata( metadata: PairingMetadata( - name: defaultWalletData.listing.name, + name: _socialProvider != null + ? '$_socialProvider Wallet' + : 'Email Wallet', description: '', - url: defaultWalletData.listing.homepage, + url: '', icons: [''], ), publicKey: '', ); // - // final IReownAppKit _appKit; Timer? _timeOutTimer; String? _connectionChainId; int _onLoadCount = 0; String _packageName = ''; + AppKitSocialOption? _socialProvider; + String? _socialUsername; + ReownAppKitModalTheme? _appTheme; + late final IReownCore _core; + late final PairingMetadata _metadata; + late final FeaturesConfig _features; late final WebViewController _webViewController; - WebViewController get controller => _webViewController; - late final WebViewWidget _webview; + + @override + Map> get supportedMethods => { + NetworkUtils.eip155: [ + 'personal_sign', + 'eth_sendTransaction', + 'eth_accounts', + 'eth_sendRawTransaction', + 'eth_signTypedData_v4', + ], + NetworkUtils.solana: + NetworkUtils.defaultNetworkMethods[NetworkUtils.solana]!, + }; + + @override WebViewWidget get webview => _webview; + @override + final isReady = ValueNotifier(false); + @override + final isConnected = ValueNotifier(false); + @override + final isTimeout = ValueNotifier(false); + @override + final isEmailEnabled = ValueNotifier(false); + @override + final isSocialEnabled = ValueNotifier(false); + @override + final email = ValueNotifier(''); + @override + final newEmail = ValueNotifier(''); + @override + final step = ValueNotifier(EmailLoginStep.idle); - late Completer _initialized; - late Completer _connected; - late Completer _response; - late Completer _disconnect; + @override + List get socials => _features.socials; @override Event onMagicLoginRequest = Event(); @@ -91,27 +113,23 @@ class MagicService implements IMagicService { @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); + @override + Event onCompleteSocialLogin = + Event(); - late final IReownCore _core; - late final PairingMetadata _metadata; + IAnalyticsService get _analyticsService => GetIt.I(); MagicService({ - // required IReownAppKit appKit, required IReownCore core, required PairingMetadata metadata, - bool enabled = false, + required FeaturesConfig featuresConfig, }) : _core = core, - _metadata = metadata { - isEnabled.value = enabled; - if (isEnabled.value) { + _metadata = metadata, + _features = featuresConfig { + isEmailEnabled.value = _features.email; + isSocialEnabled.value = _features.socials.isNotEmpty; + // + if (isEmailEnabled.value || isSocialEnabled.value) { _webViewController = WebViewController(); _webview = WebViewWidget(controller: _webViewController); isReady.addListener(_readyListener); @@ -125,46 +143,50 @@ class MagicService implements IMagicService { } } + Completer _initializedCompleter = Completer(); @override - Future init() async { - if (!isEnabled.value) { - _initialized = Completer(); - _initialized.complete(false); - _connected = Completer(); - _connected.complete(false); + Future init({String? chainId}) async { + _connectionChainId = chainId; + _initializedCompleter = Completer(); + _isConnectedCompleter = Completer(); + if (!isEmailEnabled.value && !isSocialEnabled.value) { + _initializedCompleter.complete(false); + _isConnectedCompleter.complete(false); return; } _packageName = await ReownCoreUtils.getPackageName(); await _init(); - await _initialized.future; await _isConnected(); - await _connected.future; + await _syncAppData(); isReady.value = true; - _syncDappData(); - return; + await syncTheme(_appTheme); } - Future _init() async { - _initialized = Completer(); - + Future _init() async { await _webViewController.setBackgroundColor(Colors.transparent); await _webViewController.setJavaScriptMode(JavaScriptMode.unrestricted); + await _webViewController.enableZoom(false); 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, - ); - } + onNavigationRequest: (NavigationRequest request) async { + try { + final uri = Uri.parse(request.url); + if (uri.host.isEmpty || uri.authority.isEmpty) { + return NavigationDecision.prevent; + } + if (_isAllowedDomain(uri.toString())) { + await _fitToScreen(); + return NavigationDecision.navigate; + } + if (isReady.value) { + ReownCoreUtils.openURL(uri.toString()); + return NavigationDecision.prevent; + } + } catch (_) {} return NavigationDecision.prevent; }, onWebResourceError: _onWebResourceError, @@ -176,15 +198,37 @@ class MagicService implements IMagicService { // 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(); + await _fitToScreen(); Future.delayed(Duration(milliseconds: 600)).then((value) { - if (_initialized.isCompleted) return; - _initialized.complete(true); + if (_initializedCompleter.isCompleted) return; + _initializedCompleter.complete(true); }); }, ), ); await _setDebugMode(); await _loadRequest(); + return await _initializedCompleter.future; + } + + Completer _isConnectedCompleter = Completer(); + Future _isConnected() async { + _isConnectedCompleter = Completer(); + final message = IsConnected().toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return await _isConnectedCompleter.future; + } + + Completer _syncAppDataCompleter = Completer(); + Future _syncAppData() async { + _syncAppDataCompleter = Completer(); + final message = SyncAppData( + metadata: _metadata, + projectId: _core.projectId, + sdkVersion: CoreUtils.getUserAgent(), + ).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return await _syncAppDataCompleter.future; } @override @@ -197,11 +241,100 @@ class MagicService implements IMagicService { newEmail.value = value; } + @override + void setProvider(AppKitSocialOption? provider) { + _socialProvider = provider; + } + + bool get _socialsNotReady => (!isSocialEnabled.value || !isReady.value); + bool get _emailNotReady => (!isEmailEnabled.value || !isReady.value); + bool get _serviceNotReady => + (!isEmailEnabled.value && !isSocialEnabled.value) || !isReady.value; + // ****** W3mFrameProvider public methods ******* // + // SOCIAL LOGIN RELATED METHODS + + Completer _getSocialRedirectUri = Completer(); + @override + Future getSocialRedirectUri({ + required AppKitSocialOption provider, + String? schema, + String? chainId, + }) async { + if (_socialsNotReady) { + throw Exception('Service is not ready'); + } + // + _getSocialRedirectUri = Completer(); + _connectionChainId = chainId ?? _connectionChainId; + _socialProvider = provider; + final message = GetSocialRedirectUri( + provider: _socialProvider!.name.toLowerCase(), + schema: schema, + ).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return await _getSocialRedirectUri.future; + } + + Completer _connectSocial = Completer(); + @override + Future connectSocial({required String uri}) async { + if (_socialsNotReady) { + throw Exception('Service is not ready'); + } + // + _connectSocial = Completer(); + final message = ConnectSocial(uri: uri).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return await _connectSocial.future; + } + + @override + void completeSocialLogin({required String url}) { + onCompleteSocialLogin.broadcast(CompleteSocialLoginEvent(url)); + } + + Completer _getFarcasterUri = Completer(); + @override + Future getFarcasterUri({String? chainId}) async { + if (_socialsNotReady) { + throw Exception('Service is not ready'); + } + if (_getFarcasterUri.isCompleted) { + return await _getFarcasterUri.future; + } + // + _getFarcasterUri = Completer(); + _connectionChainId = chainId ?? _connectionChainId; + _socialProvider = AppKitSocialOption.Farcaster; + final message = GetFarcasterUri().toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return await _getFarcasterUri.future; + } + + Completer _connectFarcaster = Completer(); + @override + Future awaitFarcasterResponse() async { + if (_socialsNotReady) { + throw Exception('Service is not ready'); + } + // + _connectFarcaster = Completer(); + // final message = ConnectFarcaster().toString(); + // await _webViewController.runJavaScript('sendMessage($message)'); + return await _connectFarcaster.future; + } + + // EMAIL RELATED METHODS + @override Future connectEmail({required String value, String? chainId}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) { + throw Exception('Service is not ready'); + } + // + _socialProvider = null; _connectionChainId = chainId ?? _connectionChainId; final message = ConnectEmail(email: value).toString(); await _webViewController.runJavaScript('sendMessage($message)'); @@ -209,7 +342,10 @@ class MagicService implements IMagicService { @override Future updateEmail({required String value}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) { + throw Exception('Service is not ready'); + } + // step.value = EmailLoginStep.loading; final message = UpdateEmail(email: value).toString(); await _webViewController.runJavaScript('sendMessage($message)'); @@ -217,7 +353,10 @@ class MagicService implements IMagicService { @override Future updateEmailPrimaryOtp({required String otp}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) { + throw Exception('Service is not ready'); + } + // step.value = EmailLoginStep.loading; final message = UpdateEmailPrimaryOtp(otp: otp).toString(); await _webViewController.runJavaScript('sendMessage($message)'); @@ -225,7 +364,10 @@ class MagicService implements IMagicService { @override Future updateEmailSecondaryOtp({required String otp}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) { + throw Exception('Service is not ready'); + } + // step.value = EmailLoginStep.loading; final message = UpdateEmailSecondaryOtp(otp: otp).toString(); await _webViewController.runJavaScript('sendMessage($message)'); @@ -233,93 +375,158 @@ class MagicService implements IMagicService { @override Future connectOtp({required String otp}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) { + throw Exception('Service is not ready'); + } + // 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(ReownAppKitModalTheme? theme) async { - if (!isEnabled.value || !isReady.value) return; - final message = SyncTheme(theme: theme).toString(); + _appTheme = theme; + if (_serviceNotReady || _appTheme == null) return; + // + final message = SyncTheme(theme: _appTheme).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(); + Completer _getChainIdCompleter = Completer(); + @override + Future getChainId() async { + if (_serviceNotReady) { + throw Exception('Service is not ready'); + } + // + _getChainIdCompleter = Completer(); + final message = GetChainId().toString(); await _webViewController.runJavaScript('sendMessage($message)'); + return await _getChainIdCompleter.future; } + Completer _getUserCompleter = Completer(); + bool _isUpdateUser = false; @override - Future getUser({String? chainId}) async { - if (!isEnabled.value || !isReady.value) return; - return await _getUser(chainId); + Future getUser({ + required String? chainId, + required bool isUpdate, + }) async { + if (_serviceNotReady) { + throw Exception('Service is not ready'); + } + // + await _getUser(chainId, isUpdate); + return await _getUserCompleter.future; } - Future _getUser(String? chainId) async { - final message = GetUser(chainId: chainId).toString(); + Future _getUser(String? chainId, bool isUpdate) async { + _isUpdateUser = isUpdate; + _getUserCompleter = Completer(); + String? cid = chainId ?? _connectionChainId; + if (cid != null && !NamespaceUtils.isValidChainId(cid)) { + try { + cid = ReownAppKitModalNetworks.getCaip2Chain(cid); + } catch (_) { + cid = null; + } + } + final message = GetUser(chainId: cid).toString(); return await _webViewController.runJavaScript('sendMessage($message)'); } + Completer _switchNetworkCompleter = Completer(); @override - Future switchNetwork({required String chainId}) async { - if (!isEnabled.value || !isReady.value) return; + Future switchNetwork({required String chainId}) async { + if (_serviceNotReady) { + throw Exception('Service is not ready'); + } + // + await _awaitReadyness.future; + _switchNetworkCompleter = Completer(); + await _switchNetwork(chainId); + return await _switchNetworkCompleter.future; + } + + Future _switchNetwork(String chainId) async { + if (!isConnected.value) { + _isConnectedCompleter = Completer(); + if (_socialProvider != null) { + onMagicLoginRequest.broadcast(MagicSessionEvent( + provider: _socialProvider, + )); + } else { + onMagicLoginRequest.broadcast(MagicSessionEvent( + email: email.value, + )); + await connectEmail(value: email.value); + } + final success = await _isConnectedCompleter.future; + if (!success) return; + } final message = SwitchNetwork(chainId: chainId).toString(); await _webViewController.runJavaScript('sendMessage($message)'); } + Completer _requestCompleter = Completer(); @override Future request({ String? chainId, required SessionRequestParams request, }) async { - if (!isEnabled.value) return; + if (_serviceNotReady) { + throw Exception('Service is not ready'); + } + // await _awaitReadyness.future; + _requestCompleter = Completer(); + // if (chainId != _connectionChainId && chainId != null) { + // await switchNetwork(chainId: chainId); + // } await _rpcRequest(request.toJson()); - return await _response.future; + return await _requestCompleter.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; + _isConnectedCompleter = Completer(); + if (_socialProvider != null) { + onMagicLoginRequest.broadcast(MagicSessionEvent( + provider: _socialProvider, + )); + } else { + onMagicLoginRequest.broadcast(MagicSessionEvent( + email: email.value, + )); + await connectEmail(value: email.value); + } + final success = await _isConnectedCompleter.future; if (!success) return; } onMagicRpcRequest.broadcast(MagicRequestEvent(request: parameters)); final method = parameters['method']; - final params = parameters['params'] as List; + final params = parameters['params']; final message = RpcRequest(method: method, params: params).toString(); await _webViewController.runJavaScript('sendMessage($message)'); } + Completer _disconnectCompleter = Completer(); @override Future disconnect() async { - if (!isEnabled.value || !isReady.value) return false; - _disconnect = Completer(); + if (_serviceNotReady) { + throw Exception('Service is not ready'); + } + // + _disconnectCompleter = Completer(); if (!isConnected.value) { _resetTimeOut(); - _disconnect.complete(true); - return (await _disconnect.future); + _disconnectCompleter.complete(true); + return await _disconnectCompleter.future; } final message = SignOut().toString(); await _webViewController.runJavaScript('sendMessage($message)'); - return (await _disconnect.future); + return await _disconnectCompleter.future; } // ****** Private Methods ******* // @@ -332,198 +539,308 @@ class MagicService implements IMagicService { 'x-bundle-id': _packageName, }; final uri = Uri.parse(UrlConstants.secureService); + final cid = (_connectionChainId ?? ''); final queryParams = { 'projectId': _core.projectId, - 'bundleId': _packageName, + if (cid.isNotEmpty) 'chainId': cid, }; - await _webViewController.loadRequest( - uri.replace(queryParameters: queryParams), - headers: headers, - ); + final requestUri = uri.replace(queryParameters: queryParams); + await _webViewController.loadRequest(requestUri, 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); + _initializedCompleter.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}'); + _core.logger.d('[$runtimeType] JS Console: ${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; - } + _core.logger.d('[$runtimeType] ${frameMessage.data!.toRawJson()}'); + _successMessageHandler(frameMessage.data!); // await? + _errorMessageHandler(frameMessage.data!); + } catch (e, s) { + _core.logger.e('[$runtimeType] $jsMessage $e', stackTrace: s); + } + } + + Future _successMessageHandler(MessageData messageData) async { + // ******* SYNC_DAPP_DATA_SUCCESS + if (messageData.syncDataSuccess) { + _resetTimeOut(); + _syncAppDataCompleter.complete(true); + } + // ****** IS_CONNECTED_SUCCESS + if (messageData.isConnectSuccess) { + _resetTimeOut(); + isConnected.value = messageData.getPayloadMapKey('isConnected'); + if (!_isConnectedCompleter.isCompleted) { + _isConnectedCompleter.complete(isConnected.value); } - // ****** CONNECT_OTP - if (messageData.connectOtpSuccess) { - analyticsService.instance.sendEvent(EmailVerificationCodePass()); - step.value = EmailLoginStep.idle; - await _getUser(_connectionChainId); + onMagicConnect.broadcast(MagicConnectEvent(isConnected.value)); + if (isConnected.value) { + await _getUser(_connectionChainId, false); } - // ****** UPDAET_EMAIL - if (messageData.updateEmailSuccess) { + } + if (messageData.getSocialRedirectUriSuccess) { + final uri = messageData.getPayloadMapKey('uri'); + _getSocialRedirectUri.complete(uri); + } + // ****** CONNECT_SOCIAL_SUCCESS + if (messageData.connectSocialSuccess) { + final username = messageData.getPayloadMapKey('userName'); + final email = messageData.getPayloadMapKey('email'); + _socialUsername = username ?? email; + _connectSocial.complete(true); + } + // ****** GET_FARCASTER_URI_SUCCESS + if (messageData.getFarcasterUriSuccess) { + final url = messageData.getPayloadMapKey('url'); + _getFarcasterUri.complete(url); + } + // ****** CONNECT_FARCASTER_SUCCESS + if (messageData.connectFarcasterSuccess) { + final username = messageData.getPayloadMapKey('userName'); + final email = messageData.getPayloadMapKey('email'); + _socialUsername = username ?? email; + _connectFarcaster.complete(true); + } + // ****** CONNECT_EMAIL + if (messageData.connectEmailSuccess) { + if (step.value != EmailLoginStep.loading) { final action = messageData.getPayloadMapKey('action'); - if (action == 'VERIFY_SECONDARY_OTP') { - step.value = EmailLoginStep.verifyOtp2; - } else { - step.value = EmailLoginStep.verifyOtp; + final value = action.toString().toUpperCase(); + final newStep = EmailLoginStep.fromAction(value); + if (newStep == EmailLoginStep.verifyOtp) { + if (step.value == EmailLoginStep.verifyDevice) { + _analyticsService.sendEvent(DeviceRegisteredForEmail()); + } + _analyticsService.sendEvent(EmailVerificationCodeSent()); } - analyticsService.instance.sendEvent(EmailEdit()); + step.value = newStep; } - // ****** UPDATE_EMAIL_PRIMARY_OTP - if (messageData.updateEmailPrimarySuccess) { + } + // ****** CONNECT_OTP + if (messageData.connectOtpSuccess) { + _analyticsService.sendEvent(EmailVerificationCodePass()); + step.value = EmailLoginStep.idle; + await _getUser(_connectionChainId, false); + } + // ****** UPDAET_EMAIL + if (messageData.updateEmailSuccess) { + final action = messageData.getPayloadMapKey('action'); + if (action == 'VERIFY_SECONDARY_OTP') { step.value = EmailLoginStep.verifyOtp2; + } else { + step.value = EmailLoginStep.verifyOtp; } - // ****** 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)); + _analyticsService.sendEvent(EmailEdit()); + } + // ****** UPDATE_EMAIL_PRIMARY_OTP + if (messageData.updateEmailPrimarySuccess) { + step.value = EmailLoginStep.verifyOtp2; + } + // ****** UPDATE_EMAIL_SECONDARY_OTP + if (messageData.updateEmailSecondarySuccess) { + _analyticsService.sendEvent(EmailEditComplete()); + step.value = EmailLoginStep.idle; + setEmail(newEmail.value); + setNewEmail(''); + await _getUser(_connectionChainId, true); + } + // ****** SWITCH_NETWORK_SUCCESS + if (messageData.switchNetworkSuccess) { + final chainId = messageData.getPayloadMapKey('chainId'); + if (chainId is String && chainId.contains(':')) { + final cid = chainId.split(':').last; + onMagicUpdate.broadcast(MagicSessionEvent(chainId: cid.toString())); + _connectionChainId = cid.toString(); + } else { + onMagicUpdate.broadcast(MagicSessionEvent( + chainId: chainId?.toString(), + )); + _connectionChainId = chainId?.toString(); } - // ****** GET_CHAIN_ID - if (messageData.getChainIdSuccess) { - final chainId = messageData.getPayloadMapKey('chainId'); - onMagicUpdate.broadcast(MagicSessionEvent(chainId: chainId)); + _switchNetworkCompleter.complete(true); + } + // ****** GET_CHAIN_ID + if (messageData.getChainIdSuccess) { + final chainId = messageData.getPayloadMapKey('chainId'); + if (chainId is String && chainId.contains(':')) { + final cid = chainId.split(':').last; + _connectionChainId = cid.toString(); + } else { _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, - ), + _getChainIdCompleter.complete(_connectionChainId); + } + // ****** RPC_REQUEST_SUCCESS + if (messageData.rpcRequestSuccess) { + final hash = messageData.payload; + _requestCompleter.complete(hash); + onMagicRpcRequest.broadcast( + MagicRequestEvent( + request: null, + result: hash, + success: true, + ), + ); + } + // ****** GET_USER_SUCCESS + if (messageData.getUserSuccess) { + isConnected.value = true; + final magicData = MagicData.fromJson(messageData.payload!).copytWith( + userName: _socialUsername, + ); + if (!_isConnectedCompleter.isCompleted) { + final event = MagicSessionEvent( + email: magicData.email, + userName: magicData.userName, + address: magicData.address, + chainId: magicData.chainId, + provider: magicData.provider, ); - } - // ****** 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: '', + onMagicUpdate.broadcast(event); + _isConnectedCompleter.complete(isConnected.value); + } else if (_isUpdateUser) { + final event = MagicSessionEvent( + email: magicData.email, + userName: magicData.userName, + address: magicData.address, + chainId: magicData.chainId, + provider: magicData.provider, + ); + onMagicUpdate.broadcast(event); + } else { + final session = magicData.copytWith( + peer: _peerMetadata.copyWith( + metadata: _peerMetadata.metadata.copyWith( + name: _socialProvider?.name ?? 'Email Wallet', ), - ); - 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)); + ), + self: _selfMetadata, + provider: _socialProvider, + ); + onMagicLoginSuccess.broadcast(MagicLoginEvent(session)); } - if (messageData.signOutError) { - _error(SignOutErrorEvent()); + // TODO this try/catch is a temporary workaround + try { + _getUserCompleter.complete(true); + } catch (_) {} + } + // ****** SIGN_OUT_SUCCESS + if (messageData.signOutSuccess) { + _socialUsername = null; + _resetTimeOut(); + _disconnectCompleter.complete(true); + } + } + + void _errorMessageHandler(MessageData messageData) { + if (messageData.syncDataError) { + _resetTimeOut(); + _syncAppDataCompleter.complete(false); + } + if (messageData.isConnectError) { + _error(IsConnectedErrorEvent()); + } + if (messageData.connectEmailError) { + String? message = messageData.getPayloadMapKey('message'); + if (message?.toLowerCase() == 'invalid params') { + message = 'Wrong email format'; } - } catch (e, s) { - _core.logger.d('[$runtimeType] $jsMessage', stackTrace: s); + _error(ConnectEmailErrorEvent(message: message)); + } + if (messageData.updateEmailError) { + final message = messageData.getPayloadMapKey('message'); + _error(UpdateEmailErrorEvent(message: message)); + } + if (messageData.updateEmailPrimaryOtpError) { + final message = messageData.getPayloadMapKey('message'); + _error(UpdateEmailPrimaryOtpErrorEvent(message: message)); + } + if (messageData.updateEmailSecondaryOtpError) { + final message = messageData.getPayloadMapKey('message'); + _error(UpdateEmailSecondaryOtpErrorEvent(message: message)); + } + if (messageData.connectOtpError) { + _analyticsService.sendEvent(EmailVerificationCodeFail()); + final message = messageData.getPayloadMapKey('message'); + _error(ConnectOtpErrorEvent(message: message)); + } + if (messageData.getSocialRedirectUriError) { + String? message = messageData.getPayloadMapKey('message'); + message = message?.replaceFirst( + 'Error: Magic RPC Error: [-32600] ', + '', + ); + _error(MagicErrorEvent(message)); + _getSocialRedirectUri.complete(null); + } + if (messageData.connectSocialError) { + String? message = messageData.getPayloadMapKey('message'); + message = message?.replaceFirst( + 'Error: Magic RPC Error: [-32600] ', + '', + ); + _error(MagicErrorEvent(message)); + _connectSocial.complete(false); + } + if (messageData.getFarcasterUriError) { + String? message = messageData.getPayloadMapKey('message'); + message = message?.replaceFirst( + 'Error: Magic RPC Error: [-32600] ', + '', + ); + _error(MagicErrorEvent(message)); + _getFarcasterUri.complete(null); + } + if (messageData.connectFarcasterError) { + String? message = messageData.getPayloadMapKey('message'); + message = message?.replaceFirst( + 'Error: Magic RPC Error: [-32600] ', + '', + ); + _error(MagicErrorEvent(message)); + _connectFarcaster.complete(false); + } + // GET_USER_ERROR + if (messageData.getUserError) { + final message = messageData.getPayloadMapKey('message'); + _error(GetUserErrorEvent(message: message)); + _getUserCompleter.complete(false); + } + if (messageData.switchNetworkError) { + final message = messageData.getPayloadMapKey('message'); + _error(SwitchNetworkErrorEvent(message: message)); + _switchNetworkCompleter.complete(false); + } + if (messageData.getChainIdError) { + final message = messageData.getPayloadMapKey('message'); + _error(SwitchNetworkErrorEvent(message: message)); + _getChainIdCompleter.complete(null); + } + if (messageData.rpcRequestError) { + final message = messageData.getPayloadMapKey('message'); + _error(RpcRequestErrorEvent(message)); + } + if (messageData.signOutError) { + _error(SignOutErrorEvent()); } } void _error(MagicErrorEvent errorEvent) { if (errorEvent is RpcRequestErrorEvent) { - _response.completeError(JsonRpcError(code: 0, message: errorEvent.error)); + _requestCompleter.completeError(JsonRpcError( + code: 0, + message: errorEvent.error, + )); onMagicRpcRequest.broadcast( MagicRequestEvent( request: null, @@ -558,35 +875,43 @@ class MagicService implements IMagicService { } if (errorEvent is SignOutErrorEvent) { isConnected.value = true; - _disconnect.complete(false); + _disconnectCompleter.complete(false); } - if (!_connected.isCompleted) { - _connected.complete(isConnected.value); + if (!_isConnectedCompleter.isCompleted) { + _isConnectedCompleter.complete(isConnected.value); } onMagicError.broadcast(errorEvent); } - Future _runJavascript() async { + Future _fitToScreen() async { return await _webViewController.runJavaScript(''' - const iframeFL = document.getElementById('frame-mobile-sdk') + if (document.querySelector('meta[name="viewport"]') === null) { + var meta = document.createElement('meta'); + meta.name = 'viewport'; + meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; + document.head.appendChild(meta); + } else { + document.querySelector('meta[name="viewport"]').setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); + } + '''); + } + Future _runJavascript() async { + return await _webViewController.runJavaScript(''' 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)) + const iframeFL = document.getElementById('frame-mobile-sdk') + console.log('postMessage(' + 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}'); + _core.logger.d('[$runtimeType] ${message.message}'); } void _onWebResourceError(WebResourceError error) { @@ -607,9 +932,9 @@ class MagicService implements IMagicService { bool _isAllowedDomain(String domain) { final domains = [ - Uri.parse(UrlConstants.secureService).authority, - Uri.parse(UrlConstants.secureDashboard).authority, - ..._safeDomains, + UrlConstants.secureOrigin1, + UrlConstants.secureOrigin2, + ..._thirdPartySafeDomains, ].join('|'); return RegExp(r'' + domains).hasMatch(domain); } @@ -640,9 +965,10 @@ class MagicService implements IMagicService { } if (Platform.isAndroid) { if (_webViewController.platform is AndroidWebViewController) { + final platform = + _webViewController.platform as AndroidWebViewController; AndroidWebViewController.enableDebugging(true); - (_webViewController.platform as AndroidWebViewController) - .setMediaPlaybackRequiresUserGesture(false); + platform.setMediaPlaybackRequiresUserGesture(false); final cookieManager = WebViewCookieManager().platform as AndroidWebViewCookieManager; 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 deleted file mode 100644 index d767c7d..0000000 --- a/packages/reown_appkit/lib/modal/services/magic_service/magic_service_singleton.dart +++ /dev/null @@ -1,7 +0,0 @@ -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/frame_message.dart b/packages/reown_appkit/lib/modal/services/magic_service/models/frame_message.dart index c64ef98..b30be2d 100644 --- 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 @@ -1,7 +1,5 @@ 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'; @@ -34,8 +32,10 @@ class FrameMessage { }; bool get isValidOrigin { - return Uri.parse(origin ?? '').authority == - Uri.parse(UrlConstants.secureDashboard).authority; + final authority1 = Uri.parse(UrlConstants.secureDashboard).authority; + final authority2 = Uri.parse(UrlConstants.secureService).authority; + final originAuthority = Uri.parse(origin ?? '').authority; + return originAuthority == authority1 || originAuthority == authority2; } bool get isValidData { @@ -69,7 +69,7 @@ class MessageData { Map toJson() => { 'type': type, - 'payload': payload?.toJson(), + if (payload != null) 'payload': payload, }; T getPayloadMapKey(String key) { @@ -79,7 +79,22 @@ class MessageData { // @w3m-frame events bool get syncThemeSuccess => type == '@w3m-frame/SYNC_THEME_SUCCESS'; + bool get syncThemeError => type == '@w3m-frame/SYNC_THEME_ERROR'; bool get syncDataSuccess => type == '@w3m-frame/SYNC_DAPP_DATA_SUCCESS'; + bool get syncDataError => type == '@w3m-frame/SYNC_DAPP_DATA_ERROR'; + bool get getSocialRedirectUriSuccess => + type == '@w3m-frame/GET_SOCIAL_REDIRECT_URI_SUCCESS'; + bool get getSocialRedirectUriError => + type == '@w3m-frame/GET_SOCIAL_REDIRECT_URI_ERROR'; + bool get getFarcasterUriSuccess => + type == '@w3m-frame/GET_FARCASTER_URI_SUCCESS'; + bool get getFarcasterUriError => type == '@w3m-frame/GET_FARCASTER_URI_ERROR'; + bool get connectFarcasterSuccess => + type == '@w3m-frame/CONNECT_FARCASTER_SUCCESS'; + bool get connectFarcasterError => + type == '@w3m-frame/CONNECT_FARCASTER_ERROR'; + bool get connectSocialSuccess => type == '@w3m-frame/CONNECT_SOCIAL_SUCCESS'; + bool get connectSocialError => type == '@w3m-frame/CONNECT_SOCIAL_ERROR'; 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'; @@ -124,15 +139,59 @@ class SwitchNetwork extends MessageData { }) : super(type: '@w3m-app/SWITCH_NETWORK'); @override - String toString() => '{type:\'${super.type}\',payload:{chainId:$chainId}}'; + String toString() => '{type:"${super.type}",payload:{chainId:"$chainId"}}'; } +class GetSocialRedirectUri extends MessageData { + final String provider; + final String? schema; + GetSocialRedirectUri({ + required this.provider, + this.schema, + }) : super(type: '@w3m-app/GET_SOCIAL_REDIRECT_URI'); + + @override + String toString() { + final p = 'provider:"$provider"'; + final s = 'schema:"$schema"'; + + if (schema != null) { + return '{type:"${super.type}",payload:{$p,$s}}'; + } + return '{type:"${super.type}",payload:{$p}}'; + } +} + +class ConnectSocial extends MessageData { + final String uri; + ConnectSocial({ + required this.uri, + }) : super(type: '@w3m-app/CONNECT_SOCIAL'); + + @override + String toString() => '{type:"${super.type}",payload:{uri:"$uri"}}'; +} + +class GetFarcasterUri extends MessageData { + GetFarcasterUri() : super(type: '@w3m-app/GET_FARCASTER_URI'); + + @override + String toString() => '{type:"${super.type}"}'; +} + +// class ConnectFarcaster extends MessageData { +// ConnectFarcaster() : super(type: '@w3m-app/CONNECT_FARCASTER'); + +// @override +// String toString() => '{type:"${super.type}"}'; +// } + 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\'}}'; + String toString() => '{type:"${super.type}",payload:{email:"$email"}}'; } class UpdateEmail extends MessageData { @@ -140,7 +199,7 @@ class UpdateEmail extends MessageData { UpdateEmail({required this.email}) : super(type: '@w3m-app/UPDATE_EMAIL'); @override - String toString() => '{type:\'${super.type}\',payload:{email:\'$email\'}}'; + String toString() => '{type:"${super.type}",payload:{email:"$email"}}'; } class UpdateEmailPrimaryOtp extends MessageData { @@ -150,7 +209,7 @@ class UpdateEmailPrimaryOtp extends MessageData { }) : super(type: '@w3m-app/UPDATE_EMAIL_PRIMARY_OTP'); @override - String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; + String toString() => '{type:"${super.type}",payload:{otp:"$otp"}}'; } class UpdateEmailSecondaryOtp extends MessageData { @@ -160,7 +219,7 @@ class UpdateEmailSecondaryOtp extends MessageData { }) : super(type: '@w3m-app/UPDATE_EMAIL_SECONDARY_OTP'); @override - String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; + String toString() => '{type:"${super.type}",payload:{otp:"$otp"}}'; } class ConnectOtp extends MessageData { @@ -168,7 +227,7 @@ class ConnectOtp extends MessageData { ConnectOtp({required this.otp}) : super(type: '@w3m-app/CONNECT_OTP'); @override - String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; + String toString() => '{type:"${super.type}",payload:{otp:"$otp"}}'; } class GetUser extends MessageData { @@ -178,9 +237,9 @@ class GetUser extends MessageData { @override String toString() { if ((chainId ?? '').isNotEmpty) { - return '{type:\'${super.type}\',payload:{chainId:$chainId}}'; + return '{type:"${super.type}",payload:{chainId:"$chainId"}}'; } - return '{type:\'${super.type}\'}'; + return '{type:"${super.type}"}'; } } @@ -200,7 +259,7 @@ class GetChainId extends MessageData { class RpcRequest extends MessageData { final String method; - final List params; + final dynamic params; RpcRequest({ required this.method, @@ -209,23 +268,34 @@ class RpcRequest extends MessageData { @override String toString() { - debugPrint('[$runtimeType] method $method'); - final m = 'method:\'$method\''; - final t = 'type:\'${super.type}\''; + final m = 'method:"$method"'; + final t = 'type:"${super.type}"'; + + if (params is Map) { + List ps = + (params as Map).entries.map((e) => '${e.key}:"${e.value}"').toList(); + final pString = ps.join(','); + return '{$t,payload:{$m,params:{$pString}}}'; + } + 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\']}}'; + return '{$t,payload:{$m,params:["$data","$address"]}}'; + } + if (method == 'eth_sign') { + final address = p.first; + final data = p.last; + return '{$t,payload:{$m,params:["$address","$data"]}}'; } 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\']}}'; + return '{$t,payload:{$m,params:["$address","$data"]}}'; } if (method == 'eth_sendTransaction' || method == 'eth_signTransaction') { final jp = jsonEncode(params.first); @@ -252,20 +322,20 @@ class SyncTheme extends MessageData { colors = themeData.lightColors; } - final tm = 'themeMode:\'$mode\''; + final tm = 'themeMode:"$mode"'; final mix = RenderUtils.colorToRGBA(colors.background125); - final tv1 = '\'--w3m-color-mix\':\'$mix\''; - // final tv2 = '\'--w3m-color-mix-strength\':\'0%\''; + 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 wtv1 = '"--w3m-accent":"$accent"'; final background = RenderUtils.colorToRGBA(colors.background125); - final wtv2 = '\'--w3m-background\':\'$background\''; + final wtv2 = '"--w3m-background":"$background"'; final w3mtv = 'w3mThemeVariables:{$wtv1,$wtv2}'; - return '{type:\'${super.type}\',payload:{$tm, $tv,$w3mtv}}'; + return '{type:"${super.type}",payload:{$tm, $tv,$w3mtv}}'; } } @@ -284,13 +354,17 @@ class SyncAppData extends MessageData { 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 p2 = 'sdkVersion:\'${sdkVersion.replaceAll("'", "\\'")}\''; + final m1 = 'name:\'${metadata.name.replaceAll("'", "\\'")}\''; + final m2 = 'description:\'${metadata.description.replaceAll("'", "\\'")}\''; final m3 = 'url:\'${metadata.url}\''; final m4 = 'icons:["${metadata.icons.first}"]'; - final p3 = 'metadata:{$m1,$m2,$m3,$m4}'; + final r1 = 'native:"${metadata.redirect?.native}"'; + final r2 = 'universal:"${metadata.redirect?.universal}"'; + final r3 = 'linkMode:"${metadata.redirect?.linkMode}"'; + final m5 = 'redirect:{$r1,$r2,$r3}'; + final p3 = 'metadata:{$m1,$m2,$m3,$m4,$m5}'; final p = 'payload:{$v,$p1,$p2,$p3}'; - return '{type:\'${super.type}\',$p}'; + 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 index 3237809..c2d858d 100644 --- 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 @@ -1,41 +1,67 @@ import 'package:reown_appkit/reown_appkit.dart'; class MagicData { - String email; + String? email; String address; - int chainId; + String chainId; + String? userName; + bool? smartAccountDeployed; + String? preferredAccountType; ConnectionMetadata? self; ConnectionMetadata? peer; + AppKitSocialOption? provider; MagicData({ required this.email, required this.chainId, required this.address, + this.userName, + this.smartAccountDeployed, + this.preferredAccountType, this.self, this.peer, + this.provider, }); factory MagicData.fromJson(Map json) { return MagicData( - email: json['email'].toString(), + email: json['email']?.toString(), address: json['address'].toString(), - chainId: int.parse(json['chainId'].toString()), + chainId: _parseChainId(json['chainId'].toString()), + userName: json['userName']?.toString(), + smartAccountDeployed: json['smartAccountDeployed'] as bool?, + preferredAccountType: json['preferredAccountType']?.toString(), self: (json['self'] != null) ? ConnectionMetadata.fromJson(json['self']) : null, peer: (json['peer'] != null) ? ConnectionMetadata.fromJson(json['peer']) : null, + provider: (json['provider'] != null) + ? AppKitSocialOption.fromString(json['provider'].toString()) + : null, ); } + static String _parseChainId(String value) { + String chainId = value.toString(); + if (chainId.contains(':')) { + return chainId.split(':').last; + } + return chainId; + } + Map toJson() { return { 'email': email, 'address': address, 'chainId': chainId, + 'userName': userName, + 'smartAccountDeployed': smartAccountDeployed, + 'preferredAccountType': preferredAccountType, 'self': self?.toJson(), 'peer': peer?.toJson(), + 'provider': provider?.name, }; } @@ -45,16 +71,27 @@ class MagicData { MagicData copytWith({ String? email, String? address, - int? chainId, + String? chainId, + String? userName, + bool? smartAccountDeployed, + String? preferredAccountType, ConnectionMetadata? self, ConnectionMetadata? peer, + AppKitSocialOption? provider, }) { + if (chainId != null) { + chainId = _parseChainId(chainId); + } return MagicData( email: email ?? this.email, address: address ?? this.address, chainId: chainId ?? this.chainId, + userName: userName ?? this.userName, + smartAccountDeployed: smartAccountDeployed ?? this.smartAccountDeployed, + preferredAccountType: preferredAccountType ?? this.preferredAccountType, self: self ?? this.self, peer: peer ?? this.peer, + provider: provider ?? this.provider, ); } } 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 index 2e812f1..cc6f8d4 100644 --- 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 @@ -11,11 +11,15 @@ class MagicLoginEvent implements EventArgs { class MagicSessionEvent implements EventArgs { String? email; + String? userName; + AppKitSocialOption? provider; String? address; - int? chainId; + String? chainId; MagicSessionEvent({ this.email, + this.userName, + this.provider, this.address, this.chainId, }); @@ -25,6 +29,12 @@ class MagicSessionEvent implements EventArgs { if ((email ?? '').isNotEmpty) { params['email'] = email; } + if ((userName ?? '').isNotEmpty) { + params['userName'] = userName; + } + if (provider != null) { + params['provider'] = provider; + } if ((address ?? '').isNotEmpty) { params['address'] = address; } @@ -107,11 +117,16 @@ class ConnectOtpErrorEvent extends MagicErrorEvent { } class GetUserErrorEvent extends MagicErrorEvent { - GetUserErrorEvent() : super('Error getting user'); + final String? message; + GetUserErrorEvent({this.message}) : super('Error getting user'); } class SwitchNetworkErrorEvent extends MagicErrorEvent { - SwitchNetworkErrorEvent() : super('Error switching network'); + final String? message; + SwitchNetworkErrorEvent({this.message}) + : super( + message ?? 'Error switching network', + ); } class SignOutErrorEvent extends MagicErrorEvent { @@ -122,3 +137,8 @@ class RpcRequestErrorEvent extends MagicErrorEvent { RpcRequestErrorEvent(String? message) : super(message ?? 'Error during request'); } + +class CompleteSocialLoginEvent implements EventArgs { + final String url; + CompleteSocialLoginEvent(this.url); +} 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 index 3a51c51..d2bf315 100644 --- a/packages/reown_appkit/lib/modal/services/network_service/network_service.dart +++ b/packages/reown_appkit/lib/modal/services/network_service/network_service.dart @@ -1,13 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:get_it/get_it.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/explorer_service/i_explorer_service.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 { + IExplorerService get _explorerService => GetIt.I(); + @override ValueNotifier initialized = ValueNotifier(false); @@ -18,12 +20,16 @@ class NetworkService implements INetworkService { ValueNotifier>>([]); String _getImageUrl(ReownAppKitModalNetworkInfo chainInfo) { + if (chainInfo.isTestNetwork) { + return ''; + } if (chainInfo.chainIcon != null && chainInfo.chainIcon!.contains('http')) { return chainInfo.chainIcon!; } - final imageId = - ReownAppKitModalNetworks.getNetworkIconId(chainInfo.chainId); - return explorerService.instance.getAssetImageUrl(imageId); + final imageId = ReownAppKitModalNetworks.getNetworkIconId( + chainInfo.chainId, + ); + return _explorerService.getAssetImageUrl(imageId); } @override @@ -32,17 +38,15 @@ class NetworkService implements INetworkService { return; } - final networks = ReownAppKitModalNetworks.getNetworks( - CoreConstants.namespace, - ); - for (var chain in networks) { - final imageUrl = _getImageUrl(chain); + final networks = ReownAppKitModalNetworks.getAllSupportedNetworks(); + for (var network in networks) { + final imageUrl = _getImageUrl(network); itemListComplete.add( GridItem( image: imageUrl, - id: chain.chainId, - title: RenderUtils.shorten(chain.name), - data: chain, + id: network.chainId, + title: RenderUtils.shorten(network.name), + data: network, ), ); } 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 deleted file mode 100644 index 7bf9bf8..0000000 --- a/packages/reown_appkit/lib/modal/services/network_service/network_service_singleton.dart +++ /dev/null @@ -1,10 +0,0 @@ -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/siwe_service.dart b/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart index 0241ff2..a003a2b 100644 --- a/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart +++ b/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart @@ -1,27 +1,40 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; 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:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/coinbase_service/i_coinbase_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/services/siwe_service/i_siwe_service.dart'; import 'package:reown_appkit/reown_appkit.dart'; class SiweService implements ISiweService { + IMagicService get _magicService => GetIt.I(); + ICoinbaseService get _coinbaseService => GetIt.I(); + late final SIWEConfig? _siweConfig; - final IReownAppKit _appKit; + late final IReownAppKit _appKit; + late final Map _namespaces; SiweService({ required IReownAppKit appKit, required SIWEConfig? siweConfig, + required Map namespaces, }) : _appKit = appKit, - _siweConfig = siweConfig; + _siweConfig = siweConfig, + _namespaces = namespaces; @override SIWEConfig? get config => _siweConfig; @override - bool get enabled => _siweConfig?.enabled == true; + bool get enabled { + // TODO check this logic + final nonEVM = _namespaces.keys.firstWhereOrNull( + (k) => k != NetworkUtils.eip155, + ); + return _siweConfig?.enabled == true && nonEVM == null; + } @override int get nonceRefetchIntervalMs => @@ -46,6 +59,7 @@ class SiweService implements ISiweService { Future getNonce() async { if (!enabled) throw Exception('siweConfig not enabled'); // + _appKit.core.logger.d('[$runtimeType] getNonce() called'); return await _siweConfig!.getNonce(); } @@ -67,6 +81,7 @@ class SiweService implements ISiweService { type: messageParams.type ?? CacaoHeader(t: 'eip4361'), ); + _appKit.core.logger.d('[$runtimeType] createMessage() called'); return _siweConfig!.createMessage(createMessageArgs); } @@ -78,17 +93,14 @@ class SiweService implements ISiweService { if (!enabled) throw Exception('siweConfig not enabled'); // final chainId = AuthSignature.getChainIdFromMessage(message); - final chainInfo = ReownAppKitModalNetworks.getNetworkById( - CoreConstants.namespace, - chainId, - )!; - final caip2Chain = '${CoreConstants.namespace}:${chainInfo.chainId}'; + final caip2Chain = ReownAppKitModalNetworks.getCaip2Chain(chainId); final address = AuthSignature.getAddressFromMessage(message); final bytes = utf8.encode(message); final encoded = hex.encode(bytes); // + _appKit.core.logger.d('[$runtimeType] signMessageRequest() called'); if (session.sessionService.isMagic) { - return await magicService.instance.request( + return await _magicService.request( chainId: caip2Chain, request: SessionRequestParams( method: 'personal_sign', @@ -97,7 +109,7 @@ class SiweService implements ISiweService { ); } if (session.sessionService.isCoinbase) { - return await coinbaseService.instance.request( + return await _coinbaseService.request( chainId: caip2Chain, request: SessionRequestParams( method: 'personal_sign', @@ -130,6 +142,7 @@ class SiweService implements ISiweService { cacao: cacao, clientId: clientId, ); + _appKit.core.logger.d('[$runtimeType] verifyMessage() called'); final isValid = await _siweConfig!.verifyMessage(verifyArgs); if (!isValid) { throw ReownAppKitModalException('Error verifying SIWE signature'); @@ -142,6 +155,7 @@ class SiweService implements ISiweService { if (!enabled) throw Exception('siweConfig not enabled'); // try { + _appKit.core.logger.d('[$runtimeType] getSession() called'); final siweSession = await _siweConfig!.getSession(); if (siweSession == null) { throw ReownAppKitModalException('Error getting SIWE session'); @@ -158,6 +172,7 @@ class SiweService implements ISiweService { Future signOut() async { if (!enabled) throw Exception('siweConfig not enabled'); + _appKit.core.logger.d('[$runtimeType] signOut() called'); final success = await _siweConfig!.signOut(); if (!success) { throw ReownAppKitModalException('signOut() from siweConfig failed'); @@ -173,6 +188,7 @@ class SiweService implements ISiweService { 'aud': params.uri, 'type': params.type?.t, }); + _appKit.core.logger.d('[$runtimeType] formatMessage() called'); 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 deleted file mode 100644 index 5a109ad..0000000 --- a/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service_singleton.dart +++ /dev/null @@ -1,7 +0,0 @@ -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/toast_service_singleton.dart b/packages/reown_appkit/lib/modal/services/toast_service/toast_service_singleton.dart deleted file mode 100644 index 64a6838..0000000 --- a/packages/reown_appkit/lib/modal/services/toast_service/toast_service_singleton.dart +++ /dev/null @@ -1,10 +0,0 @@ -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 index dc6486b..2a5c745 100644 --- 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 @@ -1,5 +1,4 @@ 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 { @@ -7,7 +6,7 @@ abstract class IUriService { Future isInstalled(String? uri, {String? id}); - Future launchUrl(Uri url, {LaunchMode? mode}); + Future launchUrl(Uri url, {dynamic mode}); Future openRedirect( WalletRedirect redirect, { 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 index 6f64b0d..8c2845a 100644 --- a/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart +++ b/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart @@ -4,7 +4,6 @@ import 'package:reown_appkit/modal/services/uri_service/launch_url_exception.dar 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 { @@ -28,13 +27,13 @@ class UriService extends IUriService { if (p == PlatformExact.android) { return await _androidAppCheck(uri); } else if (p == PlatformExact.ios) { - return await launcher.canLaunchUrl(Uri.parse(uri)); + return await ReownCoreUtils.canOpenUrl(Uri.parse(uri).toString()); } } on FormatException catch (e) { if (id != null) { - _core.logger.d('[$runtimeType] $uri ($id): ${e.message}'); + _core.logger.i('[$runtimeType] $uri ($id): ${e.message}'); } else { - _core.logger.d('[$runtimeType] $uri: ${e.message}'); + _core.logger.i('[$runtimeType] $uri: ${e.message}'); } } catch (e) { rethrow; @@ -45,11 +44,8 @@ class UriService extends IUriService { } @override - Future launchUrl(Uri url, {launcher.LaunchMode? mode}) async { - return await _launchUrlFunc( - url, - mode: mode, - ); + Future launchUrl(Uri url, {dynamic mode}) async { + return await _launchUrlFunc(url); } @override @@ -89,18 +85,12 @@ class UriService extends IUriService { return false; } _core.logger.i('[$runtimeType] openRedirect $uriToOpen'); - return await _launchUrlFunc( - uriToOpen!, - mode: launcher.LaunchMode.externalApplication, - ); + return await _launchUrlFunc(uriToOpen!); } - Future _launchUrlFunc(Uri url, {launcher.LaunchMode? mode}) async { + Future _launchUrlFunc(Uri url) async { try { - final success = await launcher.launchUrl( - url, - mode: mode ?? launcher.LaunchMode.platformDefault, - ); + final success = await ReownCoreUtils.openURL(url.toString()); if (!success) { throw CanNotLaunchUrl(); } 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 deleted file mode 100644 index af7eaa8..0000000 --- a/packages/reown_appkit/lib/modal/services/uri_service/url_utils_singleton.dart +++ /dev/null @@ -1,7 +0,0 @@ -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/utils/core_utils.dart b/packages/reown_appkit/lib/modal/utils/core_utils.dart index d32abfe..a7c1453 100644 --- a/packages/reown_appkit/lib/modal/utils/core_utils.dart +++ b/packages/reown_appkit/lib/modal/utils/core_utils.dart @@ -7,8 +7,8 @@ class CoreUtils { } static bool isValidEmail(String email) { - return RegExp( - r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") + if (email.contains(' ')) return false; + return RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$') .hasMatch(email); } @@ -94,11 +94,11 @@ class CoreUtils { } static String getUserAgent() { - String userAgent = '${CoreConstants.X_SDK_TYPE}' - '-flutter-' + String userAgent = '${CoreConstants.X_SDK_TYPE}/' '${CoreConstants.X_SDK_VERSION}/' '${CoreConstants.X_CORE_SDK_VERSION}/' '${ReownCoreUtils.getOS()}'; + // return userAgent; } @@ -110,7 +110,7 @@ class CoreUtils { return { 'x-project-id': projectId, 'x-sdk-type': CoreConstants.X_SDK_TYPE, - 'x-sdk-version': 'flutter-${CoreConstants.X_SDK_VERSION}', + 'x-sdk-version': CoreConstants.X_SDK_VERSION, 'user-agent': getUserAgent(), if (referer != null) 'referer': referer, if (origin != null) 'origin': origin, 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 index 2027731..7bc4871 100644 --- 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 @@ -1,14 +1,14 @@ import 'package:collection/collection.dart'; -import 'package:reown_appkit/modal/models/public/appkit_network_info.dart'; +import 'package:reown_appkit/reown_appkit.dart'; class ReownAppKitModalNetworks { // https://github.com/WalletConnect/blockchain-api/blob/master/SUPPORTED_CHAINS.md - static Map> supported = { - 'eip155': [ + static final Map> _mainnets = { + NetworkUtils.eip155: [ ReownAppKitModalNetworkInfo( name: 'Ethereum', chainId: '1', - chainIcon: chainImagesId['1'], + chainIcon: _networkIcons['1'], currency: 'ETH', rpcUrl: 'https://ethereum-rpc.publicnode.com', explorerUrl: 'https://etherscan.io', @@ -16,7 +16,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Optimism', chainId: '10', - chainIcon: chainImagesId['10'], + chainIcon: _networkIcons['10'], currency: 'ETH', rpcUrl: 'https://mainnet.optimism.io/', explorerUrl: 'https://optimistic.etherscan.io', @@ -24,7 +24,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Binance Smart Chain', chainId: '56', - chainIcon: chainImagesId['56'], + chainIcon: _networkIcons['56'], currency: 'BNB', rpcUrl: 'https://bsc-dataseed.binance.org/', explorerUrl: 'https://bscscan.com', @@ -32,7 +32,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Gnosis Chain', chainId: '100', - chainIcon: chainImagesId['100'], + chainIcon: _networkIcons['100'], currency: 'xDAI', rpcUrl: 'https://rpc.gnosischain.com', explorerUrl: 'https://gnosis.blockscout.com', @@ -40,7 +40,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Polygon', chainId: '137', - chainIcon: chainImagesId['137'], + chainIcon: _networkIcons['137'], currency: 'MATIC', rpcUrl: 'https://polygon.drpc.org', explorerUrl: 'https://polygonscan.com', @@ -48,7 +48,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'zkSync Era', chainId: '324', - chainIcon: chainImagesId['324'], + chainIcon: _networkIcons['324'], currency: 'ETH', rpcUrl: 'https://mainnet.era.zksync.io', explorerUrl: 'https://explorer.zksync.io', @@ -56,7 +56,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Polygon zkEVM', chainId: '1101', - chainIcon: chainImagesId['1101'], + chainIcon: _networkIcons['1101'], currency: 'ETH', rpcUrl: 'https://rpc-mainnet.matic.network', explorerUrl: 'https://explorer-evm.polygon.technology', @@ -64,7 +64,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Mantle', chainId: '5000', - chainIcon: chainImagesId['5000'], + chainIcon: _networkIcons['5000'], currency: 'BIT', rpcUrl: 'https://rpc.mantlenetwork.io', explorerUrl: 'https://explorer.mantlenetwork.io', @@ -72,7 +72,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Klaytn Mainnet', chainId: '8217', - chainIcon: chainImagesId['8217'], + chainIcon: _networkIcons['8217'], currency: 'KLAY', rpcUrl: 'https://public-node-api.klaytnapi.com/v1/cypress', explorerUrl: 'https://scope.klaytn.com', @@ -80,7 +80,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Base', chainId: '8453', - chainIcon: chainImagesId['8453'], + chainIcon: _networkIcons['8453'], currency: 'ETH', rpcUrl: 'https://mainnet.base.org', explorerUrl: 'https://basescan.org', @@ -88,7 +88,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Arbitrum', chainId: '42161', - chainIcon: chainImagesId['42161'], + chainIcon: _networkIcons['42161'], currency: 'ETH', rpcUrl: 'https://arbitrum.blockpi.network/v1/rpc/public', explorerUrl: 'https://arbiscan.io/', @@ -96,7 +96,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Celo', chainId: '42220', - chainIcon: chainImagesId['42220'], + chainIcon: _networkIcons['42220'], currency: 'CELO', rpcUrl: 'https://forno.celo.org', explorerUrl: 'https://explorer.celo.org/mainnet', @@ -104,7 +104,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Avalanche', chainId: '43114', - chainIcon: chainImagesId['43114'], + chainIcon: _networkIcons['43114'], currency: 'AVAX', rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', explorerUrl: 'https://snowtrace.io', @@ -112,7 +112,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Linea', chainId: '59144', - chainIcon: chainImagesId['59144'], + chainIcon: _networkIcons['59144'], currency: 'ETH', rpcUrl: 'https://rpc.linea.build', explorerUrl: 'https://explorer.linea.build', @@ -120,7 +120,7 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Zora', chainId: '7777777', - chainIcon: chainImagesId['7777777'], + chainIcon: _networkIcons['7777777'], currency: 'ETH', rpcUrl: 'https://rpc.zora.energy', explorerUrl: 'https://explorer.zora.energy', @@ -128,53 +128,26 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Aurora', chainId: '1313161554', - chainIcon: chainImagesId['1313161554'], + chainIcon: _networkIcons['1313161554'], currency: 'ETH', rpcUrl: 'https://mainnet.aurora.dev', explorerUrl: 'https://explorer.aurora.dev', ), ], - }; - - static Map> extra = { - 'eip155': [ - ReownAppKitModalNetworkInfo( - name: 'Fantom', - chainId: '250', - chainIcon: chainImagesId['250'], - currency: 'FTM', - rpcUrl: 'https://rpc.ftm.tools/', - explorerUrl: 'https://ftmscan.com', - ), - ReownAppKitModalNetworkInfo( - name: 'EVMos', - chainId: '9001', - chainIcon: chainImagesId['9001'], - currency: 'EVMOS', - rpcUrl: 'https://evmos-evm.publicnode.com', - explorerUrl: '', - ), - ReownAppKitModalNetworkInfo( - name: 'Iotx', - chainId: '4689', - chainIcon: chainImagesId['4689'], - currency: 'IOTX', - rpcUrl: 'https://rpc.ankr.com/iotex', - explorerUrl: 'https://iotexscan.io', - ), + NetworkUtils.solana: [ ReownAppKitModalNetworkInfo( - name: 'Metis', - chainId: '1088', - chainIcon: chainImagesId['1088'], - currency: 'METIS', - rpcUrl: 'https://metis-mainnet.public.blastapi.io', - explorerUrl: 'https://andromeda-explorer.metis.io', + name: 'Solana', + chainId: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + chainIcon: _networkIcons['5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], + currency: 'SOL', + rpcUrl: 'https://api.mainnet-beta.solana.com', + explorerUrl: 'https://solscan.io', ), - ] + ], }; - static Map> test = { - 'eip155': [ + static final Map> _testnets = { + NetworkUtils.eip155: [ ReownAppKitModalNetworkInfo( name: 'Sepolia', chainId: '11155111', @@ -186,7 +159,6 @@ class ReownAppKitModalNetworks { ReownAppKitModalNetworkInfo( name: 'Holesky', chainId: '17000', - chainIcon: chainImagesId['17000'], currency: 'ETH', rpcUrl: 'https://rpc.holesky.test', explorerUrl: 'https://explorer.holesky.test', @@ -210,14 +182,182 @@ class ReownAppKitModalNetworks { chainId: '80002', currency: 'MATIC', rpcUrl: 'https://rpc-amoy.polygon.technology/', - extraRpcUrls: [], explorerUrl: 'https://amoy.polygonscan.com', isTestNetwork: true, - ) + ), + ReownAppKitModalNetworkInfo( + name: 'BSC Testnet', + chainId: '97', + currency: 'tBNB', + rpcUrl: 'https://data-seed-prebsc-1-s1.binance.org:8545/', + explorerUrl: 'https://testnet.bscscan.com', + isTestNetwork: true, + ), + ], + NetworkUtils.solana: [ + ReownAppKitModalNetworkInfo( + name: 'Solana Testnet', + chainId: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', + currency: 'SOL', + rpcUrl: 'https://api.testnet.solana.com', + explorerUrl: 'https://explorer.solana.com/?cluster=testnet', + isTestNetwork: true, + ), + ReownAppKitModalNetworkInfo( + name: 'Solana Devnet', + chainId: 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + currency: 'SOL', + rpcUrl: 'https://api.devnet.solana.com', + explorerUrl: 'https://explorer.solana.com/?cluster=devnet', + isTestNetwork: true, + ), ], }; - static Map chainImagesId = { + static ReownAppKitModalNetworkInfo? getNetworkById( + String namespace, + String chainId, + ) { + if (namespace.isEmpty) { + throw ReownAppKitModalException('`namespace` can not be empty'); + } + if (chainId.isEmpty) { + throw ReownAppKitModalException('`chainId` can not be empty'); + } + if (chainId.contains(':')) { + return getAllSupportedNetworks(namespace: chainId.split(':').first) + .firstWhere( + (e) => e.chainId == chainId.split(':').last, + ); + } + return getAllSupportedNetworks(namespace: namespace).firstWhere( + (e) => e.chainId == chainId, + ); + } + + static void removeSupportedNetworks( + String namespace, { + List chainIds = const [], + bool includeTestnets = true, + }) { + if (namespace.isEmpty) { + throw ReownAppKitModalException('`namespace` can not be empty'); + } + _mainnets[namespace]?.removeWhere((chain) { + if (chainIds.isEmpty || chainIds.contains(chain.chainId)) { + return true; + } + return false; + }); + if (includeTestnets) { + _testnets[namespace]?.removeWhere((chain) { + if (chainIds.isEmpty || chainIds.contains(chain.chainId)) { + return true; + } + return false; + }); + } + } + + static void removeTestNetworks() { + for (var key in _testnets.keys) { + _testnets[key]!.clear(); + } + } + + static void addSupportedNetworks( + String namespace, + List chains, + ) { + if (namespace.isEmpty) { + throw ReownAppKitModalException('`namespace` can not be empty'); + } + + final List mainnets = [ + ...List.from(_mainnets[namespace] ?? []), + ...(chains.where((e) => e.isTestNetwork == false)), + ]; + _mainnets[namespace] = mainnets; + + final List testnets = [ + ...List.from(_testnets[namespace] ?? []), + ...(chains.where((e) => e.isTestNetwork == true)), + ]; + _testnets[namespace] = testnets; + } + + static List getAllSupportedNamespaces() { + final mainNamespaces = + _mainnets.keys.where((key) => _mainnets[key]!.isNotEmpty).toList(); + final testNamespaces = + _testnets.keys.where((key) => _mainnets[key]!.isNotEmpty).toList(); + return {...mainNamespaces, ...testNamespaces}.toList(); + } + + static List getAllSupportedNetworks({ + String? namespace, + }) { + final allMainnets = (namespace ?? '').isNotEmpty + ? (_mainnets[namespace] ?? []) + : _mainnets.values.expand((e) => e); + final mainnets = allMainnets.where((e) { + return !e.isTestNetwork; + }).toList(); + // + final allTestnets = (namespace ?? '').isNotEmpty + ? (_testnets[namespace] ?? []) + : _testnets.values.expand((e) => e); + final testnets = allTestnets.where((e) { + return e.isTestNetwork; + }).toList(); + // + return [...mainnets, ...testnets].toList(); + } + + static String getNamespaceForChainId(String chainId) { + if (chainId.isEmpty) { + throw ReownAppKitModalException('`chainId` can not be empty'); + } + if (NamespaceUtils.isValidChainId(chainId)) { + return chainId.split(':').first; + } + + String? namespace; + final namespaces = getAllSupportedNamespaces(); + for (var ns in namespaces) { + final found = getAllSupportedNetworks(namespace: ns).firstWhereOrNull( + (e) => e.chainId == chainId, + ); + if (found != null) { + namespace = ns; + break; + } + } + return namespace ?? ''; + } + + static String getNetworkIconId(String chainId) { + try { + final namespace = getNamespaceForChainId(chainId); + final network = getNetworkById(namespace, chainId); + if ((network?.chainIcon ?? '').isNotEmpty) { + return network!.chainIcon!; + } + return _networkIcons[chainId]!; + } catch (e) { + return ''; + } + } + + static String getCaip2Chain(String chainId) { + if (NamespaceUtils.isValidChainId(chainId)) { + return chainId; + } + final namespace = getNamespaceForChainId(chainId); + return '$namespace:$chainId'; + } + + static final Map _networkIcons = { // Ethereum '1': 'ba0ba0cd-17c6-4806-ad93-f9d174f17900', // Optimism @@ -264,60 +404,9 @@ class ReownAppKitModalNetworks { '7777777': '845c60df-d429-4991-e687-91ae45791600', // Aurora '1313161554': '3ff73439-a619-4894-9262-4470c773a100', + // Solana + '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'a1b58899-f671-4276-6a5e-56ca5bd59700', + // Near + 'mainnet': 'ab3d4dee-e416-4d8d-226c-4c131b8b7600', }; - - static ReownAppKitModalNetworkInfo? getNetworkById( - String namespace, - String chainId, - ) { - return supported[namespace]?.firstWhere((e) => e.chainId == chainId); - } - - static void removeNetworks( - String namespace, - List chainIds, - ) { - supported[namespace]!.removeWhere((chain) { - if (chainIds.contains(chain.chainId)) { - return true; - } - return false; - }); - } - - static void addNetworks( - String namespace, - List chains, - ) { - final List networks = [ - ...List.from(supported[namespace] ?? []), - ...chains, - ]; - - supported[namespace] = networks; - } - - 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_networks_utils.dart b/packages/reown_appkit/lib/modal/utils/public/appkit_modal_networks_utils.dart new file mode 100644 index 0000000..f1baaa1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/utils/public/appkit_modal_networks_utils.dart @@ -0,0 +1,25 @@ +import 'package:reown_core/reown_core.dart'; + +export 'appkit_modal_default_networks.dart'; +export 'appkit_modal_siwe_utils.dart'; + +class NetworkUtils { + static const eip155 = 'eip155'; + static const solana = 'solana'; + + static Map> defaultNetworkMethods = { + eip155: MethodsConstants.allMethods.toSet().toList(), + solana: [ + 'solana_getAccounts', + 'solana_signMessage', + 'solana_signTransaction', + 'solana_signAllTransactions', + 'solana_signAndSendTransaction', + ], + }; + + static Map> defaultNetworkEvents = { + eip155: EventsConstants.allEvents.toSet().toList(), + solana: [], + }; +} 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 index a514c01..4c9a92d 100644 --- 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 @@ -1,12 +1,11 @@ -import 'package:reown_appkit/modal/services/siwe_service/siwe_service_singleton.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/siwe_service/i_siwe_service.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, - ); + return GetIt.I().formatMessage(params); } static String getAddressFromMessage(String message) { 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 deleted file mode 100644 index 9193060..0000000 --- a/packages/reown_appkit/lib/modal/utils/public/appkit_modal_utils.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'appkit_modal_default_networks.dart'; -export 'appkit_modal_siwe_utils.dart'; diff --git a/packages/reown_appkit/lib/modal/widgets/avatars/account_avatar.dart b/packages/reown_appkit/lib/modal/widgets/avatars/account_avatar.dart index 51cee2b..211c368 100644 --- a/packages/reown_appkit/lib/modal/widgets/avatars/account_avatar.dart +++ b/packages/reown_appkit/lib/modal/widgets/avatars/account_avatar.dart @@ -68,8 +68,14 @@ class _AccountAvatarState extends State { void _modalNotifyListener() { setState(() { - _avatarUrl = widget.appKit.avatarUrl; - _address = widget.appKit.session?.address; + try { + _avatarUrl = widget.appKit.avatarUrl; + final chainId = widget.appKit.selectedChain?.chainId ?? ''; + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + _address = widget.appKit.session?.getAddress(namespace); + } catch (_) {} }); } } diff --git a/packages/reown_appkit/lib/modal/widgets/avatars/wallet_avatar.dart b/packages/reown_appkit/lib/modal/widgets/avatars/wallet_avatar.dart index 90a2873..4bc9e53 100644 --- a/packages/reown_appkit/lib/modal/widgets/avatars/wallet_avatar.dart +++ b/packages/reown_appkit/lib/modal/widgets/avatars/wallet_avatar.dart @@ -1,9 +1,9 @@ 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'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; class ListAvatar extends StatelessWidget { const ListAvatar({ @@ -22,10 +22,12 @@ class ListAvatar extends StatelessWidget { @override Widget build(BuildContext context) { + final appKitModal = ModalProvider.of(context).instance; final themeColors = ReownAppKitModalTheme.colorsOf(context); final radiuses = ReownAppKitModalTheme.radiusesOf(context); final radius = borderRadius ?? radiuses.radiusM; - final projectId = explorerService.instance.projectId; + final projectId = appKitModal.appKit!.core.projectId; + final validImage = (imageUrl ?? '').isNotEmpty && !disabled; return Stack( children: [ AspectRatio( @@ -67,11 +69,11 @@ class ListAvatar extends StatelessWidget { borderRadius: BorderRadius.circular(radius), ), clipBehavior: Clip.antiAlias, - child: (imageUrl ?? '').isNotEmpty + child: validImage ? ColorFiltered( colorFilter: ColorFilter.mode( - disabled ? Colors.grey : Colors.transparent, - BlendMode.saturation, + disabled ? Colors.white : Colors.transparent, + disabled ? BlendMode.saturation : BlendMode.saturation, ), child: CachedNetworkImage( imageUrl: imageUrl!, @@ -90,8 +92,10 @@ class ListAvatar extends StatelessWidget { 'lib/modal/assets/icons/network.svg', package: 'reown_appkit', colorFilter: ColorFilter.mode( - themeColors.grayGlass030, - BlendMode.srcIn, + disabled + ? Colors.black12 + : themeColors.grayGlass030, + disabled ? BlendMode.srcIn : BlendMode.srcIn, ), ), ) diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/address_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/address_button.dart index bb806fb..5ea4e1d 100644 --- a/packages/reown_appkit/lib/modal/widgets/buttons/address_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/buttons/address_button.dart @@ -38,7 +38,15 @@ class _AddressButtonState extends State { void _modalNotifyListener() { setState(() { - _address = widget.service.session?.address; + try { + final chainId = widget.service.selectedChain!.chainId; + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + _address = widget.service.session?.getAddress(namespace); + } catch (e) { + widget.service.appKit!.core.logger.e('[$runtimeType] $e'); + } }); } 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 index 2c25388..21c4099 100644 --- a/packages/reown_appkit/lib/modal/widgets/buttons/address_copy_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/buttons/address_copy_button.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.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 { @@ -19,10 +20,16 @@ class AddressCopyButton extends StatelessWidget { final themeColors = ReownAppKitModalTheme.colorsOf(context); return GestureDetector( onTap: () async { - await Clipboard.setData(ClipboardData(text: service.session!.address!)); - toastService.instance.show( - ToastMessage(type: ToastType.success, text: 'Address copied'), + final chainId = service.selectedChain!.chainId; + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, ); + final address = service.session!.getAddress(namespace)!; + await Clipboard.setData(ClipboardData(text: address)); + GetIt.I().show(ToastMessage( + type: ToastType.success, + text: 'Address copied', + )); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/email_login_input_field.dart b/packages/reown_appkit/lib/modal/widgets/buttons/email_login_input_field.dart new file mode 100644 index 0000000..c3ace1b --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/email_login_input_field.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/pages/confirm_email_page.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'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/input_email.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; + +class EmailLoginInputField extends StatefulWidget { + const EmailLoginInputField({super.key}); + + @override + State createState() => _EmailLoginInputFieldState(); +} + +class _EmailLoginInputFieldState extends State { + IMagicService get _magicService => GetIt.I(); + IAnalyticsService get _analyticsService => GetIt.I(); + + bool _submitted = false; + @override + void initState() { + super.initState(); + _magicService.step.addListener(_stepListener); + } + + void _stepListener() { + if ((_magicService.step.value == EmailLoginStep.verifyDevice || + _magicService.step.value == EmailLoginStep.verifyOtp || + _magicService.step.value == EmailLoginStep.verifyOtp2) && + _submitted) { + widgetStack.instance.push(ConfirmEmailPage()); + _submitted = false; + } + } + + @override + void dispose() { + _magicService.step.removeListener(_stepListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _magicService.isEmailEnabled, + builder: (context, emailEnabled, _) { + if (!emailEnabled) { + return const SizedBox.shrink(); + } + return InputEmailWidget( + onFocus: (value) { + if (value) { + _analyticsService.sendEvent(EmailLoginSelected()); + } + }, + onValueChange: (value) { + _magicService.setEmail(value); + }, + onSubmitted: (value) { + setState(() => _submitted = true); + final service = ModalProvider.of(context).instance; + final chainId = service.selectedChain?.chainId; + _analyticsService.sendEvent(EmailSubmitted()); + _magicService.connectEmail( + value: value, + chainId: chainId, + ); + }, + ); + }, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/network_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/network_button.dart index 6266cbf..d4fab66 100644 --- a/packages/reown_appkit/lib/modal/widgets/buttons/network_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/buttons/network_button.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/services/explorer_service/i_explorer_service.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'; @@ -24,22 +25,22 @@ class NetworkButton extends StatelessWidget { final ReownAppKitModalStatus serviceStatus; final VoidCallback? onTap; - String _getImageUrl(ReownAppKitModalNetworkInfo chainInfo) { + String _getImageUrl(ReownAppKitModalNetworkInfo? chainInfo) { + if (chainInfo == null) return ''; + if (chainInfo.chainIcon != null && chainInfo.chainIcon!.contains('http')) { return chainInfo.chainIcon!; } - final imageId = - ReownAppKitModalNetworks.getNetworkIconId(chainInfo.chainId); - return explorerService.instance.getAssetImageUrl(imageId); + final imageId = ReownAppKitModalNetworks.getNetworkIconId( + chainInfo.chainId, + ); + return GetIt.I().getAssetImageUrl(imageId); } @override Widget build(BuildContext context) { final themeColors = ReownAppKitModalTheme.colorsOf(context); - String imageUrl = ''; - if (chainInfo != null && (chainInfo!.chainIcon ?? '').isNotEmpty) { - imageUrl = _getImageUrl(chainInfo!); - } + final imageUrl = _getImageUrl(chainInfo); final radiuses = ReownAppKitModalTheme.radiusesOf(context); final borderRadius = radiuses.isSquare() ? 0.0 : size.height / 2; return BaseButton( diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart index ba7a548..db6c58c 100644 --- a/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart @@ -6,11 +6,15 @@ class PrimaryButton extends StatelessWidget { final String title; final VoidCallback? onTap; final bool loading; + final Color? color; + final BorderRadiusGeometry? borderRadius; const PrimaryButton({ super.key, required this.title, this.onTap, this.loading = false, + this.color, + this.borderRadius, }); @override @@ -36,7 +40,7 @@ class PrimaryButton extends StatelessWidget { if (states.contains(MaterialState.disabled)) { return themeColors.grayGlass010; } - return themeColors.accent100; + return color ?? themeColors.accent100; }, ), foregroundColor: MaterialStateProperty.resolveWith( @@ -54,9 +58,10 @@ class PrimaryButton extends StatelessWidget { color: themeColors.grayGlass010, width: 1.0, ), - borderRadius: radiuses.isSquare() - ? BorderRadius.all(Radius.zero) - : BorderRadius.circular(16.0), + borderRadius: borderRadius ?? + (radiuses.isSquare() + ? BorderRadius.all(Radius.zero) + : BorderRadius.circular(16.0)), ); }, ), diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/social_login_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/social_login_button.dart new file mode 100644 index 0000000..83e87b2 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/social_login_button.dart @@ -0,0 +1,110 @@ +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'; +import 'package:reown_appkit/modal/widgets/lists/list_items/base_list_item.dart'; +import 'package:shimmer/shimmer.dart'; + +class SocialLoginButton extends StatelessWidget { + const SocialLoginButton({ + super.key, + required this.logoPath, + required this.onTap, + this.title, + this.textAlign = TextAlign.center, + }); + final String logoPath; + final VoidCallback onTap; + final String? title; + final TextAlign textAlign; + + @override + Widget build(BuildContext context) { + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + return BaseListItem( + onTap: onTap, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LayoutBuilder(builder: (_, constraints) { + return ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(constraints.maxHeight), + child: SvgPicture.asset( + logoPath, + package: 'reown_appkit', + height: constraints.maxHeight, + width: constraints.maxHeight, + ), + ); + }), + if (title != null) + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 12.0, + right: kListItemHeight - 12.0, + ), + child: Text( + title!, + textAlign: textAlign, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + ), + ), + ], + ), + ); + } +} + +class ShimmerSocialLoginButton extends StatelessWidget { + const ShimmerSocialLoginButton({super.key, this.title}); + final String? title; + + @override + Widget build(BuildContext context) { + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final themeData = ReownAppKitModalTheme.getDataOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + return Shimmer.fromColors( + baseColor: themeColors.grayGlass005, + highlightColor: themeColors.grayGlass020, + child: BaseListItem( + child: Row( + children: [ + LayoutBuilder(builder: (_, constraints) { + return ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(constraints.maxHeight), + child: Container( + width: constraints.maxHeight, + height: constraints.maxHeight, + color: Colors.black.withOpacity(0.2), + ), + ); + }), + if (title != null) + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: kListItemHeight - 12.0), + child: Text( + title!, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/social_login_buttons_view.dart b/packages/reown_appkit/lib/modal/widgets/buttons/social_login_buttons_view.dart new file mode 100644 index 0000000..fca0e2f --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/social_login_buttons_view.dart @@ -0,0 +1,161 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/pages/all_social_logins.dart'; +import 'package:reown_appkit/modal/pages/social_login_page.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; +import 'package:reown_appkit/modal/widgets/buttons/social_login_button.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/reown_appkit.dart'; + +class SocialLoginButtonsView extends StatefulWidget { + const SocialLoginButtonsView({super.key}); + + @override + State createState() => _SocialLoginButtonsViewState(); +} + +class _SocialLoginButtonsViewState extends State { + IMagicService get _magicService => GetIt.I(); + + @override + Widget build(BuildContext context) { + final isPortrait = ResponsiveData.isPortrait(context); + return ValueListenableBuilder( + valueListenable: _magicService.isReady, + builder: (context, isReady, _) { + final options = _magicService.socials; + final count = options.length; + if (count == 0) { + return SizedBox.shrink(); + } + if (count == 1) { + return Column( + children: [ + const SizedBox.square(dimension: kListViewSeparatorHeight), + isReady + ? SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + '${options.first.name.toLowerCase()}_logo.svg', + ), + onTap: () => _initSocialLogin(options.first), + title: 'Continue with ${options.first.name}', + ) + : ShimmerSocialLoginButton( + title: 'Continue with ${options.first.name}', + ), + const SizedBox.square(dimension: kListViewSeparatorHeight), + ], + ); + } + final maxItems = isPortrait ? 6 : 8; + final isLess = count <= 4; + final fits = count == maxItems; + final exceeds = count > maxItems; + // + final firstItem = isLess ? null : options.first; + final restItems = isLess + ? options + : fits + ? options.sublist(1, min(options.length, maxItems)) + : options.sublist(1, min(options.length, (maxItems - 1))); + // + final secondRowList = [ + ...restItems.map( + (item) => Expanded( + child: isReady + ? SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + '${item.name.toLowerCase()}_logo.svg', + ), + onTap: () => _initSocialLogin(item), + ) + : ShimmerSocialLoginButton(), + ), + ), + if (exceeds) + Expanded( + child: isReady + ? SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + 'more_social_icon.svg', + ), + onTap: () { + widgetStack.instance.push( + AllSocialLoginsPage( + onSelect: (selected) { + widgetStack.instance.pop(); + _initSocialLogin(selected); + }, + ), + ); + }, + ) + : ShimmerSocialLoginButton(), + ), + ]; + return Column( + children: [ + Builder( + builder: (_) { + if (firstItem == null) { + return SizedBox.shrink(); + } + return Column( + children: [ + const SizedBox.square(dimension: kListViewSeparatorHeight), + isReady + ? SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + '${firstItem.name.toLowerCase()}_logo.svg', + ), + onTap: () => _initSocialLogin(firstItem), + title: 'Continue with ${firstItem.name}', + ) + : ShimmerSocialLoginButton( + title: 'Continue with ${firstItem.name}', + ), + ], + ); + }, + ), + Column( + children: [ + const SizedBox.square(dimension: kListViewSeparatorHeight), + Row( + children: _buttonsWithDivider(secondRowList), + ), + ], + ), + const SizedBox.square(dimension: kListViewSeparatorHeight), + ], + ); + }, + ); + } + + void _initSocialLogin(AppKitSocialOption option) => widgetStack.instance.push( + SocialLoginPage(socialOption: option), + ); + + List _buttonsWithDivider(List widgets) { + List spacedWidgets = []; + for (int i = 0; i < widgets.length; i++) { + spacedWidgets.add(widgets[i]); + if (i < widgets.length - 1) { + spacedWidgets.add( + const SizedBox.square(dimension: kListViewSeparatorHeight), + ); + } + } + return spacedWidgets; + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/icons/rounded_icon.dart b/packages/reown_appkit/lib/modal/widgets/icons/rounded_icon.dart index 27944d6..1651b4d 100644 --- a/packages/reown_appkit/lib/modal/widgets/icons/rounded_icon.dart +++ b/packages/reown_appkit/lib/modal/widgets/icons/rounded_icon.dart @@ -1,7 +1,8 @@ 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:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/explorer_service/i_explorer_service.dart'; import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; import 'package:reown_appkit/modal/utils/core_utils.dart'; @@ -25,7 +26,7 @@ class RoundedIcon extends StatelessWidget { @override Widget build(BuildContext context) { final themeColors = ReownAppKitModalTheme.colorsOf(context); - final projectId = explorerService.instance.projectId; + final projectId = GetIt.I().projectId; final radius = borderRadius ?? size; return Container( width: size, 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 index 8cc65f7..b8917cb 100644 --- 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 @@ -95,7 +95,9 @@ class WalletGridItem extends StatelessWidget { style: themeData.textStyles.tiny500.copyWith( color: isSelected ? themeColors.accent100 - : themeColors.foreground100, + : (isNetwork && onTap == null) + ? themeColors.background300 + : themeColors.foreground100, height: 1.0, ), ), 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 index 8636bb0..ad7028a 100644 --- 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 @@ -89,19 +89,21 @@ class AccountListItem extends StatelessWidget { ), ), 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, - ), - ), + (onTap != null + ? 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, + ), + ) + : const SizedBox.shrink()), ], ), ); 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 index 4abbc06..6e02ecf 100644 --- 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 @@ -8,10 +8,14 @@ class AllWalletsItem extends StatelessWidget { const AllWalletsItem({ super.key, this.title = 'All wallets', + this.titleAlign = TextAlign.left, + this.leading, this.trailing, this.onTap, }); final String title; + final TextAlign titleAlign; + final Widget? leading; final Widget? trailing; final VoidCallback? onTap; @@ -21,30 +25,36 @@ class AllWalletsItem extends StatelessWidget { final themeColors = ReownAppKitModalTheme.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, + child: LayoutBuilder(builder: (_, constraints) { + return Row( + children: [ + SizedBox.square( + dimension: constraints.maxHeight, + child: leading ?? + ThemedIcon( + iconPath: 'lib/modal/assets/icons/dots.svg', + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + title, + textAlign: titleAlign, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), ), ), ), - ), - trailing ?? const SizedBox.shrink(), - ], - ), + SizedBox.square( + dimension: constraints.maxHeight, + child: Container(), + ), + ], + ); + }), + trailing: trailing, ); } } 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 index faad699..1e244e8 100644 --- 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 @@ -53,7 +53,12 @@ class BaseListItem extends StatelessWidget { ), child: Padding( padding: padding ?? const EdgeInsets.all(8.0), - child: child, + child: Row( + children: [ + Expanded(child: child), + trailing ?? SizedBox.shrink(), + ], + ), ), ); } 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 index 17a56ee..a85c536 100644 --- 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 @@ -2,7 +2,6 @@ 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'; @@ -54,10 +53,7 @@ class DownloadWalletItem extends StatelessWidget { void _downloadApp(BuildContext context) { try { - launchUrlString( - _storeUrl, - mode: LaunchMode.externalApplication, - ); + ReownCoreUtils.openURL(_storeUrl); } catch (e) { ModalProvider.of(context).instance.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 deleted file mode 100644 index 370ec6b..0000000 --- a/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_connect_item.dart +++ /dev/null @@ -1,56 +0,0 @@ -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 = ReownAppKitModalTheme.getDataOf(context); - final themeColors = ReownAppKitModalTheme.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/wallets_list.dart b/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart index 0b474ac..e90adfe 100644 --- a/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart +++ b/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart @@ -40,16 +40,24 @@ class WalletsList extends StatelessWidget { ); final walletsListItems = isLoading - ? loadingList + ? loadingList.map( + (listItem) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: listItem, + ), + ) : 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, + (listItem) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: WalletListItem( + onTap: () => onTapWallet?.call(listItem.data), + showCheckmark: listItem.data.installed, + imageUrl: listItem.image, + title: listItem.title, + trailing: listItem.data.recent + ? const WalletItemChip(value: ' RECENT ') + : null, + ), ), ); final List items = List.from(walletsListItems); @@ -62,7 +70,7 @@ class WalletsList extends StatelessWidget { return ListView.separated( padding: const EdgeInsets.symmetric( - horizontal: kPadding12, + horizontal: kPadding8, vertical: kPadding12, ), itemBuilder: (context, index) { @@ -72,7 +80,7 @@ class WalletsList extends StatelessWidget { ); }, separatorBuilder: (_, index) => SizedBox.square( - dimension: index == 0 ? 0.0 : kListViewSeparatorHeight, + dimension: 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 index 2d6e5f2..d76ef46 100644 --- a/packages/reown_appkit/lib/modal/widgets/miscellaneous/all_wallets_header.dart +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/all_wallets_header.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/services/explorer_service/i_explorer_service.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'; @@ -21,12 +22,12 @@ class AllWalletsHeader extends StatelessWidget { child: ModalSearchBar( hint: 'Search wallet', onTextChanged: (value) { - explorerService.instance.search(query: value); + GetIt.I().search(query: value); }, onDismissKeyboard: (clear) { FocusManager.instance.primaryFocus?.unfocus(); if (clear) { - explorerService.instance.search(query: null); + GetIt.I().search(query: null); } }, ), diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart index e01cbe2..d1cf1e0 100644 --- a/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart @@ -1,6 +1,9 @@ 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:get_it/get_it.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.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'; @@ -26,8 +29,10 @@ class InputEmailWidget extends StatefulWidget { } class _InputEmailWidgetState extends State { - bool hasFocus = false; + IMagicService get _magicService => GetIt.I(); + late TextEditingController _controller; + bool hasFocus = false; bool _ready = false; bool _timedOut = false; bool _submitted = false; @@ -36,19 +41,27 @@ class _InputEmailWidgetState extends State { 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); + _ready = _magicService.isReady.value; + _timedOut = _magicService.isTimeout.value; + _magicService.onMagicError.subscribe(_onMagicErrorEvent); + _magicService.isReady.addListener(_updateStatus); + _magicService.isTimeout.addListener(_updateStatus); } void _updateStatus() { + if (!mounted) return; setState(() { - _ready = magicService.instance.isReady.value; - _timedOut = magicService.instance.isTimeout.value; + _ready = _magicService.isReady.value; + _timedOut = _magicService.isTimeout.value; }); } + void _onMagicErrorEvent(MagicErrorEvent? event) { + if (!mounted) return; + _submitted = false; + setState(() {}); + } + @override void didUpdateWidget(covariant InputEmailWidget oldWidget) { _updateStatus(); @@ -57,8 +70,9 @@ class _InputEmailWidgetState extends State { @override void dispose() { - magicService.instance.isTimeout.addListener(_updateStatus); - magicService.instance.isReady.removeListener(_updateStatus); + _magicService.onMagicError.subscribe(_onMagicErrorEvent); + _magicService.isTimeout.addListener(_updateStatus); + _magicService.isReady.removeListener(_updateStatus); super.dispose(); } @@ -66,6 +80,7 @@ class _InputEmailWidgetState extends State { Widget build(BuildContext context) { final themeColors = ReownAppKitModalTheme.colorsOf(context); return ModalSearchBar( + height: kListItemHeight, enabled: !_timedOut && _ready && !_submitted, controller: _controller, initialValue: _controller.text, @@ -80,16 +95,23 @@ class _InputEmailWidgetState extends State { }, onFocusChange: _onFocusChange, suffixIcon: widget.suffixIcon ?? - (!magicService.instance.isReady.value || _submitted + (!_magicService.isReady.value || _submitted ? Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - CircularLoader(size: 20.0, strokeWidth: 2.0), + Row( + children: [ + CircularLoader(size: 20.0, strokeWidth: 2.0), + const SizedBox.square( + dimension: kListViewSeparatorHeight, + ), + ], + ) ], ) : ValueListenableBuilder( - valueListenable: magicService.instance.email, + valueListenable: _magicService.email, builder: (context, value, _) { if (!hasFocus || _invalidEmail(value)) { return SizedBox.shrink(); @@ -136,8 +158,8 @@ class _InputEmailWidgetState extends State { void _clearEmail() { _controller.clear(); - magicService.instance.setEmail(''); - magicService.instance.setNewEmail(''); + _magicService.setEmail(''); + _magicService.setNewEmail(''); FocusManager.instance.primaryFocus?.unfocus(); } } diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart index 1c1d622..d5a8d47 100644 --- a/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart @@ -29,6 +29,7 @@ class ModalSearchBar extends StatefulWidget { this.debounce = true, this.focusNode, this.width, + this.height = kSearchFieldHeight, this.enabled = true, this.inputFormatters, }); @@ -52,6 +53,7 @@ class ModalSearchBar extends StatefulWidget { final bool debounce; final FocusNode? focusNode; final double? width; + final double height; final bool enabled; final List? inputFormatters; @@ -113,7 +115,11 @@ class _ModalSearchBarState extends State final radiuses = ReownAppKitModalTheme.radiusesOf(context); _decorationTween = DecorationTween( begin: BoxDecoration( - borderRadius: BorderRadius.circular(radiuses.radiusXS), + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : (radiuses.isCircular() + ? BorderRadius.circular(widget.height) + : BorderRadius.circular(widget.height * 0.4)), boxShadow: [ BoxShadow( color: Colors.transparent, @@ -125,7 +131,11 @@ class _ModalSearchBarState extends State ], ), end: BoxDecoration( - borderRadius: BorderRadius.circular(radiuses.radiusXS), + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : (radiuses.isCircular() + ? BorderRadius.circular(widget.height) + : BorderRadius.circular(widget.height * 0.4)), boxShadow: [ BoxShadow( color: themeColors.accenGlass015, @@ -175,7 +185,11 @@ class _ModalSearchBarState extends State final radiuses = ReownAppKitModalTheme.radiusesOf(context); final unfocusedBorder = OutlineInputBorder( borderSide: BorderSide(color: themeColors.grayGlass005, width: 1.0), - borderRadius: BorderRadius.circular(radiuses.radius2XS), + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : (radiuses.isCircular() + ? BorderRadius.circular(widget.height) + : BorderRadius.circular(widget.height * 0.3)), ); final focusedBorder = unfocusedBorder.copyWith( borderSide: BorderSide(color: themeColors.accent100, width: 1.0), @@ -187,7 +201,7 @@ class _ModalSearchBarState extends State return DecoratedBoxTransition( decoration: _decorationTween.animate(_animationController), child: Container( - height: kSearchFieldHeight + 8.0, + height: widget.height + 8.0, width: widget.width, padding: const EdgeInsets.all(4.0), child: TextFormField( @@ -228,7 +242,7 @@ class _ModalSearchBarState extends State Container( width: 16.0, height: 16.0, - margin: const EdgeInsets.only(left: kPadding12), + margin: const EdgeInsets.only(left: kPadding16), child: GestureDetector( onTap: () { _controller.clear(); @@ -249,10 +263,10 @@ class _ModalSearchBarState extends State ], ), prefixIconConstraints: BoxConstraints( - maxHeight: kSearchFieldHeight, - minHeight: kSearchFieldHeight, - maxWidth: 36.0, - minWidth: widget.noIcons ? 0.0 : 36.0, + maxHeight: widget.height, + minHeight: widget.height, + maxWidth: 40.0, + minWidth: widget.noIcons ? 0.0 : 40.0, ), labelStyle: themeData.textStyles.paragraph500.copyWith( color: themeColors.inverse100, @@ -292,8 +306,8 @@ class _ModalSearchBarState extends State ) : null), suffixIconConstraints: BoxConstraints( - maxHeight: kSearchFieldHeight, - minHeight: kSearchFieldHeight, + maxHeight: widget.height, + minHeight: widget.height, maxWidth: 36.0, minWidth: widget.noIcons ? 0.0 : 36.0, ), @@ -303,7 +317,7 @@ class _ModalSearchBarState extends State disabledBorder: disabledBorder, focusedBorder: focusedBorder, filled: true, - fillColor: themeColors.grayGlass005, + fillColor: themeColors.grayGlass002, contentPadding: const EdgeInsets.all(0.0), ), ), 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 index d02c099..d76fe03 100644 --- a/packages/reown_appkit/lib/modal/widgets/miscellaneous/verify_otp_view.dart +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/verify_otp_view.dart @@ -1,9 +1,10 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.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'; @@ -68,7 +69,7 @@ class _VerifyOtpViewState extends State void _resendEmail() async { final diff = DateTime.now().difference(_resendEnabledAt).inSeconds; if (diff < 0) { - toastService.instance.show(ToastMessage( + GetIt.I().show(ToastMessage( type: ToastType.error, text: 'Try again after ${diff.abs()} seconds', )); @@ -76,7 +77,7 @@ class _VerifyOtpViewState extends State final email = widget.currentEmail; widget.resendEmail(value: email); _resendEnabledAt = DateTime.now().add(Duration(seconds: 30)); - toastService.instance.show(ToastMessage( + GetIt.I().show(ToastMessage( type: ToastType.success, text: 'Code email resent', )); diff --git a/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart b/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart index bde4413..8f5c6d3 100644 --- a/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart +++ b/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart @@ -14,10 +14,12 @@ class ModalNavbar extends StatelessWidget { required this.body, required this.title, this.leftAction, + this.rightActions = const [], this.safeAreaLeft = false, this.safeAreaRight = false, this.safeAreaBottom = true, this.noClose = false, + this.noBack = false, this.divider = true, }); @@ -26,7 +28,13 @@ class ModalNavbar extends StatelessWidget { final Widget body; final String title; final NavbarActionButton? leftAction; - final bool safeAreaLeft, safeAreaRight, safeAreaBottom, noClose, divider; + final List rightActions; + final bool safeAreaLeft, + safeAreaRight, + safeAreaBottom, + noClose, + noBack, + divider; @override Widget build(BuildContext context) { @@ -54,7 +62,7 @@ class ModalNavbar extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - widgetStack.instance.canPop() + widgetStack.instance.canPop() && !noBack ? NavbarActionButton( asset: 'lib/modal/assets/icons/chevron_left.svg', action: onBack ?? widgetStack.instance.pop, @@ -82,6 +90,9 @@ class ModalNavbar extends StatelessWidget { ModalProvider.of(context).instance.closeModal(); }, ), + Row( + children: rightActions, + ), ], ); }, 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 index 7c0f998..d06fd82 100644 --- a/packages/reown_appkit/lib/modal/widgets/navigation/navbar_action_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/navigation/navbar_action_button.dart @@ -9,18 +9,22 @@ class NavbarActionButton extends StatelessWidget { required this.asset, required this.action, this.color, + this.dimension = kNavbarHeight, }); final String asset; final VoidCallback action; final Color? color; + final double dimension; @override Widget build(BuildContext context) { final themeColors = ReownAppKitModalTheme.colorsOf(context); return SizedBox.square( - dimension: kNavbarHeight, + dimension: dimension, child: IconButton( onPressed: action, + padding: const EdgeInsets.all(0.0), + visualDensity: VisualDensity.compact, icon: SvgPicture.asset( asset, package: 'reown_appkit', 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 index bb52d63..4fc83d3 100644 --- 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 @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/pages/social_login_page.dart'; +import 'package:reown_appkit/modal/services/explorer_service/i_explorer_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.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'; @@ -18,14 +17,17 @@ import 'package:reown_appkit/reown_appkit.dart'; class AppKitModalAccountButton extends StatefulWidget { const AppKitModalAccountButton({ super.key, - required this.appKit, + @Deprecated('Use appKitModal parameter') this.appKit, + required this.appKitModal, this.size = BaseButtonSize.regular, this.avatar, this.context, this.custom, }); - final IReownAppKitModal appKit; + @Deprecated('Use appKitModal parameter') + final IReownAppKitModal? appKit; + final IReownAppKitModal appKitModal; final BaseButtonSize size; final String? avatar; final BuildContext? context; @@ -37,59 +39,72 @@ class AppKitModalAccountButton extends StatefulWidget { } class _AppKitModalAccountButtonState extends State { - String _balance = BalanceButton.balanceDefault; + IMagicService get _magicService => GetIt.I(); String _address = ''; - String? _tokenImage; - String? _tokenName; @override void initState() { super.initState(); _modalNotifyListener(); - widget.appKit.addListener(_modalNotifyListener); + widget.appKitModal.addListener(_modalNotifyListener); // TODO [AppKitModalAccountButton] this should go in ReownAppKitModal but for that, init() method of ReownAppKitModal should receive a BuildContext, which would be a breaking change - magicService.instance.onMagicRpcRequest.subscribe(_approveSign); - magicService.instance.onMagicLoginRequest.subscribe(_loginRequested); + _magicService.onMagicRpcRequest.subscribe(_approveSign); + _magicService.onMagicLoginRequest.subscribe(_loginRequested); } @override void dispose() { - widget.appKit.removeListener(_modalNotifyListener); - magicService.instance.onMagicRpcRequest.unsubscribe(_approveSign); - magicService.instance.onMagicLoginRequest.unsubscribe(_loginRequested); + widget.appKitModal.removeListener(_modalNotifyListener); + _magicService.onMagicRpcRequest.unsubscribe(_approveSign); + _magicService.onMagicLoginRequest.unsubscribe(_loginRequested); super.dispose(); } void _modalNotifyListener() { - setState(() { - _address = widget.appKit.session?.address ?? ''; - final chainId = widget.appKit.selectedChain?.chainId ?? ''; - final imageId = ReownAppKitModalNetworks.getNetworkIconId(chainId); - _tokenImage = explorerService.instance.getAssetImageUrl(imageId); - _balance = widget.appKit.chainBalance; - _tokenName = widget.appKit.selectedChain?.currency; - }); + final chainId = widget.appKitModal.selectedChain?.chainId ?? ''; + if (chainId.isNotEmpty) { + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + _address = widget.appKitModal.session?.getAddress(namespace) ?? ''; + } + setState(() {}); } void _onTap() { - widget.appKit.openModalView(); + widget.appKitModal.openModalView(); } void _approveSign(MagicRequestEvent? args) async { if (args?.request != null) { - if (widget.appKit.isOpen) { + if (widget.appKitModal.isOpen) { widgetStack.instance.push(ApproveTransactionPage()); } else { - widget.appKit.openModalView(ApproveTransactionPage()); + widget.appKitModal.openModalView(ApproveTransactionPage()); } } } void _loginRequested(MagicSessionEvent? args) { - if (widget.appKit.isOpen) { - widgetStack.instance.popAllAndPush(ConfirmEmailPage()); + if (args == null) return; + final provider = args.provider; + final isOpen = widget.appKitModal.isOpen; + if (isOpen) { + if (provider != null) { + widgetStack.instance.popAllAndPush(SocialLoginPage( + socialOption: provider, + )); + } else { + widgetStack.instance.popAllAndPush(ConfirmEmailPage()); + } } else { - widget.appKit.openModalView(ConfirmEmailPage()); + if (provider != null) { + widget.appKitModal.openModalView(SocialLoginPage( + socialOption: provider, + )); + } else { + widget.appKitModal.openModalView(ConfirmEmailPage()); + } } } @@ -101,7 +116,8 @@ class _AppKitModalAccountButtonState extends State { final themeColors = ReownAppKitModalTheme.colorsOf(context); final radiuses = ReownAppKitModalTheme.radiusesOf(context); final borderRadius = radiuses.isSquare() ? 0.0 : widget.size.height / 2; - final enabled = _address.isNotEmpty && widget.appKit.status.isInitialized; + final enabled = + _address.isNotEmpty && widget.appKitModal.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 Stack( @@ -145,20 +161,18 @@ class _AppKitModalAccountButtonState extends State { mainAxisSize: MainAxisSize.min, children: [ _BalanceButton( - isLoading: widget.appKit.status.isLoading, - balance: _balance, - tokenName: _tokenName, - tokenImage: _tokenImage, - iconSize: widget.size.iconSize, + appKit: widget.appKitModal, buttonSize: widget.size, onTap: enabled ? _onTap : null, ), const SizedBox.square(dimension: 4.0), - _AddressButton( - address: _address, - buttonSize: widget.size, - appKit: widget.appKit, - onTap: enabled ? _onTap : null, + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: AppKitModalAddressButton( + size: widget.size, + appKitModal: widget.appKitModal, + onTap: enabled ? _onTap : null, + ), ), ], ), @@ -168,128 +182,14 @@ class _AppKitModalAccountButtonState extends State { } } -class _AddressButton extends StatelessWidget { - const _AddressButton({ - required this.buttonSize, - required this.address, - required this.appKit, - required this.onTap, - }); - final BaseButtonSize buttonSize; - final VoidCallback? onTap; - final String address; - final IReownAppKitModal appKit; - - @override - Widget build(BuildContext context) { - if (address.isEmpty) { - return SizedBox.shrink(); - } - final themeData = ReownAppKitModalTheme.getDataOf(context); - final textStyle = buttonSize == BaseButtonSize.small - ? themeData.textStyles.small600 - : themeData.textStyles.paragraph600; - final themeColors = ReownAppKitModalTheme.colorsOf(context); - final radiuses = ReownAppKitModalTheme.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( - appKit: appKit, - 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.appKit, required this.onTap, - required this.isLoading, - required this.balance, - required this.tokenName, - required this.tokenImage, - required this.iconSize, required this.buttonSize, }); + final IReownAppKitModal appKit; final VoidCallback? onTap; - final bool isLoading; - final String balance; - final String? tokenName; - final String? tokenImage; - final double iconSize; final BaseButtonSize buttonSize; @override @@ -299,6 +199,13 @@ class _BalanceButton extends StatelessWidget { final textStyle = buttonSize == BaseButtonSize.small ? themeData.textStyles.small600 : themeData.textStyles.paragraph600; + final chainId = appKit.selectedChain?.chainId ?? ''; + final imageId = ReownAppKitModalNetworks.getNetworkIconId(chainId); + String tokenImage = GetIt.I().getAssetImageUrl(imageId); + final balance = appKit.balanceNotifier.value; + if (balance.contains(AppKitModalBalanceButton.balanceDefault)) { + tokenImage = ''; + } return BaseButton( size: BaseButtonSize.small, onTap: onTap, @@ -319,7 +226,7 @@ class _BalanceButton extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - isLoading + appKit.status.isLoading ? Row( children: [ const SizedBox.square(dimension: kPadding6), @@ -330,21 +237,26 @@ class _BalanceButton extends StatelessWidget { const SizedBox.square(dimension: kPadding6), ], ) - : (tokenImage ?? '').isEmpty + : tokenImage.isEmpty ? RoundedIcon( assetPath: 'lib/modal/assets/icons/network.svg', - size: iconSize, + size: buttonSize.iconSize, assetColor: themeColors.inverse100, padding: 4.0, ) : RoundedIcon( imageUrl: tokenImage, - size: iconSize + 2.0, + size: buttonSize.iconSize + 2.0, ), const SizedBox.square(dimension: 4.0), - Text( - '$balance ${tokenName ?? ''}', - style: textStyle, + ValueListenableBuilder( + valueListenable: appKit.balanceNotifier, + builder: (_, balance, __) { + return Text( + balance, + style: textStyle, + ); + }, ), ], ), diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_address_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_address_button.dart new file mode 100644 index 0000000..dbf0539 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_address_button.dart @@ -0,0 +1,119 @@ +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 AppKitModalAddressButton extends StatelessWidget { + const AppKitModalAddressButton({ + super.key, + required this.appKitModal, + this.size = BaseButtonSize.regular, + this.onTap, + }); + final IReownAppKitModal appKitModal; + final BaseButtonSize size; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + if (appKitModal.session == null) { + return SizedBox.shrink(); + } + final chainId = appKitModal.selectedChain?.chainId ?? ''; + if (chainId.isEmpty) { + return SizedBox.shrink(); + } + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId(chainId); + final address = appKitModal.session!.getAddress(namespace); + if ((address ?? '').isEmpty) { + return SizedBox.shrink(); + } + final themeData = ReownAppKitModalTheme.getDataOf(context); + final textStyle = size == BaseButtonSize.small + ? themeData.textStyles.small600 + : themeData.textStyles.paragraph600; + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final innerBorderRadius = radiuses.isSquare() ? 0.0 : size.height / 2; + return Padding( + padding: EdgeInsets.only( + top: size == BaseButtonSize.small ? 4.0 : 0.0, + bottom: size == BaseButtonSize.small ? 4.0 : 0.0, + ), + child: BaseButton( + size: size, + onTap: onTap, + overridePadding: MaterialStateProperty.all( + EdgeInsets.only( + left: size == 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(size.iconSize), + border: Border.all( + color: themeColors.grayGlass005, + width: 1.0, + strokeAlign: BorderSide.strokeAlignInside, + ), + ), + child: AccountAvatar( + appKit: appKitModal, + size: size.iconSize * 0.95, + disabled: false, + ), + ), + const SizedBox.square(dimension: 4.0), + Text( + RenderUtils.truncate( + address!, + length: size == BaseButtonSize.small ? 2 : 4, + ), + style: textStyle, + ), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_balance_button.dart similarity index 53% rename from packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart rename to packages/reown_appkit/lib/modal/widgets/public/appkit_modal_balance_button.dart index 2c100d7..3b67c81 100644 --- a/packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_balance_button.dart @@ -1,55 +1,56 @@ import 'package:flutter/material.dart'; -import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; +import 'package:get_it/get_it.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/services/explorer_service/i_explorer_service.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/circular_loader.dart'; import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; -// Export -class BalanceButton extends StatefulWidget { - static const balanceDefault = '_._'; - - const BalanceButton({ +class AppKitModalBalanceButton extends StatefulWidget { + const AppKitModalBalanceButton({ super.key, - required this.service, + required this.appKitModal, this.size = BaseButtonSize.regular, this.onTap, }); - - final IReownAppKitModal service; + static const balanceDefault = '_.__'; + final IReownAppKitModal appKitModal; final BaseButtonSize size; final VoidCallback? onTap; @override - State createState() => _BalanceButtonState(); + State createState() => + _AppKitModalBalanceButtonState(); } -class _BalanceButtonState extends State { - String _balance = BalanceButton.balanceDefault; +class _AppKitModalBalanceButtonState extends State { String? _tokenImage; - String? _tokenName; @override void initState() { super.initState(); _modalNotifyListener(); - widget.service.addListener(_modalNotifyListener); + widget.appKitModal.addListener(_modalNotifyListener); } @override void dispose() { - widget.service.removeListener(_modalNotifyListener); + widget.appKitModal.removeListener(_modalNotifyListener); super.dispose(); } void _modalNotifyListener() { setState(() { - final chainId = widget.service.selectedChain?.chainId ?? '1'; + final chainId = widget.appKitModal.selectedChain?.chainId ?? '1'; final imageId = ReownAppKitModalNetworks.getNetworkIconId(chainId); - _tokenImage = explorerService.instance.getAssetImageUrl(imageId); - _balance = widget.service.chainBalance; - _tokenName = widget.service.selectedChain?.currency; + _tokenImage = GetIt.I().getAssetImageUrl(imageId); + final balance = widget.appKitModal.balanceNotifier.value; + if (balance.contains(AppKitModalBalanceButton.balanceDefault)) { + _tokenImage = ''; + } }); } @@ -96,12 +97,35 @@ class _BalanceButtonState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - RoundedIcon( - imageUrl: _tokenImage, - size: widget.size.height * 0.7, - ), + widget.appKitModal.status.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: widget.size.height * 0.55, + assetColor: themeColors.inverse100, + padding: 4.0, + ) + : RoundedIcon( + imageUrl: _tokenImage!, + size: widget.size.height * 0.55, + ), const SizedBox.square(dimension: 4.0), - Text('$_balance ${_tokenName ?? ''}'), + ValueListenableBuilder( + valueListenable: widget.appKitModal.balanceNotifier, + builder: (_, balance, __) { + return Text(balance); + }, + ), ], ), ); 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 index 0707804..3887109 100644 --- 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 @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.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'; @@ -110,17 +111,18 @@ class _WebViewWidget extends StatefulWidget { } class _WebViewWidgetState extends State<_WebViewWidget> { + IMagicService get _magicService => GetIt.I(); bool _show = true; // @override void initState() { super.initState(); - magicService.instance.onMagicRpcRequest.subscribe(_onRequest); + _magicService.onMagicRpcRequest.subscribe(_onRequest); } @override void dispose() { - magicService.instance.onMagicRpcRequest.unsubscribe(_onRequest); + _magicService.onMagicRpcRequest.unsubscribe(_onRequest); super.dispose(); } @@ -134,12 +136,13 @@ class _WebViewWidgetState extends State<_WebViewWidget> { @override Widget build(BuildContext context) { - final emailEnabled = magicService.instance.isEnabled.value; - if (emailEnabled && _show) { + final emailEnabled = _magicService.isEmailEnabled.value; + final socialEnabled = _magicService.isSocialEnabled.value; + if ((emailEnabled || socialEnabled) && _show) { return SizedBox( width: 0.5, height: 0.5, - child: magicService.instance.webview, + child: _magicService.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 index 8aeae6e..069ff8b 100644 --- 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 @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/i_analytics_service.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'; @@ -57,7 +58,7 @@ class _AppKitModalNetworkSelectButtonState } void _onConnectPressed() { - analyticsService.instance.sendEvent(ClickNetworksEvent()); + GetIt.I().sendEvent(ClickNetworksEvent()); widget.appKit.openModalView( ReownAppKitModalSelectNetworkPage( onTapNetwork: (info) { 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 index 317f50d..71e0c33 100644 --- a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart @@ -1,6 +1,8 @@ +export 'appkit_modal_account_button.dart'; +export 'appkit_modal_address_button.dart'; +export 'appkit_modal_balance_button.dart'; 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/text/appkit_address.dart b/packages/reown_appkit/lib/modal/widgets/text/appkit_address.dart index 814cc3e..9add272 100644 --- a/packages/reown_appkit/lib/modal/widgets/text/appkit_address.dart +++ b/packages/reown_appkit/lib/modal/widgets/text/appkit_address.dart @@ -48,7 +48,11 @@ class _AddressState extends State
{ void _modalNotifyListener() { setState(() { - _address = widget.service.session?.address; + final chainId = widget.service.selectedChain?.chainId ?? ''; + final namespace = ReownAppKitModalNetworks.getNamespaceForChainId( + chainId, + ); + _address = widget.service.session?.getAddress(namespace); }); } } diff --git a/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart b/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart index 3671578..d449b1c 100644 --- a/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart +++ b/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart @@ -1,7 +1,6 @@ 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'; @@ -20,43 +19,46 @@ class BalanceText extends StatefulWidget { } class _BalanceTextState extends State { - String _balance = BalanceButton.balanceDefault; - String? _tokenName; - IReownAppKitModal? _service; + IReownAppKitModal? _appKitModal; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - _service = ModalProvider.of(context).instance; + _appKitModal = ModalProvider.of(context).instance; _modalNotifyListener(); - _service?.addListener(_modalNotifyListener); + _appKitModal?.addListener(_modalNotifyListener); }); } @override void dispose() { - _service?.removeListener(_modalNotifyListener); + _appKitModal?.removeListener(_modalNotifyListener); super.dispose(); } void _modalNotifyListener() { - if (_service == null) return; - setState(() { - _balance = _service!.chainBalance; - _tokenName = _service?.selectedChain?.currency; - }); + if (_appKitModal == null) return; + setState(() {}); } @override Widget build(BuildContext context) { final themeData = ReownAppKitModalTheme.getDataOf(context); final themeColors = ReownAppKitModalTheme.colorsOf(context); - return Text( - '$_balance ${_tokenName ?? ''}', - style: themeData.textStyles.paragraph500.copyWith( - color: themeColors.foreground200, - ), + if (_appKitModal == null) { + return const SizedBox.shrink(); + } + return ValueListenableBuilder( + valueListenable: _appKitModal!.balanceNotifier, + builder: (_, balance, __) { + return Text( + balance, + 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 index ed3f6f7..cdbe8d5 100644 --- a/packages/reown_appkit/lib/modal/widgets/toast/toast.dart +++ b/packages/reown_appkit/lib/modal/widgets/toast/toast.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.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({ @@ -37,7 +38,7 @@ class _ToastWidgetState extends State if (!mounted) { return; } - _controller.reverse().then((value) => toastService.instance.clear()); + _controller.reverse().then((value) => GetIt.I().clear()); }); }); } diff --git a/packages/reown_appkit/lib/modal/widgets/toast/toast_presenter.dart b/packages/reown_appkit/lib/modal/widgets/toast/toast_presenter.dart index fe25e5a..93daccf 100644 --- a/packages/reown_appkit/lib/modal/widgets/toast/toast_presenter.dart +++ b/packages/reown_appkit/lib/modal/widgets/toast/toast_presenter.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/toast_service/i_toast_service.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 { @@ -9,7 +10,7 @@ class ToastPresenter extends StatelessWidget { @override Widget build(BuildContext context) { return StreamBuilder( - stream: toastService.instance.toasts, + stream: GetIt.I().toasts, builder: (context, snapshot) { if (snapshot.hasData && snapshot.data != null) { return ToastWidget(message: snapshot.data!); 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 index cd64279..4ba4569 100644 --- 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 @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/explorer_service/i_explorer_service.dart'; import 'package:reown_appkit/reown_appkit.dart'; class ExplorerServiceItemsListener extends StatefulWidget { @@ -26,23 +27,24 @@ class ExplorerServiceItemsListener extends StatefulWidget { class _ExplorerServiceItemsListenerState extends State { List> _items = []; + IExplorerService get _explorerService => GetIt.I(); @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: explorerService.instance.initialized, + valueListenable: _explorerService.initialized, builder: (context, initialised, _) { if (!initialised) { return widget.builder(context, initialised, [], false); } return ValueListenableBuilder( - valueListenable: explorerService.instance.isSearching, + valueListenable: _explorerService.isSearching, builder: (context, searching, _) { if (searching) { return widget.builder(context, initialised, _items, searching); } return ValueListenableBuilder>( - valueListenable: explorerService.instance.listings, + valueListenable: _explorerService.listings, builder: (context, items, _) { if (widget.listen) { _items = items.toGridItems(); @@ -65,7 +67,7 @@ extension on List { GridItem( title: item.listing.name, id: item.listing.id, - image: explorerService.instance.getWalletImageUrl( + image: GetIt.I().getWalletImageUrl( item.listing.imageId, ), data: item, 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 index 3a59bd7..1d2eacd 100644 --- 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 @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:reown_appkit/modal/constants/string_constants.dart'; +import 'package:get_it/get_it.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/services/network_service/i_network_service.dart'; +import 'package:reown_appkit/modal/utils/public/appkit_modal_default_networks.dart'; import 'package:reown_appkit/modal/widgets/modal_provider.dart'; class NetworkServiceItemsListener extends StatelessWidget { + INetworkService get _networkService => GetIt.I(); const NetworkServiceItemsListener({ super.key, required this.builder, @@ -20,30 +22,15 @@ class NetworkServiceItemsListener extends StatelessWidget { @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: networkService.instance.initialized, + valueListenable: _networkService.initialized, builder: (context, bool initialised, _) { if (!initialised) { return builder(context, initialised, []); } return ValueListenableBuilder( - valueListenable: networkService.instance.itemList, + valueListenable: _networkService.itemList, builder: (context, items, _) { - final service = ModalProvider.of(context).instance; - 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); - }); + final parsedItems = items.parseItems(context); return builder(context, initialised, parsedItems); }, ); @@ -51,3 +38,31 @@ class NetworkServiceItemsListener extends StatelessWidget { ); } } + +extension on List> { + List> parseItems(BuildContext context) { + final service = ModalProvider.of(context).instance; + final supportedChains = service.getAvailableChains(); + if (supportedChains == null) { + return this + ..sort((a, b) { + final disabledA = a.disabled ? 0 : 1; + final disabledB = b.disabled ? 0 : 1; + return disabledB.compareTo(disabledA); + }); + } + return map((item) { + final caip2Chain = ReownAppKitModalNetworks.getCaip2Chain( + item.data.chainId, + ); + return item.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); + }); + } +} 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 index 55bdf39..6d86684 100644 --- 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 @@ -11,6 +11,7 @@ abstract class IWidgetStack with ChangeNotifier { void push( Widget widget, { bool renderScreen = false, + bool replace = false, AnalyticsEvent? event, }); 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 index 34b7089..6cec8d0 100644 --- a/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart +++ b/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/i_analytics_service.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'; @@ -18,13 +19,17 @@ class WidgetStack extends IWidgetStack { @override void push( Widget widget, { + bool replace = false, bool renderScreen = false, AnalyticsEvent? event, }) { if (event != null) { - analyticsService.instance.sendEvent(event); + GetIt.I().sendEvent(event); } onRenderScreen.value = renderScreen; + if (replace) { + _stack.removeLast(); + } _stack.add(widget); notifyListeners(); } diff --git a/packages/reown_appkit/lib/version.dart b/packages/reown_appkit/lib/version.dart index 92a0d66..cf971dd 100644 --- a/packages/reown_appkit/lib/version.dart +++ b/packages/reown_appkit/lib/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '1.0.1'; +const packageVersion = '1.2.0'; diff --git a/packages/reown_appkit/pubspec.yaml b/packages/reown_appkit/pubspec.yaml index 830494f..cf6893e 100644 --- a/packages/reown_appkit/pubspec.yaml +++ b/packages/reown_appkit/pubspec.yaml @@ -1,6 +1,6 @@ name: reown_appkit description: "Reown is the onchain UX platform that provides toolkits built on top of the WalletConnect Network" -version: 1.0.1 +version: 1.2.0 homepage: https://github.com/reown-com/reown_flutter repository: https://github.com/reown-com/reown_flutter/tree/master/packages/reown_appkit documentation: https://docs.reown.com/appkit/flutter/core/installation @@ -10,9 +10,9 @@ environment: dependencies: appcheck: ^1.5.1 - cached_network_image: ^3.3.1 + cached_network_image: ^3.4.0 coinbase_wallet_sdk: ^1.0.10 - collection: ^1.17.2 + collection: ^1.18.0 convert: ^3.1.1 cupertino_icons: ^1.0.2 custom_sliding_segmented_control: ^1.8.1 @@ -21,32 +21,38 @@ dependencies: sdk: flutter flutter_svg: ^2.0.10+1 freezed_annotation: ^2.4.1 + get_it: ^8.0.0 http: ^1.1.2 json_annotation: ^4.8.1 plugin_platform_interface: ^2.1.8 qr_flutter_wc: ^0.0.3 - reown_core: ^1.0.0 - reown_sign: ^1.0.0 + reown_core: ^1.0.4 + # reown_core: + # path: ../reown_core/ + reown_sign: ^1.0.4 + # 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 + uuid: ^4.5.1 + webview_flutter: ^4.8.0 + webview_flutter_android: ^3.16.3 + webview_flutter_wkwebview: ^3.14.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 + lints: ^4.0.0 logger: ^2.2.0 mockito: ^5.4.3 - package_info_plus: ^7.0.0 - reown_walletkit: ^1.0.0 + package_info_plus: ^8.0.2 + reown_walletkit: ^1.0.3 + # 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 diff --git a/packages/reown_appkit/test/shared/shared_test_utils.mocks.dart b/packages/reown_appkit/test/shared/shared_test_utils.mocks.dart index d8a1e50..fe4428e 100644 --- a/packages/reown_appkit/test/shared/shared_test_utils.mocks.dart +++ b/packages/reown_appkit/test/shared/shared_test_utils.mocks.dart @@ -1454,7 +1454,7 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ) as _i19.Logger); @override - void addLogListener(_i19.LogCallback? callback) => super.noSuchMethod( + void addLogListener(dynamic Function(String)? callback) => super.noSuchMethod( Invocation.method( #addLogListener, [callback], @@ -1463,7 +1463,8 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ); @override - bool removeLogListener(_i19.LogCallback? callback) => (super.noSuchMethod( + bool removeLogListener(dynamic Function(String)? callback) => + (super.noSuchMethod( Invocation.method( #removeLogListener, [callback], @@ -1482,15 +1483,14 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ) as _i23.Future); @override - _i23.Future addLinkModeSupportedApp(String? universalLink) => + _i23.Future addLinkModeSupportedApp(String? universalLink) => (super.noSuchMethod( Invocation.method( #addLinkModeSupportedApp, [universalLink], ), - returnValue: _i23.Future.value(), - returnValueForMissingStub: _i23.Future.value(), - ) as _i23.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override List getLinkModeSupportedApps() => (super.noSuchMethod( diff --git a/packages/reown_core/CHANGELOG.md b/packages/reown_core/CHANGELOG.md index 227f443..2032aba 100644 --- a/packages/reown_core/CHANGELOG.md +++ b/packages/reown_core/CHANGELOG.md @@ -1,3 +1,15 @@ +## 1.0.4 + +- Minor change + +## 1.0.3 + +- Better logs + +## 1.0.1 + +- Minor change + ## 1.0.0 -Initial release. +- Initial release. diff --git a/packages/reown_core/lib/connectivity/connectivity.dart b/packages/reown_core/lib/connectivity/connectivity.dart index 1cef8df..91ffe1a 100644 --- a/packages/reown_core/lib/connectivity/connectivity.dart +++ b/packages/reown_core/lib/connectivity/connectivity.dart @@ -31,14 +31,14 @@ class ConnectivityState implements IConnectivity { final isOnlineStatus = isMobileData || isWifi; if (isOnline.value != isOnlineStatus) { - _core.logger.i('[$runtimeType] Connectivity changed $result'); isOnline.value = isOnlineStatus; + _core.logger.i('[$runtimeType] Connectivity changed $isOnlineStatus'); - if (isOnline.value && !_core.relayClient.isConnected) { - await _core.relayClient.connect(); - } else if (!isOnline.value && _core.relayClient.isConnected) { - await _core.relayClient.disconnect(); - } + // 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/core_impl.dart b/packages/reown_core/lib/core_impl.dart index 3aa699a..7300996 100644 --- a/packages/reown_core/lib/core_impl.dart +++ b/packages/reown_core/lib/core_impl.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:math'; + import 'package:logger/logger.dart'; import 'package:reown_core/connectivity/connectivity.dart'; import 'package:reown_core/connectivity/i_connectivity.dart'; @@ -83,13 +86,26 @@ class ReownCore implements IReownCore { Logger get logger => _logger; @override - void addLogListener(LogCallback callback) { - Logger.addLogListener(callback); + void addLogListener(Function(String) callback) { + try { + _logCallback = (LogEvent event) { + if (event.level == _logLevel.toLevel()) { + callback.call('[LogLevel ${event.level.name}] ${event.message}'); + } + }; + Logger.addLogListener(_logCallback!); + } catch (_) {} } + late final LogLevel _logLevel; + late final LogCallback? _logCallback; + @override - bool removeLogListener(LogCallback callback) { - return Logger.removeLogListener(callback); + bool removeLogListener(Function(String) callback) { + if (_logCallback != null) { + return Logger.removeLogListener(_logCallback!); + } + return false; } @override @@ -104,9 +120,16 @@ class ReownCore implements IReownCore { IHttpClient httpClient = const HttpWrapper(), IWebSocketHandler? webSocketHandler, }) { + PrettyPrinter(); + _logLevel = logLevel; _logger = Logger( - level: logLevel.toLevel(), - printer: PrettyPrinter(methodCount: null), + level: _logLevel.toLevel(), + printer: _LogPrinter( + stackTraceBeginIndex: 0, + methodCount: + _logLevel == LogLevel.debug || _logLevel == LogLevel.error ? 8 : 0, + errorMethodCount: 8, + ), ); heartbeat = HeartBeat(); storage = SharedPrefsStores( @@ -203,13 +226,16 @@ class ReownCore implements IReownCore { } @override - Future addLinkModeSupportedApp(String universalLink) async { + Future addLinkModeSupportedApp(String universalLink) async { + logger.d('[$runtimeType] addLinkModeSupportedApp $universalLink'); return await linkModeStore.update(universalLink); } @override List getLinkModeSupportedApps() { - return linkModeStore.getList(); + final linoModeApps = linkModeStore.getList(); + logger.d('[$runtimeType] getLinkModeSupportedApps $linoModeApps'); + return linoModeApps; } @override @@ -219,3 +245,245 @@ class ReownCore implements IReownCore { } } } + +class _LogPrinter extends LogPrinter { + static const topLeftCorner = '┌'; + static const bottomLeftCorner = '└'; + static const middleCorner = '├'; + static const verticalLine = '│'; + static const doubleDivider = '─'; + static const singleDivider = '┄'; + + static final Map defaultLevelEmojis = { + Level.debug: '🐛', + Level.info: '📝', + Level.error: '❌', + }; + + static final _deviceStackTraceRegex = RegExp(r'#[0-9]+\s+(.+) \((\S+)\)'); + + static final _webStackTraceRegex = RegExp(r'^((packages|dart-sdk)/\S+/)'); + + static final _browserStackTraceRegex = + RegExp(r'^(?:package:)?(dart:\S+|\S+)'); + + final int stackTraceBeginIndex; + + final int? methodCount; + + final int? errorMethodCount; + + final int lineLength = 200; + + final Map excludeBox = const {}; + + final bool noBoxingByDefault = false; + + final List excludePaths = const []; + + /// Contains the parsed rules resulting from [excludeBox] and [noBoxingByDefault]. + late final Map _includeBox; + String _topBorder = ''; + String _middleBorder = ''; + String _bottomBorder = ''; + + _LogPrinter({ + this.stackTraceBeginIndex = 0, + this.methodCount = 0, + this.errorMethodCount = 8, + }) { + var doubleDividerLine = StringBuffer(); + var singleDividerLine = StringBuffer(); + for (var i = 0; i < lineLength - 1; i++) { + doubleDividerLine.write(doubleDivider); + singleDividerLine.write(singleDivider); + } + + _topBorder = '$topLeftCorner$doubleDividerLine'; + _middleBorder = '$middleCorner$singleDividerLine'; + _bottomBorder = '$bottomLeftCorner$doubleDividerLine'; + + // Translate excludeBox map (constant if default) to includeBox map with all Level enum possibilities + _includeBox = {}; + for (var l in Level.values) { + _includeBox[l] = !noBoxingByDefault; + } + excludeBox.forEach((k, v) => _includeBox[k] = !v); + } + + @override + List log(LogEvent event) { + var messageStr = stringifyMessage(event.message); + + String? stackTraceStr; + if (event.error != null) { + if ((errorMethodCount == null || errorMethodCount! > 0)) { + stackTraceStr = formatStackTrace( + event.stackTrace ?? StackTrace.current, + errorMethodCount, + ); + } + } else if (methodCount == null || methodCount! > 0) { + stackTraceStr = formatStackTrace( + event.stackTrace ?? StackTrace.current, + methodCount, + ); + } + + var errorStr = event.error?.toString(); + + return _formatAndPrint( + event.level, + messageStr, + DateTime.now().toString(), + errorStr, + stackTraceStr, + ); + } + + String? formatStackTrace(StackTrace? stackTrace, int? methodCount) { + List lines = stackTrace + .toString() + .split('\n') + .where( + (line) => + !_discardDeviceStacktraceLine(line) && + !_discardWebStacktraceLine(line) && + !_discardBrowserStacktraceLine(line) && + line.isNotEmpty, + ) + .toList(); + List formatted = []; + + int stackTraceLength = + (methodCount != null ? min(lines.length, methodCount) : lines.length); + for (int count = 0; count < stackTraceLength; count++) { + var line = lines[count]; + if (count < stackTraceBeginIndex) { + continue; + } + formatted.add('#$count ${line.replaceFirst(RegExp(r'#\d+\s+'), '')}'); + } + + if (formatted.isEmpty) { + return null; + } else { + return formatted.join('\n'); + } + } + + bool _isInExcludePaths(String segment) { + for (var element in excludePaths) { + if (segment.startsWith(element)) { + return true; + } + } + return false; + } + + bool _discardDeviceStacktraceLine(String line) { + var match = _deviceStackTraceRegex.matchAsPrefix(line); + if (match == null) { + return false; + } + final segment = match.group(2)!; + if (segment.startsWith('package:logger')) { + return true; + } + return _isInExcludePaths(segment); + } + + bool _discardWebStacktraceLine(String line) { + var match = _webStackTraceRegex.matchAsPrefix(line); + if (match == null) { + return false; + } + final segment = match.group(1)!; + if (segment.startsWith('packages/logger') || + segment.startsWith('dart-sdk/lib')) { + return true; + } + return _isInExcludePaths(segment); + } + + bool _discardBrowserStacktraceLine(String line) { + var match = _browserStackTraceRegex.matchAsPrefix(line); + if (match == null) { + return false; + } + final segment = match.group(1)!; + if (segment.startsWith('package:logger') || segment.startsWith('dart:')) { + return true; + } + return _isInExcludePaths(segment); + } + + // Handles any object that is causing JsonEncoder() problems + Object toEncodableFallback(dynamic object) { + return object.toString(); + } + + String stringifyMessage(dynamic message) { + final finalMessage = message is Function ? message() : message; + if (finalMessage is Map || finalMessage is Iterable) { + var encoder = JsonEncoder.withIndent(' ', toEncodableFallback); + return encoder.convert(finalMessage); + } else { + return finalMessage.toString(); + } + } + + String _getEmoji(Level level) { + // if (printEmojis) { + final String? emoji = defaultLevelEmojis[level]; + if (emoji != null) { + return '$emoji '; + } + // } + return ''; + } + + List _formatAndPrint( + Level level, + String message, + String time, + String? error, + String? stacktrace, + ) { + final hasBorders = methodCount != null && methodCount! > 0; + + List buffer = []; + var verticalLineAtLevel = + (_includeBox[level]!) && hasBorders ? ('$verticalLine ') : ''; + + if (_includeBox[level]! && hasBorders) buffer.add(_topBorder); + + if (error != null) { + for (var line in error.split('\n')) { + if (line.isNotEmpty) { + buffer.add('$verticalLineAtLevel$line'); + } + } + if (_includeBox[level]! && hasBorders) buffer.add(_middleBorder); + } + + if (stacktrace != null) { + for (var line in stacktrace.split('\n')) { + if (line.isNotEmpty) { + buffer.add('$verticalLineAtLevel$line'); + } + } + if (_includeBox[level]! && hasBorders) buffer.add(_middleBorder); + } + + var emoji = _getEmoji(level); + for (var line in message.split('\n')) { + if (line.isNotEmpty) { + buffer.add('$verticalLineAtLevel$time $emoji$line'); + } + } + if (_includeBox[level]! && hasBorders) buffer.add(_bottomBorder); + + return buffer; + } +} diff --git a/packages/reown_core/lib/i_core_impl.dart b/packages/reown_core/lib/i_core_impl.dart index 599781d..1feb1e7 100644 --- a/packages/reown_core/lib/i_core_impl.dart +++ b/packages/reown_core/lib/i_core_impl.dart @@ -35,10 +35,10 @@ abstract class IReownCore { void confirmOnlineStateOrThrow(); - Future addLinkModeSupportedApp(String universalLink); + Future addLinkModeSupportedApp(String universalLink); List getLinkModeSupportedApps(); - void addLogListener(LogCallback callback); - bool removeLogListener(LogCallback callback); + void addLogListener(Function(String) callback); + bool removeLogListener(Function(String) callback); } diff --git a/packages/reown_core/lib/pairing/pairing.dart b/packages/reown_core/lib/pairing/pairing.dart index 0bb6975..5a81282 100644 --- a/packages/reown_core/lib/pairing/pairing.dart +++ b/packages/reown_core/lib/pairing/pairing.dart @@ -195,9 +195,7 @@ class Pairing implements IPairing { 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.relayClient.subscribe(topic: topic); await core.expirer.set(topic, expiry); onPairingCreate.broadcast( @@ -351,6 +349,8 @@ class Pairing implements IPairing { Future disconnect({required String topic}) async { _checkInitialized(); + core.logger.i('[$runtimeType] disconnect $topic'); + await _isValidDisconnect(topic); if (pairings.has(topic)) { // Send the request to delete the pairing, we don't care if it fails @@ -431,12 +431,12 @@ class Pairing implements IPairing { }); pendingRequests[payload['id']] = resp; + core.logger.d( + '[$runtimeType] sendRequest appLink: $appLink, ' + 'id: $id topic: $topic, method: $method, params: $params, ttl: $ttl', + ); 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!, @@ -446,10 +446,6 @@ class Pairing implements IPairing { await ReownCoreUtils.openURL(redirectURL); } } else { - core.logger.t( - 'pairing sendRequest Relay, ' - 'id: $id topic: $topic, method: $method, params: $params, ttl: $ttl', - ); // RpcOptions opts = MethodConstants.RPC_OPTS[method]!['req']!; RpcOptions opts = MethodConstants.RPC_OPTS[method]!['req']!; if (ttl != null) { @@ -505,23 +501,19 @@ class Pairing implements IPairing { return; } + core.logger.d( + '[$runtimeType] sendRequest appLink: $appLink, ' + 'id: $id topic: $topic, method: $method, result: $result', + ); 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, @@ -556,16 +548,16 @@ class Pairing implements IPairing { return; } + core.logger.d( + '[$runtimeType] sendRequest appLink: $appLink, ' + 'id: $id topic: $topic, method: $method, error: $error', + ); 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; @@ -573,10 +565,6 @@ class Pairing implements IPairing { 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, @@ -605,6 +593,7 @@ class Pairing implements IPairing { } Future _deletePairing(String topic, bool expirerHasDeleted) async { + core.logger.d('[$runtimeType] _deletePairing $topic, $expirerHasDeleted'); await core.relayClient.unsubscribe(topic: topic); await pairings.delete(topic); await core.crypto.deleteSymKey(topic); @@ -614,6 +603,7 @@ class Pairing implements IPairing { } Future _cleanup() async { + core.logger.d('[$runtimeType] _cleanup'); final List expiredPairings = getPairings() .where( (PairingInfo info) => ReownCoreUtils.isExpired(info.expiry), @@ -688,8 +678,12 @@ class Pairing implements IPairing { } // If we have a reciever public key for the topic, use it - ReceiverPublicKey? receiverPublicKey = - topicToReceiverPublicKey.get(event.topic); + ReceiverPublicKey? receiverPublicKey = topicToReceiverPublicKey.get( + event.topic, + ); + core.logger.d( + '[$runtimeType] _onMessageEvent, receiverPublicKey: $receiverPublicKey', + ); // If there was a public key, delete it. One use. if (receiverPublicKey != null) { await topicToReceiverPublicKey.delete(event.topic); @@ -704,12 +698,15 @@ class Pairing implements IPairing { ), ); + core.logger.d( + '[$runtimeType] _onMessageEvent, payloadString: $payloadString', + ); + 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')) { @@ -780,7 +777,9 @@ class Pairing implements IPairing { JsonRpcRequest request, [ _, ]) async { - // print('delete'); + core.logger.d( + '[$runtimeType] _onPairingDeleteRequest $topic, ${request.toJson()}', + ); final int id = request.id; try { await _isValidDisconnect(topic); @@ -846,6 +845,7 @@ class Pairing implements IPairing { if (event == null) { return; } + core.logger.d('[$runtimeType] _onExpired, ${event.toString()}'); if (pairings.has(event.target)) { // Clean up the pairing @@ -877,7 +877,9 @@ class Pairing implements IPairing { required String topic, required String envelope, }) async { - core.logger.i('[$runtimeType] dispatchEnvelope $topic, $envelope'); + core.logger.d( + '[$runtimeType] dispatchEnvelope, topic: $topic, envelope: $envelope', + ); final message = Uri.decodeComponent(envelope); await core.relayClient.handleLinkModeMessage(topic, message); diff --git a/packages/reown_core/lib/relay_client/relay_client.dart b/packages/reown_core/lib/relay_client/relay_client.dart index 63f9f7e..667ec0d 100644 --- a/packages/reown_core/lib/relay_client/relay_client.dart +++ b/packages/reown_core/lib/relay_client/relay_client.dart @@ -114,6 +114,8 @@ class RelayClient implements IRelayClient { }) async { _checkInitialized(); + core.logger.i('[$runtimeType] publish, $topic, $message'); + Map data = { 'message': message, 'ttl': ttl, @@ -128,8 +130,8 @@ class RelayClient implements IRelayClient { data, JsonRpcUtils.payloadId(entropy: 6), ); - } catch (e) { - // print(e); + } catch (e, s) { + core.logger.e('[$runtimeType], publish: $e', stackTrace: s); onRelayClientError.broadcast(ErrorEvent(e)); } } @@ -138,6 +140,8 @@ class RelayClient implements IRelayClient { Future subscribe({required String topic}) async { _checkInitialized(); + core.logger.i('[$runtimeType] subscribe, $topic'); + pendingSubscriptions[topic] = _onSubscribe(topic); return await pendingSubscriptions[topic]; @@ -147,6 +151,8 @@ class RelayClient implements IRelayClient { Future unsubscribe({required String topic}) async { _checkInitialized(); + core.logger.i('[$runtimeType] unsubscribe, $topic'); + String id = topicMap.get(topic) ?? ''; try { @@ -158,7 +164,8 @@ class RelayClient implements IRelayClient { }, JsonRpcUtils.payloadId(entropy: 6), ); - } catch (e) { + } catch (e, s) { + core.logger.e('[$runtimeType], unsubscribe: $e', stackTrace: s); onRelayClientError.broadcast(ErrorEvent(e)); } @@ -191,14 +198,15 @@ class RelayClient implements IRelayClient { /// PRIVATE FUNCTIONS /// Future _connect({String? relayUrl}) async { - core.logger - .t('[$runtimeType]: _connect $relayUrl, isConnected: $isConnected'); + core.logger.d( + '[$runtimeType]: _connect $relayUrl, isConnected: $isConnected', + ); if (isConnected) { return; } core.relayUrl = relayUrl ?? core.relayUrl; - core.logger.d('[$runtimeType] Connecting to relay url ${core.relayUrl}'); + core.logger.i('[$runtimeType] Connecting to relay url ${core.relayUrl}'); // If we have tried connecting to the relay before, disconnect if (_active) { @@ -212,20 +220,20 @@ class RelayClient implements IRelayClient { _connecting = false; _subscribeToHeartbeat(); // - } on TimeoutException catch (e) { - core.logger.d('[$runtimeType]: Connect timeout: $e'); + } on TimeoutException catch (e, s) { + core.logger.e('[$runtimeType], _connect timeout: $e', stackTrace: s); onRelayClientError.broadcast(ErrorEvent('Connection to relay timeout')); _connecting = false; _connect(); - } catch (e) { - core.logger.d('[$runtimeType]: Connect error: $e'); + } catch (e, s) { + core.logger.e('[$runtimeType], _connect error: $e', stackTrace: s); onRelayClientError.broadcast(ErrorEvent(e)); _connecting = false; } } Future _disconnect() async { - core.logger.t('[$runtimeType]: _disconnecting from relay'); + core.logger.d('[$runtimeType]: _disconnecting from relay'); _active = false; final bool shouldBroadcastDisonnect = isConnected; @@ -244,12 +252,12 @@ class RelayClient implements IRelayClient { _connecting = true; _active = true; final auth = await core.crypto.signJWT(core.relayUrl); - core.logger.t('[$runtimeType]: Signed JWT: $auth'); + core.logger.d('[$runtimeType]: Signed JWT: $auth'); final url = ReownCoreUtils.formatRelayRpcUrl( protocol: ReownConstants.CORE_PROTOCOL, version: ReownConstants.CORE_VERSION, - relayUrl: core.relayUrl, sdkVersion: ReownConstants.SDK_VERSION, + relayUrl: core.relayUrl, auth: auth, projectId: core.projectId, packageName: (await ReownCoreUtils.getPackageName()), @@ -260,9 +268,9 @@ class RelayClient implements IRelayClient { jsonRPC = null; } - core.logger.t('[$runtimeType]: Initializing WebSocket with $url'); + core.logger.d('[$runtimeType]: Initializing WebSocket with $url'); await socketHandler.setup(url: url); - await socketHandler.connect().timeout(Duration(seconds: 5)); + await socketHandler.connect(); jsonRPC = Peer(socketHandler.channel!); @@ -300,18 +308,19 @@ class RelayClient implements IRelayClient { ); onRelayClientConnect.broadcast(); - core.logger.d('[$runtimeType]: Connected to relay ${core.relayUrl}'); + core.logger.i('[$runtimeType]: Connected to relay ${core.relayUrl}'); } Future _handleRelayClose(int? code, String? reason) async { if (_handledClose) { - core.logger.i('[$runtimeType]: Relay close already handled'); + core.logger.d('[$runtimeType]: Relay close already handled'); return; } _handledClose = true; - core.logger.i( - '[$runtimeType]: Handling relay close, code: $code, reason: $reason'); + core.logger.d( + '[$runtimeType]: Handling relay close, code: $code, reason: $reason', + ); // If the relay isn't active (Disconnected manually), don't do anything if (!_active) { return; @@ -333,6 +342,7 @@ class RelayClient implements IRelayClient { message: errorReason, )), ); + core.logger.e('[$runtimeType], _handleRelayClose: $core, $errorReason'); } } } @@ -362,7 +372,7 @@ class RelayClient implements IRelayClient { // 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'); + core.logger.d('[$runtimeType]: handleLinkModeMessage: $topic, $message'); // if client calls dispatchEnvelope with the same message more than once we do nothing. final recorded = messageTracker.messageIsRecorded(topic, message); @@ -383,10 +393,10 @@ class RelayClient implements IRelayClient { /// JSON RPC MESSAGE HANDLERS Future handlePublish(String topic, String message) async { - core.logger.t('[$runtimeType]: Handling Publish Message: $topic, $message'); + core.logger.d('[$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'); + core.logger.d('[$runtimeType]: Ignoring Message: $topic, $message'); return false; } @@ -416,7 +426,7 @@ class RelayClient implements IRelayClient { } void _handleUnsubscribe(Parameters params) { - core.logger.i('[$runtimeType]: handle unsubscribe $params'); + core.logger.d('[$runtimeType]: handle unsubscribe $params'); } /// MESSAGE HANDLING @@ -464,8 +474,11 @@ class RelayClient implements IRelayClient { {'topic': topic}, JsonRpcUtils.payloadId(entropy: 6), ); - } catch (e) { - core.logger.w('RelayClient, onSubscribe error. Topic: $topic, Error: $e'); + } catch (e, s) { + core.logger.e( + '[$runtimeType], _onSubscribe: Topic, $topic, Error: $e', + stackTrace: s, + ); onRelayClientError.broadcast(ErrorEvent(e)); } diff --git a/packages/reown_core/lib/store/generic_store.dart b/packages/reown_core/lib/store/generic_store.dart index e9df241..c46c911 100644 --- a/packages/reown_core/lib/store/generic_store.dart +++ b/packages/reown_core/lib/store/generic_store.dart @@ -158,6 +158,7 @@ class GenericStore implements IGenericStore { } catch (e) { // print('Error restoring $storageKey: $e'); await storage.delete(storedVersion); + rethrow; } } } diff --git a/packages/reown_core/lib/store/link_mode_store.dart b/packages/reown_core/lib/store/link_mode_store.dart index 74299e4..00f7e36 100644 --- a/packages/reown_core/lib/store/link_mode_store.dart +++ b/packages/reown_core/lib/store/link_mode_store.dart @@ -1,9 +1,9 @@ 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); +abstract class ILinkModeStore implements IGenericStore> { + Future update(String url); + Future remove(String url); List getList(); } @@ -17,17 +17,40 @@ class LinkModeStore extends GenericStore> required super.fromJson, }); - static const _key = ReownConstants.REOWN_LINK_MODE_APPS; + static const _key = 'linkModeStore'; @override - Future update(String url) async { + 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}); + try { + final currentList = getList(); + if (!currentList.contains(url)) { + final newList = List.from([...currentList, url]); + await storage.set(_key, {_key: newList}); + return true; + } + } catch (_) { + // debugPrint('[$runtimeType] update, $_key, $e'); + } + return false; + } + + @override + Future remove(String url) async { + checkInitialized(); + + try { + final currentList = getList(); + if (currentList.contains(url)) { + final newList = List.from(currentList..remove(url)); + await storage.set(_key, {_key: newList}); + return true; + } + } catch (_) { + // debugPrint('[$runtimeType] remove, $_key, $e'); } + return false; } @override diff --git a/packages/reown_core/lib/utils/constants.dart b/packages/reown_core/lib/utils/constants.dart index 3765ad1..bb0b580 100644 --- a/packages/reown_core/lib/utils/constants.dart +++ b/packages/reown_core/lib/utils/constants.dart @@ -24,8 +24,6 @@ class ReownConstants { 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 { diff --git a/packages/reown_core/lib/utils/log_level.dart b/packages/reown_core/lib/utils/log_level.dart index 9be0076..c3bd659 100644 --- a/packages/reown_core/lib/utils/log_level.dart +++ b/packages/reown_core/lib/utils/log_level.dart @@ -1,28 +1,22 @@ import 'package:logger/logger.dart'; enum LogLevel { - verbose, + all, debug, info, - warning, error, - wtf, nothing; Level toLevel() { switch (this) { - case LogLevel.verbose: - return Level.trace; + case LogLevel.all: + return Level.all; 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 index 6704572..d75d25f 100644 --- a/packages/reown_core/lib/utils/method_constants.dart +++ b/packages/reown_core/lib/utils/method_constants.dart @@ -15,6 +15,7 @@ class MethodConstants { static const WC_SESSION_DELETE = 'wc_sessionDelete'; static const WC_SESSION_PING = 'wc_sessionPing'; + // Deprecated method but still supported for retrocompatibility // static const WC_AUTH_REQUEST = 'wc_authRequest'; static const WC_SESSION_AUTHENTICATE = 'wc_sessionAuthenticate'; diff --git a/packages/reown_core/lib/utils/utils.dart b/packages/reown_core/lib/utils/utils.dart index 5988c49..79fdf44 100644 --- a/packages/reown_core/lib/utils/utils.dart +++ b/packages/reown_core/lib/utils/utils.dart @@ -248,12 +248,16 @@ class ReownCoreUtils { return '$universalLink?wc_ev=$encodedEnvelope&topic=$topic'; } - static Future openURL(String url) async { + static Future canOpenUrl(String url) async { + return await canLaunchUrlString(url); + } + + static Future openURL( + String url, [ + LaunchMode mode = LaunchMode.externalApplication, + ]) async { try { - final success = await launchUrlString( - url, - mode: LaunchMode.externalApplication, - ); + final success = await launchUrlString(url, mode: mode); if (!success) { throw ReownCoreError(code: 3000, message: 'Can not open $url'); } diff --git a/packages/reown_core/lib/verify/verify.dart b/packages/reown_core/lib/verify/verify.dart index 267815a..e9e5848 100644 --- a/packages/reown_core/lib/verify/verify.dart +++ b/packages/reown_core/lib/verify/verify.dart @@ -40,7 +40,9 @@ class Verify implements IVerify { } return AttestationResponse.fromJson(jsonDecode(response.body)); } catch (e) { - _core.logger.d('[$runtimeType] resolve $e'); + if (e is! AttestationNotFound) { + _core.logger.e('[$runtimeType] resolve $e'); + } rethrow; } } @@ -68,7 +70,7 @@ class Verify implements IVerify { String url = verifyUrl ?? ReownConstants.VERIFY_SERVER; if (!ReownConstants.TRUSTED_VERIFY_URLS.contains(url)) { - _core.logger.i( + _core.logger.d( '[$runtimeType] verifyUrl $url not included in trusted list, ' 'assigning default: ${ReownConstants.VERIFY_SERVER}', ); diff --git a/packages/reown_core/lib/version.dart b/packages/reown_core/lib/version.dart index 526cd53..00bb7c4 100644 --- a/packages/reown_core/lib/version.dart +++ b/packages/reown_core/lib/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '1.0.0'; +const packageVersion = '1.0.4'; diff --git a/packages/reown_core/pubspec.yaml b/packages/reown_core/pubspec.yaml index 944ea27..bcdc890 100644 --- a/packages/reown_core/pubspec.yaml +++ b/packages/reown_core/pubspec.yaml @@ -1,6 +1,6 @@ name: reown_core description: "Reown is the onchain UX platform that provides toolkits built on top of the WalletConnect Network" -version: 1.0.0 +version: 1.0.4 homepage: https://github.com/reown-com/reown_flutter repository: https://github.com/reown-com/reown_flutter/tree/master/packages/reown_core @@ -20,13 +20,13 @@ dependencies: http: ^1.2.0 json_annotation: ^4.8.1 logger: ^2.2.0 - package_info_plus: ^7.0.0 + package_info_plus: ^8.0.2 pointycastle: ^3.9.1 shared_preferences: ^2.2.2 - stack_trace: ^1.10.0 + stack_trace: ^1.11.1 stream_channel: ^2.1.0 - url_launcher: ^6.3.0 - web_socket_channel: ^2.4.4 + url_launcher: ^6.3.1 + web_socket_channel: ^2.4.5 x25519: ^0.1.1 dev_dependencies: diff --git a/packages/reown_core/test/shared/shared_test_utils.mocks.dart b/packages/reown_core/test/shared/shared_test_utils.mocks.dart index e422282..941fe2c 100644 --- a/packages/reown_core/test/shared/shared_test_utils.mocks.dart +++ b/packages/reown_core/test/shared/shared_test_utils.mocks.dart @@ -1454,7 +1454,7 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ) as _i19.Logger); @override - void addLogListener(_i19.LogCallback? callback) => super.noSuchMethod( + void addLogListener(dynamic Function(String)? callback) => super.noSuchMethod( Invocation.method( #addLogListener, [callback], @@ -1463,7 +1463,8 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ); @override - bool removeLogListener(_i19.LogCallback? callback) => (super.noSuchMethod( + bool removeLogListener(dynamic Function(String)? callback) => + (super.noSuchMethod( Invocation.method( #removeLogListener, [callback], @@ -1482,15 +1483,14 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ) as _i23.Future); @override - _i23.Future addLinkModeSupportedApp(String? universalLink) => + _i23.Future addLinkModeSupportedApp(String? universalLink) => (super.noSuchMethod( Invocation.method( #addLinkModeSupportedApp, [universalLink], ), - returnValue: _i23.Future.value(), - returnValueForMissingStub: _i23.Future.value(), - ) as _i23.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override List getLinkModeSupportedApps() => (super.noSuchMethod( diff --git a/packages/reown_sign/CHANGELOG.md b/packages/reown_sign/CHANGELOG.md index 227f443..b92af00 100644 --- a/packages/reown_sign/CHANGELOG.md +++ b/packages/reown_sign/CHANGELOG.md @@ -1,3 +1,19 @@ +## 1.0.4 + +- Minor change + +## 1.0.3 + +- Core Update + +## 1.0.2 + +- Minor change + +## 1.0.1 + +- Minor change + ## 1.0.0 Initial release. diff --git a/packages/reown_sign/lib/reown_sign.dart b/packages/reown_sign/lib/reown_sign.dart index 48431de..66b3e3c 100644 --- a/packages/reown_sign/lib/reown_sign.dart +++ b/packages/reown_sign/lib/reown_sign.dart @@ -28,4 +28,3 @@ 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_engine.dart b/packages/reown_sign/lib/sign_engine.dart index a61e01a..0435a38 100644 --- a/packages/reown_sign/lib/sign_engine.dart +++ b/packages/reown_sign/lib/sign_engine.dart @@ -1009,6 +1009,12 @@ class ReownSign implements IReownSign { function: _onSessionAuthenticateRequest, type: ProtocolType.sign, ); + // Deprecated method but still supported for retrocompatibility + // core.pairing.register( + // method: MethodConstants.WC_AUTH_REQUEST, + // function: _onAuthRequest, + // type: ProtocolType.sign, + // ); } bool _shouldIgnoreSessionPropose(String topic) { @@ -1026,13 +1032,13 @@ class ReownSign implements IReownSign { _, ]) async { if (_shouldIgnoreSessionPropose(topic)) { - core.logger.t( + core.logger.i( 'Session Propose ignored. Session Authenticate will be used instead', ); return; } try { - core.logger.t( + core.logger.d( '_onSessionProposeRequest, topic: $topic, payload: $payload', ); final proposeRequest = WcSessionProposeRequest.fromJson(payload.params); @@ -1064,7 +1070,7 @@ class ReownSign implements IReownSign { ); } on ReownSignError catch (err) { // If they aren't, send an error - core.logger.t( + core.logger.e( '_onSessionProposeRequest ReownSignError: $err', ); final rpcOpts = MethodConstants.RPC_OPTS[payload.method]; @@ -1381,7 +1387,8 @@ class ReownSign implements IReownSign { final appLink = (session.peer.metadata.redirect?.universal ?? ''); if (session.transportType.isLinkMode && appLink.isNotEmpty) { // save app as supported for link mode - core.addLinkModeSupportedApp(appLink); + final success = await core.addLinkModeSupportedApp(appLink); + core.logger.d('[$runtimeType] addLinkModeSupportedApp, $success'); } final methodKey = _getRegisterKey( @@ -1531,6 +1538,7 @@ class ReownSign implements IReownSign { } Future _onPairingDelete(PairingEvent? event) async { + core.logger.i('[$runtimeType] onPairingDelete ${event.toString()}'); // Delete all the sessions associated with the pairing if (event == null) { return; @@ -1897,6 +1905,8 @@ class ReownSign implements IReownSign { } catch (e, s) { if (e is! AttestationNotFound) { core.logger.e('[$runtimeType] verify error', error: e, stackTrace: s); + } else { + core.logger.d('[$runtimeType] attestation not found'); } return VerifyContext( origin: metadataUri?.origin ?? proposerMetada.url, @@ -2046,10 +2056,17 @@ class ReownSign implements IReownSign { final selfLinkMode = metadata.redirect?.linkMode == true; final selfLink = (metadata.redirect?.universal ?? ''); final walletUniversalLink = (walletLink ?? ''); + final linkModeApps = core.getLinkModeSupportedApps(); + final containsLink = linkModeApps.contains(walletLink); + core.logger.d( + '[$runtimeType] _isLinkModeAuthenticate, selfLinkMode: $selfLinkMode, ' + 'selfLink: $selfLink, walletUniversalLink: $walletUniversalLink ' + 'linkModeApps: $linkModeApps, containsLink: $containsLink', + ); return selfLinkMode && selfLink.isNotEmpty && walletUniversalLink.isNotEmpty && - core.getLinkModeSupportedApps().contains(walletLink); + containsLink; } @override @@ -2416,14 +2433,13 @@ class ReownSign implements IReownSign { 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, - ); + final success = await core.addLinkModeSupportedApp(walletLink); + if (success) { + await sessions.update( + sessionTopic, + transportType: TransportType.linkMode, + ); + } } } } @@ -2618,12 +2634,24 @@ class ReownSign implements IReownSign { await _deleteProposal(id); } + // Deprecated method but still supported for retrocompatibility + // void _onAuthRequest(String topic, JsonRpcRequest payload, [_]) async { + // await core.pairing.sendError( + // payload.id, + // topic, + // payload.method, + // JsonRpcError.invalidRequest( + // '${payload.method} is deprecated, use wc_sessionAuthenticate', + // ), + // ); + // } + void _onSessionAuthenticateRequest( String topic, JsonRpcRequest payload, [ TransportType transportType = TransportType.relay, ]) async { - core.logger.t( + core.logger.d( '_onSessionAuthenticateRequest, topic: $topic, payload: $payload', ); @@ -2640,22 +2668,20 @@ class ReownSign implements IReownSign { ); await pairings.set(topic, pairingInfo); - core.logger.t( + core.logger.d( '[$runtimeType] _onSessionAuthenticateRequest pairingInfo $pairingInfo', ); } - final sessionAuthRequest = WcSessionAuthRequestParams.fromJson( - payload.params, - ); + final saRequest = WcSessionAuthRequestParams.fromJson(payload.params); try { final cacaoPayload = CacaoRequestPayload.fromSessionAuthPayload( - sessionAuthRequest.authPayload, + saRequest.authPayload, ); final verifyContext = await _getVerifyContext( payload, - sessionAuthRequest.requester.metadata, + saRequest.requester.metadata, transportType, ); @@ -2664,33 +2690,32 @@ class ReownSign implements IReownSign { PendingSessionAuthRequest( id: payload.id, pairingTopic: topic, - requester: sessionAuthRequest.requester, + requester: saRequest.requester, authPayload: cacaoPayload, - expiryTimestamp: sessionAuthRequest.expiryTimestamp, + expiryTimestamp: saRequest.expiryTimestamp, verifyContext: verifyContext, transportType: transportType, ), ); - final appLink = - (sessionAuthRequest.requester.metadata.redirect?.universal ?? ''); + final appLink = (saRequest.requester.metadata.redirect?.universal ?? ''); if (transportType.isLinkMode && appLink.isNotEmpty) { // save app as supported for link mode - core.addLinkModeSupportedApp(appLink); + await core.addLinkModeSupportedApp(appLink); } onSessionAuthRequest.broadcast( SessionAuthRequest( id: payload.id, topic: topic, - requester: sessionAuthRequest.requester, - authPayload: sessionAuthRequest.authPayload, + requester: saRequest.requester, + authPayload: saRequest.authPayload, verifyContext: verifyContext, transportType: transportType, ), ); } on ReownSignError catch (err) { - final receiverPublicKey = sessionAuthRequest.requester.publicKey; + final receiverPublicKey = saRequest.requester.publicKey; final senderPublicKey = await core.crypto.generateKeyPair(); final encodeOpts = EncodeOptions( @@ -2714,6 +2739,7 @@ class ReownSign implements IReownSign { Future dispatchEnvelope(String url) async { final topic = ReownCoreUtils.getSearchParamFromURL(url, 'topic'); final envelope = ReownCoreUtils.getSearchParamFromURL(url, 'wc_ev'); + core.logger.d('[$runtimeType] dispatchEnvelope $url'); if (envelope.isEmpty) { throw ReownSignError(code: 0, message: 'Envelope not found'); @@ -2724,7 +2750,7 @@ class ReownSign implements IReownSign { final session = sessions.get(topic); if (session != null) { - core.logger.i('[$runtimeType] sessions.update $topic to linkMode'); + core.logger.d('[$runtimeType] sessions.update $topic to linkMode'); await sessions.update( session.topic, transportType: TransportType.linkMode, @@ -2778,6 +2804,9 @@ class ReownSign implements IReownSign { final session = sessions.get(topic)!; final isLinkMode = session.transportType.isLinkMode; final isEnabled = _isLinkModeEnabled(session.peer.metadata); + core.logger.d( + '[$runtimeType] callRedirect, isLinkMode: $isLinkMode, isEnabled: $isEnabled', + ); if (isLinkMode && isEnabled) { // linkMode redirection is already handled in the requests return false; diff --git a/packages/reown_sign/lib/utils/auth_api_validators.dart b/packages/reown_sign/lib/utils/auth_api_validators.dart index 7e56e2f..ade6ff2 100644 --- a/packages/reown_sign/lib/utils/auth_api_validators.dart +++ b/packages/reown_sign/lib/utils/auth_api_validators.dart @@ -110,6 +110,14 @@ class AuthApiValidators { ).toSignError(); } + final isValidChainId = NamespaceUtils.isValidChainId(params.chains.first); + if (!isValidChainId) { + throw Errors.getInternalError( + Errors.NON_CONFORMING_NAMESPACES, + context: 'authenticate() chainId should conform to "CAIP-2" format', + ).toSignError(); + } + final namespace = NamespaceUtils.getNamespaceFromChain(params.chains.first); if (namespace != 'eip155') { throw Errors.getInternalError( diff --git a/packages/reown_sign/lib/version.dart b/packages/reown_sign/lib/version.dart index 526cd53..00bb7c4 100644 --- a/packages/reown_sign/lib/version.dart +++ b/packages/reown_sign/lib/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '1.0.0'; +const packageVersion = '1.0.4'; diff --git a/packages/reown_sign/pubspec.yaml b/packages/reown_sign/pubspec.yaml index 9230147..48c7dca 100644 --- a/packages/reown_sign/pubspec.yaml +++ b/packages/reown_sign/pubspec.yaml @@ -1,6 +1,6 @@ name: reown_sign description: "Reown is the onchain UX platform that provides toolkits built on top of the WalletConnect Network" -version: 1.0.0 +version: 1.0.4 homepage: https://github.com/reown-com/reown_flutter repository: https://github.com/reown-com/reown_flutter/tree/master/packages/reown_sign @@ -14,9 +14,10 @@ dependencies: sdk: flutter freezed_annotation: ^2.2.0 http: ^1.2.0 - logger: ^2.2.0 pointycastle: ^3.9.1 - reown_core: ^1.0.0 + reown_core: ^1.0.4 + # reown_core: + # path: ../reown_core/ web3dart: ^2.7.3 dev_dependencies: @@ -28,8 +29,9 @@ dev_dependencies: sdk: flutter freezed: ^2.4.5 json_serializable: ^6.7.0 + logger: ^2.2.0 mockito: ^5.4.3 - package_info_plus: ^7.0.0 + package_info_plus: ^8.0.2 platforms: android: diff --git a/packages/reown_sign/test/shared/shared_test_utils.mocks.dart b/packages/reown_sign/test/shared/shared_test_utils.mocks.dart index 0a44348..234edd8 100644 --- a/packages/reown_sign/test/shared/shared_test_utils.mocks.dart +++ b/packages/reown_sign/test/shared/shared_test_utils.mocks.dart @@ -1454,7 +1454,7 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ) as _i19.Logger); @override - void addLogListener(_i19.LogCallback? callback) => super.noSuchMethod( + void addLogListener(dynamic Function(String)? callback) => super.noSuchMethod( Invocation.method( #addLogListener, [callback], @@ -1463,7 +1463,8 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ); @override - bool removeLogListener(_i19.LogCallback? callback) => (super.noSuchMethod( + bool removeLogListener(dynamic Function(String)? callback) => + (super.noSuchMethod( Invocation.method( #removeLogListener, [callback], @@ -1482,15 +1483,14 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ) as _i23.Future); @override - _i23.Future addLinkModeSupportedApp(String? universalLink) => + _i23.Future addLinkModeSupportedApp(String? universalLink) => (super.noSuchMethod( Invocation.method( #addLinkModeSupportedApp, [universalLink], ), - returnValue: _i23.Future.value(), - returnValueForMissingStub: _i23.Future.value(), - ) as _i23.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override List getLinkModeSupportedApps() => (super.noSuchMethod( diff --git a/packages/reown_walletkit/CHANGELOG.md b/packages/reown_walletkit/CHANGELOG.md index 38b39c0..4f0694e 100644 --- a/packages/reown_walletkit/CHANGELOG.md +++ b/packages/reown_walletkit/CHANGELOG.md @@ -1,7 +1,11 @@ +## 1.0.3 + +- Minor dependency update + ## 1.0.1 - Update readme ## 1.0.0 -Initial release. +- Initial release diff --git a/packages/reown_walletkit/dart_dependency_validator.yaml b/packages/reown_walletkit/dart_dependency_validator.yaml new file mode 100644 index 0000000..46c10a3 --- /dev/null +++ b/packages/reown_walletkit/dart_dependency_validator.yaml @@ -0,0 +1,10 @@ +# dart_dependency_validator.yaml + +# Set true if you allow pinned packages in your project. +# allow_pins: true +# Exclude one or more paths from being scanned. Supports glob syntax. +exclude: + - 'example/**' # Glob's are supported +# Ignore one or more packages. +# ignore: +# - analyzer \ No newline at end of file diff --git a/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj index 84a1ca5..a3f0416 100644 --- a/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,12 +9,12 @@ /* 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 */; }; - 2210E862B6357BBBE637E65E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B30D38A088691AB65C417E1 /* Pods_Runner.framework */; }; 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 */; }; + 9BE64AD39D412E74E7A41E07 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D653E099EE390AEBCBF8FC57 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -33,15 +33,14 @@ /* 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 = ""; }; + 09EE3E8E42FD659E0B4D7BB1 /* 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 = ""; }; + 1C3C261AB4BD043406C866FF /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 4344D4580FDF616D112FD695 /* 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 = ""; }; - 6C9EDDA5952C970914CCB47F /* 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 = ""; }; 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 = ""; }; - 8B30D38A088691AB65C417E1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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; }; @@ -49,10 +48,11 @@ 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 = ""; }; - 9AA202A4F9D07A1A38223F79 /* 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 = ""; }; - C375F5AA7036C24F3F188FA4 /* 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 = ""; }; - FB97E2D3A1A3B24E657AF4BE /* 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 = ""; }; - FCEC095E30F1D60B46295232 /* 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 = ""; }; + 9AFAE38CBB7C347E0F6EA228 /* 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 = ""; }; + C5C38FC2FB137A8DB7737C2D /* 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 = ""; }; + CE1FDD150CAC5C2D25BBCBB4 /* 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 = ""; }; + D653E099EE390AEBCBF8FC57 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DC2E0D1AB6C7D5CA0B096C7D /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,13 +60,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2210E862B6357BBBE637E65E /* Pods_Runner.framework in Frameworks */, + 9BE64AD39D412E74E7A41E07 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 79F7800211283132B8A40025 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D653E099EE390AEBCBF8FC57 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -85,7 +93,7 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, C4281BF2C021B5570082F0DC /* Pods */, - B0BA46C205DD193AD1539395 /* Frameworks */, + 79F7800211283132B8A40025 /* Frameworks */, ); sourceTree = ""; }; @@ -114,23 +122,15 @@ path = Runner; sourceTree = ""; }; - B0BA46C205DD193AD1539395 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 8B30D38A088691AB65C417E1 /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; C4281BF2C021B5570082F0DC /* Pods */ = { isa = PBXGroup; children = ( - 6C9EDDA5952C970914CCB47F /* Pods-Runner.debug-production.xcconfig */, - 9AA202A4F9D07A1A38223F79 /* Pods-Runner.debug-internal.xcconfig */, - 4344D4580FDF616D112FD695 /* Pods-Runner.release-production.xcconfig */, - C375F5AA7036C24F3F188FA4 /* Pods-Runner.release-internal.xcconfig */, - FB97E2D3A1A3B24E657AF4BE /* Pods-Runner.profile-production.xcconfig */, - FCEC095E30F1D60B46295232 /* Pods-Runner.profile-internal.xcconfig */, + 9AFAE38CBB7C347E0F6EA228 /* Pods-Runner.debug-production.xcconfig */, + 09EE3E8E42FD659E0B4D7BB1 /* Pods-Runner.debug-internal.xcconfig */, + CE1FDD150CAC5C2D25BBCBB4 /* Pods-Runner.release-production.xcconfig */, + 1C3C261AB4BD043406C866FF /* Pods-Runner.release-internal.xcconfig */, + DC2E0D1AB6C7D5CA0B096C7D /* Pods-Runner.profile-production.xcconfig */, + C5C38FC2FB137A8DB7737C2D /* Pods-Runner.profile-internal.xcconfig */, ); path = Pods; sourceTree = ""; @@ -142,14 +142,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 253EC1671D118E0514180759 /* [CP] Check Pods Manifest.lock */, + D1A8417ECB78F1B2893E1798 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 33F8A66AB48275C47BFAE6E6 /* [CP] Embed Pods Frameworks */, + 6493043A7933D573920485DE /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -209,29 +209,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 253EC1671D118E0514180759 /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); + name = "Thin Binary"; 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; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 33F8A66AB48275C47BFAE6E6 /* [CP] Embed Pods Frameworks */ = { + 6493043A7933D573920485DE /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -248,36 +242,42 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); - name = "Thin Binary"; + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + D1A8417ECB78F1B2893E1798 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Run Script"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + 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 */ diff --git a/packages/reown_walletkit/example/ios/fastlane/Fastfile b/packages/reown_walletkit/example/ios/fastlane/Fastfile index 9415b0d..0877531 100644 --- a/packages/reown_walletkit/example/ios/fastlane/Fastfile +++ b/packages/reown_walletkit/example/ios/fastlane/Fastfile @@ -114,6 +114,8 @@ platform :ios do distribute_external: true, notify_external_testers: true, skip_waiting_for_build_processing: false, + beta_app_feedback_email: "alfredo@reown.com", + beta_app_description: "WalletKit sample app", groups: ["External Testers"] ) 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 index 41c724b..45a00f1 100644 --- 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 @@ -46,7 +46,9 @@ class BottomSheetListenerState extends State { Future.delayed(Duration(seconds: item.closeAfter), () { try { if (!mounted) return; - Navigator.pop(context); + if (Navigator.canPop(context)) { + Navigator.pop(context); + } } catch (e) { debugPrint('[$runtimeType] close $e'); } @@ -78,7 +80,11 @@ class BottomSheetListenerState extends State { IconButton( padding: const EdgeInsets.all(0.0), visualDensity: VisualDensity.compact, - onPressed: () => Navigator.pop(context), + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + }, icon: const Icon(Icons.close_sharp), ), ], 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 index a3187c2..cf9f575 100644 --- a/packages/reown_walletkit/example/lib/dependencies/chain_services/evm_service.dart +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/evm_service.dart @@ -476,6 +476,7 @@ class EVMService { topic, session!.peer.metadata.redirect, response.error?.message, + response.error == null, ); } on ReownSignError catch (error) { MethodsUtils.handleRedirect( @@ -589,4 +590,58 @@ class EVMService { return false; } } + + Future getBalance({required String address}) async { + final uri = Uri.parse('https://rpc.walletconnect.org/v1'); + final queryParams = { + 'projectId': _walletKit.core.projectId, + 'chainId': chainSupported.chainId + }; + final response = await http.post( + uri.replace(queryParameters: queryParams), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'id': 1, + 'jsonrpc': '2.0', + 'method': 'eth_getBalance', + 'params': [address, 'latest'], + }), + ); + if (response.statusCode == 200 && response.body.isNotEmpty) { + try { + final result = _parseRpcResultAs(response.body); + final amount = EtherAmount.fromBigInt( + EtherUnit.wei, + hexToInt(result), + ); + return amount.getValueInUnit(EtherUnit.ether); + } catch (e) { + throw Exception('Failed to load balance. $e'); + } + } + try { + final errorData = jsonDecode(response.body) as Map; + final reasons = errorData['reasons'] as List; + final reason = reasons.isNotEmpty + ? reasons.first['description'] ?? '' + : response.body; + throw Exception(reason); + } catch (e) { + rethrow; + } + } + + 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; + } else { + throw jsonResponse.error ?? 'Error parsing result'; + } + } catch (e) { + rethrow; + } + } } 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 index f964fca..af238e7 100644 --- a/packages/reown_walletkit/example/lib/dependencies/chain_services/kadena_service.dart +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/kadena_service.dart @@ -112,7 +112,7 @@ class KadenaService { ); try { - final chain = ChainData.kadenaChains.firstWhere( + final chain = ChainsDataList.kadenaChains.firstWhere( (c) => c.chainId == chainSupported.chainId, ); final uri = Uri.parse(chain.rpc.first); 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 index 6b68adf..621592a 100644 --- a/packages/reown_walletkit/example/lib/dependencies/chain_services/solana_service.dart +++ b/packages/reown_walletkit/example/lib/dependencies/chain_services/solana_service.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; @@ -215,6 +216,57 @@ class SolanaService { ); } } + + Future getBalance({required String address}) async { + final uri = Uri.parse('https://rpc.walletconnect.org/v1'); + final queryParams = { + 'projectId': _walletKit.core.projectId, + 'chainId': chainSupported.chainId + }; + final response = await http.post( + uri.replace(queryParameters: queryParams), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'id': 1, + 'jsonrpc': '2.0', + 'method': 'getBalance', + 'params': [address] + }), + ); + if (response.statusCode == 200 && response.body.isNotEmpty) { + try { + final result = _parseRpcResultAs>(response.body); + final value = result['value'] as int; + return value / 1000000000.0; + } catch (e) { + throw Exception('Failed to load balance. $e'); + } + } + try { + final errorData = jsonDecode(response.body) as Map; + final reasons = errorData['reasons'] as List; + final reason = reasons.isNotEmpty + ? reasons.first['description'] ?? '' + : response.body; + throw Exception(reason); + } catch (e) { + rethrow; + } + } + + 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; + } else { + throw jsonResponse.error ?? 'Error parsing result'; + } + } catch (e) { + rethrow; + } + } } extension on String { 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 index 77cabf9..58ecce1 100644 --- 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 @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; @@ -219,6 +220,57 @@ class SolanaService2 { ); } } + + Future getBalance({required String address}) async { + final uri = Uri.parse('https://rpc.walletconnect.org/v1'); + final queryParams = { + 'projectId': _walletKit.core.projectId, + 'chainId': chainSupported.chainId + }; + final response = await http.post( + uri.replace(queryParameters: queryParams), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'id': 1, + 'jsonrpc': '2.0', + 'method': 'getBalance', + 'params': [address], + }), + ); + if (response.statusCode == 200 && response.body.isNotEmpty) { + try { + final result = _parseRpcResultAs>(response.body); + final value = result['value'] as int; + return value / 1000000000.0; + } catch (e) { + throw Exception('Failed to load balance. $e'); + } + } + try { + final errorData = jsonDecode(response.body) as Map; + final reasons = errorData['reasons'] as List; + final reason = reasons.isNotEmpty + ? reasons.first['description'] ?? '' + : response.body; + throw Exception(reason); + } catch (e) { + rethrow; + } + } + + 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; + } else { + throw jsonResponse.error ?? 'Error parsing result'; + } + } catch (e) { + rethrow; + } + } } extension on Map { diff --git a/packages/reown_walletkit/example/lib/dependencies/deep_link_handler.dart b/packages/reown_walletkit/example/lib/dependencies/deep_link_handler.dart index dde014a..7bcddd9 100644 --- a/packages/reown_walletkit/example/lib/dependencies/deep_link_handler.dart +++ b/packages/reown_walletkit/example/lib/dependencies/deep_link_handler.dart @@ -25,7 +25,10 @@ class DeepLinkHandler { static void checkInitialLink() async { if (kIsWeb) return; try { - _methodChannel.invokeMethod('initialLink'); + final initialLink = await _methodChannel.invokeMethod('initialLink'); + if (initialLink != null) { + _onLink(initialLink); + } } catch (e) { debugPrint('[SampleWallet] [DeepLinkHandler] checkInitialLink $e'); } @@ -39,20 +42,19 @@ class DeepLinkHandler { 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())); + static void _onLink(dynamic link) async { + debugPrint('[SampleWallet] _onLink $link'); + try { + return await _walletKit.dispatchEnvelope('$link'); + } catch (e) { + final decodedUri = Uri.parse(Uri.decodeFull('$link')); if (decodedUri.isScheme('wc')) { debugPrint('[SampleWallet] is legacy uri $decodedUri'); waiting.value = true; await _walletKit.pair(uri: decodedUri); } else { final uriParam = ReownCoreUtils.getSearchParamFromURL( - '$decodedUri', + decodedUri.toString(), 'uri', ); if (decodedUri.isScheme(nativeUri.scheme) && uriParam.isNotEmpty) { 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 index 50ee3e7..96c23cf 100644 --- a/packages/reown_walletkit/example/lib/dependencies/key_service/key_service.dart +++ b/packages/reown_walletkit/example/lib/dependencies/key_service/key_service.dart @@ -148,7 +148,7 @@ class KeyService extends IKeyService { final private = EthPrivateKey.fromHex(keyPair.privateKey); final address = private.address.hex; final evmChainKey = ChainKey( - chains: ChainData.eip155Chains.map((e) => e.chainId).toList(), + chains: ChainsDataList.eip155Chains.map((e) => e.chainId).toList(), privateKey: keyPair.privateKey, publicKey: keyPair.publicKey, address: address, @@ -174,7 +174,7 @@ class KeyService extends IKeyService { ChainKey _kadenaChainKey() { return ChainKey( - chains: ChainData.kadenaChains.map((e) => e.chainId).toList(), + chains: ChainsDataList.kadenaChains.map((e) => e.chainId).toList(), privateKey: DartDefines.kadenaSecretKey, publicKey: DartDefines.kadenaAddress, address: DartDefines.kadenaAddress, @@ -183,7 +183,7 @@ class KeyService extends IKeyService { ChainKey _polkadotChainKey() { return ChainKey( - chains: ChainData.polkadotChains.map((e) => e.chainId).toList(), + chains: ChainsDataList.polkadotChains.map((e) => e.chainId).toList(), privateKey: DartDefines.polkadotMnemonic, publicKey: '', address: DartDefines.polkadotAddress, @@ -192,7 +192,7 @@ class KeyService extends IKeyService { ChainKey _solanaChainKey() { return ChainKey( - chains: ChainData.solanaChains.map((e) => e.chainId).toList(), + chains: ChainsDataList.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 index 9cc3831..806be58 100644 --- a/packages/reown_walletkit/example/lib/dependencies/walletkit_service.dart +++ b/packages/reown_walletkit/example/lib/dependencies/walletkit_service.dart @@ -53,7 +53,7 @@ class WalletKitService extends IWalletKitService { _walletKit = ReownWalletKit( core: ReownCore( projectId: DartDefines.projectId, - logLevel: LogLevel.error, + logLevel: LogLevel.all, ), metadata: PairingMetadata( name: 'FL WalletKit Sample', @@ -66,8 +66,6 @@ class WalletKitService extends IWalletKitService { ), ); - _walletKit!.core.addLogListener(_logListener); - // Setup our listeners debugPrint('[SampleWallet] create'); _walletKit!.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid); @@ -148,18 +146,8 @@ class WalletKitService extends IWalletKitService { } } - 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( @@ -216,17 +204,19 @@ class WalletKitService extends IWalletKitService { // 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( + _walletKit!.approveSession( id: args.id, namespaces: NamespaceUtils.regenerateNamespacesWithChains( args.params.generatedNamespaces!, ), sessionProperties: args.params.sessionProperties, ); - MethodsUtils.handleRedirect( - session.topic, - session.session!.peer.metadata.redirect, - ); + // MethodsUtils.handleRedirect( + // session.topic, + // session.session!.peer.metadata.redirect, + // '', + // true, + // ); } on ReownSignError catch (error) { MethodsUtils.handleRedirect( '', @@ -271,7 +261,13 @@ class WalletKitService extends IWalletKitService { void _onSessionConnect(SessionConnect? args) { if (args != null) { final session = jsonEncode(args.session.toJson()); - debugPrint('[SampleWallet] _onSessionConnect $session'); + log('[SampleWallet] _onSessionConnect $session'); + MethodsUtils.handleRedirect( + args.session.topic, + args.session.peer.metadata.redirect, + '', + true, + ); } } @@ -292,7 +288,7 @@ class WalletKitService extends IWalletKitService { final SessionAuthPayload authPayload = args.authPayload; final jsonPyaload = jsonEncode(authPayload.toJson()); debugPrint('[SampleWallet] _onSessionAuthRequest $jsonPyaload'); - final supportedChains = ChainData.eip155Chains.map((e) => e.chainId); + final supportedChains = ChainsDataList.eip155Chains.map((e) => e.chainId); final supportedMethods = SupportedEVMMethods.values.map((e) => e.name); final newAuthPayload = AuthSignature.populateAuthPayload( authPayload: authPayload, @@ -358,9 +354,12 @@ class WalletKitService extends IWalletKitService { id: args.id, auths: cacaos, ); + debugPrint('[$runtimeType] approveSessionAuthenticate $session'); MethodsUtils.handleRedirect( session.topic, session.session?.peer.metadata.redirect, + '', + true, ); } on ReownSignError catch (error) { MethodsUtils.handleRedirect( diff --git a/packages/reown_walletkit/example/lib/main.dart b/packages/reown_walletkit/example/lib/main.dart index adf9c2f..670546d 100644 --- a/packages/reown_walletkit/example/lib/main.dart +++ b/packages/reown_walletkit/example/lib/main.dart @@ -70,7 +70,7 @@ class _MyHomePageState extends State with GetItStateMixin { GetIt.I.registerSingleton(walletKitService); // Support EVM Chains - for (final chainData in ChainData.eip155Chains) { + for (final chainData in ChainsDataList.eip155Chains) { GetIt.I.registerSingleton( EVMService(chainSupported: chainData), instanceName: chainData.chainId, @@ -78,7 +78,7 @@ class _MyHomePageState extends State with GetItStateMixin { } // Support Kadena Chains - for (final chainData in ChainData.kadenaChains) { + for (final chainData in ChainsDataList.kadenaChains) { GetIt.I.registerSingleton( KadenaService(chainSupported: chainData), instanceName: chainData.chainId, @@ -86,7 +86,7 @@ class _MyHomePageState extends State with GetItStateMixin { } // Support Polkadot Chains - for (final chainData in ChainData.polkadotChains) { + for (final chainData in ChainsDataList.polkadotChains) { GetIt.I.registerSingleton( PolkadotService(chainSupported: chainData), instanceName: chainData.chainId, @@ -95,7 +95,7 @@ class _MyHomePageState extends State with GetItStateMixin { // 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) { + for (final chainData in ChainsDataList.solanaChains) { GetIt.I.registerSingleton( SolanaService2(chainSupported: chainData), instanceName: chainData.chainId, @@ -103,7 +103,7 @@ class _MyHomePageState extends State with GetItStateMixin { } // Support Cosmos Chains - for (final chainData in ChainData.cosmosChains) { + for (final chainData in ChainsDataList.cosmosChains) { GetIt.I.registerSingleton( CosmosService(chainSupported: chainData), instanceName: chainData.chainId, diff --git a/packages/reown_walletkit/example/lib/models/chain_data.dart b/packages/reown_walletkit/example/lib/models/chain_data.dart index e85a2e7..249594c 100644 --- a/packages/reown_walletkit/example/lib/models/chain_data.dart +++ b/packages/reown_walletkit/example/lib/models/chain_data.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:reown_walletkit_wallet/models/chain_metadata.dart'; -class ChainData { +class ChainsDataList { static final List eip155Chains = [ ChainMetadata( type: ChainType.eip155, diff --git a/packages/reown_walletkit/example/lib/pages/app_detail_page.dart b/packages/reown_walletkit/example/lib/pages/app_detail_page.dart index a653bed..06cd578 100644 --- a/packages/reown_walletkit/example/lib/pages/app_detail_page.dart +++ b/packages/reown_walletkit/example/lib/pages/app_detail_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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'; @@ -139,13 +140,39 @@ class AppDetailPageState extends State { 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, + Builder( + builder: (BuildContext context) { + if (metadata!.icons.isNotEmpty) { + final imageUrl = metadata.icons.first; + if (imageUrl.split('.').last == 'svg') { + return Container( + width: 80.0, + height: 80.0, + padding: const EdgeInsets.all(1.0), + decoration: BoxDecoration( + border: + Border.all(width: 1.0, color: Colors.black38), + borderRadius: + BorderRadius.all(Radius.circular(40.0)), + ), + child: ClipRRect( + borderRadius: + BorderRadius.all(Radius.circular(40.0)), + child: SvgPicture.network(imageUrl), + ), + ); + } + return CircleAvatar( + backgroundImage: NetworkImage(imageUrl), + radius: 40.0, + ); + } + return CircleAvatar( + backgroundImage: + const AssetImage('assets/images/default_icon.png'), + radius: 40.0, + ); + }, ), const SizedBox(width: 10.0), Expanded( @@ -213,6 +240,8 @@ class AppDetailPageState extends State { } void _back() { - Navigator.of(context).pop(); + if (Navigator.canPop(context)) { + Navigator.pop(context); + } } } diff --git a/packages/reown_walletkit/example/lib/pages/apps_page.dart b/packages/reown_walletkit/example/lib/pages/apps_page.dart index dd297af..6d64da8 100644 --- a/packages/reown_walletkit/example/lib/pages/apps_page.dart +++ b/packages/reown_walletkit/example/lib/pages/apps_page.dart @@ -244,7 +244,9 @@ class AppsPageState extends State with GetItStateMixin { actions: [ TextButton( onPressed: () { - Navigator.of(context).pop(); + if (Navigator.canPop(context)) { + Navigator.pop(context); + } }, child: const Text( 'Close', diff --git a/packages/reown_walletkit/example/lib/pages/settings_page.dart b/packages/reown_walletkit/example/lib/pages/settings_page.dart index 77444d1..114b40a 100644 --- a/packages/reown_walletkit/example/lib/pages/settings_page.dart +++ b/packages/reown_walletkit/example/lib/pages/settings_page.dart @@ -8,8 +8,12 @@ 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/chain_services/evm_service.dart'; +import 'package:reown_walletkit_wallet/dependencies/chain_services/solana_service_2.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/constants.dart'; import 'package:reown_walletkit_wallet/widgets/custom_button.dart'; import 'package:reown_walletkit_wallet/widgets/recover_from_seed.dart'; @@ -37,7 +41,9 @@ class _SettingsPageState extends State { onCreateAddress: () async { await keysService.createAddressFromSeed(); await keysService.loadKeys(); - Navigator.of(context).pop(); + if (Navigator.canPop(context)) { + Navigator.pop(context); + } setState(() {}); }, onAccountChanged: (address) async { @@ -75,6 +81,14 @@ class _SettingsPageState extends State { const SizedBox(height: 20.0), const Divider(height: 1.0), _Buttons( + onDeleteData: () async { + final walletKit = GetIt.I().walletKit; + await walletKit.core.storage.deleteAll(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Storage cleared'), + duration: Duration(seconds: 1), + )); + }, onRestoreFromSeed: () async { final mnemonic = await GetIt.I().queueBottomSheet( @@ -160,11 +174,20 @@ class _EVMAccounts extends StatefulWidget { class _EVMAccountsState extends State<_EVMAccounts> { int _currentPage = 0; late final PageController _pageController; + ChainMetadata? _selectedChain; + double _balance = 0.0; @override void initState() { super.initState(); + _selectedChain = ChainsDataList.eip155Chains.first; _pageController = PageController(); + final keysService = GetIt.I(); + final chainKeys = keysService.getKeysForChain('eip155'); + GetIt.I + .get(instanceName: _selectedChain!.chainId) + .getBalance(address: chainKeys[_currentPage].address) + .then((value) => setState(() => _balance = value)); } @override @@ -178,7 +201,6 @@ class _EVMAccountsState extends State<_EVMAccounts> { 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})', @@ -240,6 +262,41 @@ class _EVMAccountsState extends State<_EVMAccounts> { ], ), ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Text( + '${_balance.toStringAsFixed(3)} ETH', + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + DropdownButton( + value: _selectedChain, + items: ChainsDataList.eip155Chains.map((e) { + return DropdownMenuItem( + value: e, + child: Text(e.name), + ); + }).toList(), + onChanged: (ChainMetadata? chain) { + setState(() => _selectedChain = chain); + final chainKey = chainKeys[_currentPage]; + GetIt.I + .get(instanceName: chain?.chainId) + .getBalance(address: chainKey.address) + .then((value) => setState(() => _balance = value)); + }, + ), + ], + ), + ), SizedBox( height: 300.0, child: PageView.builder( @@ -259,7 +316,7 @@ class _EVMAccountsState extends State<_EVMAccounts> { const SizedBox(height: 12.0), _DataContainer( title: 'CAIP-10', - data: 'eip155:1:${chainKey.address}', + data: '${_selectedChain?.chainId}:${chainKey.address}', height: 84.0, ), const SizedBox(height: 12.0), @@ -322,7 +379,27 @@ class _EVMAccountsState extends State<_EVMAccounts> { } } -class _SolanaAccounts extends StatelessWidget { +class _SolanaAccounts extends StatefulWidget { + @override + State<_SolanaAccounts> createState() => _SolanaAccountsState(); +} + +class _SolanaAccountsState extends State<_SolanaAccounts> { + ChainMetadata? _selectedChain; + double _balance = 0.0; + + @override + void initState() { + super.initState(); + _selectedChain = ChainsDataList.solanaChains.first; + final keysService = GetIt.I(); + final chainKeys = keysService.getKeysForChain('solana'); + GetIt.I + .get(instanceName: _selectedChain!.chainId) + .getBalance(address: chainKeys.first.address) + .then((value) => setState(() => _balance = value)); + } + @override Widget build(BuildContext context) { final keysService = GetIt.I(); @@ -348,6 +425,41 @@ class _SolanaAccounts extends StatelessWidget { ], ), ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Text( + '${_balance.toStringAsFixed(3)} SOL', + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + DropdownButton( + value: _selectedChain, + items: ChainsDataList.solanaChains.map((e) { + return DropdownMenuItem( + value: e, + child: Text(e.name), + ); + }).toList(), + onChanged: (ChainMetadata? chain) { + setState(() => _selectedChain = chain); + final chainKey = chainKeys.first; + GetIt.I + .get(instanceName: chain?.chainId) + .getBalance(address: chainKey.address) + .then((value) => setState(() => _balance = value)); + }, + ), + ], + ), + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Column( @@ -520,9 +632,11 @@ class _DeviceData extends StatelessWidget { class _Buttons extends StatelessWidget { final VoidCallback onRestoreFromSeed; final VoidCallback onRestoreDefault; + final VoidCallback onDeleteData; const _Buttons({ required this.onRestoreFromSeed, required this.onRestoreDefault, + required this.onDeleteData, }); @override @@ -532,6 +646,19 @@ class _Buttons extends StatelessWidget { child: Column( children: [ const SizedBox(height: 8.0), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: onDeleteData, + child: Text( + 'Clear local storage', + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(height: 12.0), Row( children: [ CustomButton( diff --git a/packages/reown_walletkit/example/lib/utils/methods_utils.dart b/packages/reown_walletkit/example/lib/utils/methods_utils.dart index d05fa37..f129d25 100644 --- a/packages/reown_walletkit/example/lib/utils/methods_utils.dart +++ b/packages/reown_walletkit/example/lib/utils/methods_utils.dart @@ -53,16 +53,19 @@ class MethodsUtils { String topic, Redirect? redirect, [ String? error, + bool success = false, ]) { debugPrint( '[SampleWallet] handleRedirect topic: $topic, redirect: $redirect, error: $error'); - openApp(topic, redirect, onFail: (e) { - goBackModal( - title: 'Error', + openApp( + topic, + redirect, + onFail: (e) => goBackModal( + title: success ? 'Success' : 'Error', message: error, - success: false, - ); - }); + success: success, + ), + ); } static void openApp( diff --git a/packages/reown_walletkit/example/lib/widgets/pairing_item.dart b/packages/reown_walletkit/example/lib/widgets/pairing_item.dart index d68576b..381729f 100644 --- a/packages/reown_walletkit/example/lib/widgets/pairing_item.dart +++ b/packages/reown_walletkit/example/lib/widgets/pairing_item.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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'; @@ -31,11 +32,33 @@ class PairingItem extends StatelessWidget { .toList(); return ListTile( - leading: CircleAvatar( - backgroundImage: (metadata.icons.isNotEmpty - ? NetworkImage(metadata.icons[0]) - : const AssetImage('assets/images/default_icon.png')) - as ImageProvider, + leading: Builder( + builder: (BuildContext context) { + if (metadata.icons.isNotEmpty) { + final imageUrl = metadata.icons.first; + if (imageUrl.split('.').last == 'svg') { + return Container( + width: 40.0, + height: 40.0, + padding: const EdgeInsets.all(1.0), + decoration: BoxDecoration( + border: Border.all(width: 1.0, color: Colors.black38), + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + child: SvgPicture.network(imageUrl), + ), + ); + } + return CircleAvatar( + backgroundImage: NetworkImage(imageUrl), + ); + } + return CircleAvatar( + backgroundImage: const AssetImage('assets/images/default_icon.png'), + ); + }, ), title: Text( metadata.name, 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 index 9426ad9..873c7fd 100644 --- 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 @@ -41,7 +41,11 @@ class WCRequestWidget extends StatelessWidget { children: [ CustomButton( onTap: onReject ?? - () => Navigator.of(context).pop(WCBottomSheetResult.reject), + () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.reject); + } + }, type: CustomButtonType.invalid, child: const Text( StringConstants.reject, @@ -54,7 +58,11 @@ class WCRequestWidget extends StatelessWidget { ), CustomButton( onTap: onAccept ?? - () => Navigator.of(context).pop(WCBottomSheetResult.one), + () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.one); + } + }, type: CustomButtonType.valid, child: const Text( StringConstants.approve, 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 index 2ab94ac..18794cc 100644 --- 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 @@ -26,8 +26,11 @@ class WCSessionAuthRequestWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ CustomButton( - onTap: () => - Navigator.of(context).pop(WCBottomSheetResult.reject), + onTap: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.reject); + } + }, type: CustomButtonType.invalid, child: const Text( 'Cancel', @@ -37,7 +40,11 @@ class WCSessionAuthRequestWidget extends StatelessWidget { ), const SizedBox(width: StyleConstants.linear8), CustomButton( - onTap: () => Navigator.of(context).pop(WCBottomSheetResult.one), + onTap: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.one); + } + }, type: CustomButtonType.normal, child: const Text( 'Sign One', @@ -47,7 +54,11 @@ class WCSessionAuthRequestWidget extends StatelessWidget { ), const SizedBox(width: StyleConstants.linear8), CustomButton( - onTap: () => Navigator.of(context).pop(WCBottomSheetResult.all), + onTap: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.all); + } + }, type: CustomButtonType.valid, child: const Text( 'Sign All', diff --git a/packages/reown_walletkit/example/pubspec.yaml b/packages/reown_walletkit/example/pubspec.yaml index 0edb485..be1d49e 100644 --- a/packages/reown_walletkit/example/pubspec.yaml +++ b/packages/reown_walletkit/example/pubspec.yaml @@ -16,12 +16,13 @@ dependencies: fl_toast: ^3.1.0 flutter: sdk: flutter + flutter_svg: ^2.0.10+1 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 + package_info_plus: ^8.0.2 pointycastle: ^3.9.1 polkadart: ^0.4.6 polkadart_keyring: ^0.4.3 diff --git a/packages/reown_walletkit/generate_files.sh b/packages/reown_walletkit/generate_files.sh index 06f20ed..deec0b8 100644 --- a/packages/reown_walletkit/generate_files.sh +++ b/packages/reown_walletkit/generate_files.sh @@ -20,8 +20,9 @@ dart run dependency_validator cd ios -pod deintegrate -pod cache clean -all +# rm Podfile.lock +# pod deintegrate +# pod cache clean -all pod install cd .. diff --git a/packages/reown_walletkit/lib/version.dart b/packages/reown_walletkit/lib/version.dart index 92a0d66..ee68207 100644 --- a/packages/reown_walletkit/lib/version.dart +++ b/packages/reown_walletkit/lib/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '1.0.1'; +const packageVersion = '1.0.3'; diff --git a/packages/reown_walletkit/pubspec.yaml b/packages/reown_walletkit/pubspec.yaml index 007b025..2bafe84 100644 --- a/packages/reown_walletkit/pubspec.yaml +++ b/packages/reown_walletkit/pubspec.yaml @@ -1,6 +1,6 @@ name: reown_walletkit description: "Reown is the onchain UX platform that provides toolkits built on top of the WalletConnect Network" -version: 1.0.1 +version: 1.0.3 homepage: https://github.com/reown-com/reown_flutter repository: https://github.com/reown-com/reown_flutter/tree/master/packages/reown_walletkit documentation: https://docs.reown.com/walletkit/flutter/installation @@ -12,8 +12,12 @@ dependencies: event: ^2.1.2 flutter: sdk: flutter - reown_core: ^1.0.0 - reown_sign: ^1.0.0 + reown_core: ^1.0.4 + # reown_core: + # path: ../reown_core/ + reown_sign: ^1.0.4 + # reown_sign: + # path: ../reown_sign/ dev_dependencies: build_runner: ^2.4.7 @@ -25,8 +29,10 @@ dev_dependencies: http: ^1.2.2 logger: ^2.2.0 mockito: ^5.4.3 - package_info_plus: ^7.0.0 - reown_appkit: ^1.0.0 + package_info_plus: ^8.0.2 + reown_appkit: ^1.0.2 + # reown_appkit: + # path: ../reown_appkit/ platforms: android: diff --git a/packages/reown_walletkit/test/shared/shared_test_utils.mocks.dart b/packages/reown_walletkit/test/shared/shared_test_utils.mocks.dart index e7a5ab2..7f5c83e 100644 --- a/packages/reown_walletkit/test/shared/shared_test_utils.mocks.dart +++ b/packages/reown_walletkit/test/shared/shared_test_utils.mocks.dart @@ -1454,7 +1454,7 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ) as _i19.Logger); @override - void addLogListener(_i19.LogCallback? callback) => super.noSuchMethod( + void addLogListener(dynamic Function(String)? callback) => super.noSuchMethod( Invocation.method( #addLogListener, [callback], @@ -1463,7 +1463,8 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ); @override - bool removeLogListener(_i19.LogCallback? callback) => (super.noSuchMethod( + bool removeLogListener(dynamic Function(String)? callback) => + (super.noSuchMethod( Invocation.method( #removeLogListener, [callback], @@ -1482,15 +1483,14 @@ class MockReownCore extends _i1.Mock implements _i28.ReownCore { ) as _i23.Future); @override - _i23.Future addLinkModeSupportedApp(String? universalLink) => + _i23.Future addLinkModeSupportedApp(String? universalLink) => (super.noSuchMethod( Invocation.method( #addLinkModeSupportedApp, [universalLink], ), - returnValue: _i23.Future.value(), - returnValueForMissingStub: _i23.Future.value(), - ) as _i23.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override List getLinkModeSupportedApps() => (super.noSuchMethod(