Initial commit: Pezkuwi Wallet Android

Security hardened release:
- Code obfuscation enabled (minifyEnabled=true, shrinkResources=true)
- Sensitive files excluded (google-services.json, keystores)
- Branch.io key moved to BuildConfig placeholder
- Updated dependencies: OkHttp 4.12.0, Gson 2.10.1, BouncyCastle 1.77
- Comprehensive ProGuard rules for crypto wallet
- Navigation 2.7.7, Lifecycle 2.7.0, ConstraintLayout 2.1.4
This commit is contained in:
2026-02-12 05:19:41 +03:00
commit a294aa1a6b
7687 changed files with 441811 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# http://editorconfig.org
root = true
[{*.kt, *.kts}]
#Ktlint configuration
max_line_length = 160
insert_final_newline = true
indent_size = 4
ktlint_disabled_rules = import-ordering, package-name, trailing-comma-on-call-site, trailing-comma-on-declaration-site, filename
+72
View File
@@ -0,0 +1,72 @@
import os
import sys
import re
from datetime import datetime, timezone
import requests
ALLOWED_SEVERITIES = {"Major", "Critical", "Normal"}
# Matches: "Release severity: <value>" (case-insensitive, flexible spaces)
SEVERITY_LINE_RE = re.compile(r"^release\s+severity\s*:\s*(.+)$", re.IGNORECASE)
def parse_base_params(comment_link: str) -> None:
if not comment_link:
print("COMMENT_LINK is not set. Provide a valid PR comment API URL in env var COMMENT_LINK.")
sys.exit(1)
env_file = os.getenv("GITHUB_ENV")
if not env_file:
print("GITHUB_ENV is not set. This script expects GitHub Actions environment.")
sys.exit(1)
try:
resp = requests.get(comment_link, timeout=10)
resp.raise_for_status()
payload = resp.json()
except requests.RequestException as e:
print(f"Failed to fetch PR comment: {e}")
sys.exit(1)
except ValueError:
print("Response is not valid JSON.")
sys.exit(1)
body = payload.get("body")
if not isinstance(body, str) or not body.strip():
print("PR comment body is empty. Add 'Release severity: Major | Critical | Normal'.")
sys.exit(1)
lines = [line.strip() for line in body.splitlines()]
severity_raw = ""
for line in lines:
m = SEVERITY_LINE_RE.match(line)
if m:
severity_raw = m.group(1).strip()
break
if not severity_raw:
print("Release severity is missing. Add a line 'Release severity: Major | Critical | Normal'.")
sys.exit(1)
if severity_raw not in ALLOWED_SEVERITIES:
print(f"Invalid severity '{severity_raw}'. Allowed values: Major, Critical, Normal.")
sys.exit(1)
severity = severity_raw
time_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
try:
with open(env_file, "a", encoding="utf-8") as f:
f.write(f"TIME={time_iso}\n")
f.write(f"SEVERITY={severity}\n")
except OSError as e:
print(f"Failed to write to GITHUB_ENV: {e}")
sys.exit(1)
if __name__ == "__main__":
parse_base_params(os.getenv("COMMENT_LINK"))
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
adb devices
# Install debug app
adb -s emulator-5554 install app/debug/app-debug.apk
# Install instrumental tests
adb -s emulator-5554 install app/androidTest/debug/app-debug-androidTest.apk
# Run tests
adb logcat -c &&
python - <<END
import os
import re
import subprocess as sp
import sys
import threading
import time
done = False
def update():
# prevent CI from killing the process for inactivity
while not done:
time.sleep(5)
print ("Running...")
t = threading.Thread(target=update)
t.dameon = True
t.start()
def run():
os.system('adb wait-for-device')
p = sp.Popen('adb shell am instrument -w -m -e debug false -e class "io.novafoundation.nova.balances.BalancesIntegrationTest" io.novafoundation.nova.debug.test/io.qameta.allure.android.runners.AllureAndroidJUnitRunner',
shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE)
return p.communicate()
success = re.compile(r'OK \(\d+ tests\)')
stdout, stderr = run()
stdout = stdout.decode('ISO-8859-1')
stderr = stderr.decode('ISO-8859-1')
done = True
print (stderr)
print (stdout)
if success.search(stderr + stdout):
sys.exit(0)
else:
sys.exit(1) # make sure we fail if the tests fail
END
EXIT_CODE=$?
adb logcat -d '*:E'
# Export results
adb exec-out run-as io.novafoundation.nova.debug sh -c 'cd /data/data/io.novafoundation.nova.debug/files && tar cf - allure-results' > allure-results.tar
exit $EXIT_CODE
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
adb devices
# Install debug app
adb -s emulator-5554 install app/debug/app-debug.apk
# Install instrumental tests
adb -s emulator-5554 install test-app/androidTest/debug/app-debug-androidTest.apk
# Run tests
adb logcat -c &&
python - <<END
import os
import re
import subprocess as sp
import sys
import threading
import time
done = False
def update():
# prevent CI from killing the process for inactivity
while not done:
time.sleep(5)
print ("Running...")
t = threading.Thread(target=update)
t.dameon = True
t.start()
def run():
os.system('adb wait-for-device')
p = sp.Popen('adb shell am instrument -w -m -e notClass io.novafoundation.nova.balances.BalancesIntegrationTest -e package io.novafoundation.nova.debug io.novafoundation.nova.debug.test/io.qameta.allure.android.runners.AllureAndroidJUnitRunner',
shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE)
return p.communicate()
success = re.compile(r'OK \(\d+ tests\)')
stdout, stderr = run()
stdout = stdout.decode('ISO-8859-1')
stderr = stderr.decode('ISO-8859-1')
done = True
print (stderr)
print (stdout)
if success.search(stderr + stdout):
sys.exit(0)
else:
sys.exit(1) # make sure we fail if the tests fail
END
EXIT_CODE=$?
adb logcat -d '*:E'
# Export results
adb exec-out run-as io.novafoundation.nova.debug sh -c 'cd /data/data/io.novafoundation.nova.debug/files && tar cf - allure-results' > allure-results.tar
exit $EXIT_CODE
+204
View File
@@ -0,0 +1,204 @@
name: Reusable workflow for build Android
on:
workflow_call:
inputs:
branch:
required: true
default: main
type: string
gradlew-command:
required: false
type: string
default: "false"
run-tests:
required: false
type: boolean
default: true
keystore-file-name:
required: false
type: string
default: "false"
keystore-file-base64:
required: false
type: string
default: "false"
upload-name:
required: false
type: string
default: "apk"
build-debug-tests:
required: false
type: boolean
default: false
secrets:
# Crowdloan secrets - NOT NEEDED for Pezkuwi (own blockchain)
ACALA_PROD_AUTH_TOKEN:
required: false
ACALA_TEST_AUTH_TOKEN:
required: false
MOONBEAM_PROD_AUTH_TOKEN:
required: false
MOONBEAM_TEST_AUTH_TOKEN:
required: false
# Fiat on-ramp - OPTIONAL (future update)
MOONPAY_PRODUCTION_SECRET:
required: false
MOONPAY_TEST_SECRET:
required: false
# EVM chain support - REQUIRED for cross-chain
EHTERSCAN_API_KEY_MOONBEAM:
required: true
EHTERSCAN_API_KEY_MOONRIVER:
required: true
EHTERSCAN_API_KEY_ETHEREUM:
required: true
INFURA_API_KEY:
required: true
# RPC provider - use own nodes or Dwellir
DWELLIR_API_KEY:
required: false
# WalletConnect - REQUIRED for dApp connections
WALLET_CONNECT_PROJECT_ID:
required: true
# Google OAuth - REQUIRED for cloud backup
DEBUG_GOOGLE_OAUTH_ID:
required: true
RELEASE_GOOGLE_OAUTH_ID:
required: true
# Special secrets for signing:
CI_MARKET_KEYSTORE_PASS:
required: false
CI_MARKET_KEYSTORE_KEY_ALIAS:
required: false
CI_MARKET_KEYSTORE_KEY_PASS:
required: false
CI_MARKET_KEY_FILE:
required: false
CI_KEYSTORE_PASS:
required: false
CI_KEYSTORE_KEY_ALIAS:
required: false
CI_KEYSTORE_KEY_PASS:
required: false
CI_GITHUB_KEYSTORE_PASS:
required: false
CI_GITHUB_KEYSTORE_KEY_ALIAS:
required: false
CI_GITHUB_KEYSTORE_KEY_PASS:
required: false
# Secrets for google-services:
CI_DEVELOP_GOOGLE_SERVICES:
required: true
CI_PRODUCTION_GOOGLE_SERVICES:
required: true
env:
ACALA_PROD_AUTH_TOKEN: ${{ secrets.ACALA_PROD_AUTH_TOKEN }}
ACALA_TEST_AUTH_TOKEN: ${{ secrets.ACALA_TEST_AUTH_TOKEN }}
MOONBEAM_PROD_AUTH_TOKEN: ${{ secrets.MOONBEAM_PROD_AUTH_TOKEN }}
MOONBEAM_TEST_AUTH_TOKEN: ${{ secrets.MOONBEAM_TEST_AUTH_TOKEN }}
MOONPAY_PRODUCTION_SECRET: ${{ secrets.MOONPAY_PRODUCTION_SECRET }}
MOONPAY_TEST_SECRET: ${{ secrets.MOONPAY_TEST_SECRET }}
MERCURYO_PRODUCTION_SECRET: ${{ secrets.MERCURYO_PRODUCTION_SECRET }}
MERCURYO_TEST_SECRET: ${{ secrets.MERCURYO_TEST_SECRET }}
EHTERSCAN_API_KEY_MOONBEAM: ${{ secrets.EHTERSCAN_API_KEY_MOONBEAM }}
EHTERSCAN_API_KEY_MOONRIVER: ${{ secrets.EHTERSCAN_API_KEY_MOONRIVER }}
EHTERSCAN_API_KEY_ETHEREUM: ${{ secrets.EHTERSCAN_API_KEY_ETHEREUM }}
INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }}
DWELLIR_API_KEY: ${{ secrets.DWELLIR_API_KEY }}
WALLET_CONNECT_PROJECT_ID: ${{ secrets.WALLET_CONNECT_PROJECT_ID }}
DEBUG_GOOGLE_OAUTH_ID: ${{ secrets.DEBUG_GOOGLE_OAUTH_ID }}
RELEASE_GOOGLE_OAUTH_ID: ${{ secrets.RELEASE_GOOGLE_OAUTH_ID }}
CI_MARKET_KEYSTORE_PASS: ${{ secrets.CI_MARKET_KEYSTORE_PASS }}
CI_MARKET_KEYSTORE_KEY_ALIAS: ${{ secrets.CI_MARKET_KEYSTORE_KEY_ALIAS }}
CI_MARKET_KEYSTORE_KEY_PASS: ${{ secrets.CI_MARKET_KEYSTORE_KEY_PASS }}
CI_MARKET_KEY_FILE: ${{ secrets.RELEASE_MARKET_KEY_FILE }}
CI_KEYSTORE_PASS: ${{ secrets.CI_KEYSTORE_PASS }}
CI_KEYSTORE_KEY_ALIAS: ${{ secrets.CI_KEYSTORE_KEY_ALIAS }}
CI_KEYSTORE_KEY_PASS: ${{ secrets.CI_KEYSTORE_KEY_PASS }}
CI_GITHUB_KEYSTORE_PASS: ${{ secrets.CI_GITHUB_KEYSTORE_PASS }}
CI_GITHUB_KEYSTORE_KEY_ALIAS: ${{ secrets.CI_GITHUB_KEYSTORE_KEY_ALIAS }}
CI_GITHUB_KEYSTORE_KEY_PASS: ${{ secrets.CI_GITHUB_KEYSTORE_KEY_PASS }}
CI_GITHUB_KEYSTORE_KEY_FILE: ${{ secrets.BASE64_GITHUB_KEYSTORE_FILE }}
CI_DEVELOP_GOOGLE_SERVICES_FILE: ${{ secrets.CI_DEVELOP_GOOGLE_SERVICES }}
CI_PRODUCTION_GOOGLE_SERVICES_FILE: ${{ secrets.CI_PRODUCTION_GOOGLE_SERVICES }}
POLKASSEMBLY_SUMMARY_API_KEY: ${{ secrets.POLKASSEMBLY_SUMMARY_API_KEY }}
CI_BUILD_ID: ${{ github.run_number }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{inputs.upload-name}}
cancel-in-progress: true
jobs:
build-app:
name: Build app and test
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- name: Checkout particular branch
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
- name: 📂 Set up DEV Google Services
uses: davidSchuppa/base64Secret-toFile-action@v3
with:
secret: ${{ env.CI_DEVELOP_GOOGLE_SERVICES_FILE }}
filename: google-services.json
destination-path: ./app/
- name: 📂 Set up PROD Google Services
uses: davidSchuppa/base64Secret-toFile-action@v3
with:
secret: ${{ env.CI_PRODUCTION_GOOGLE_SERVICES_FILE }}
filename: google-services.json
destination-path: ./app/src/release/
- name: 🔧 Install dependencies
uses: ./.github/workflows/install/
- name: 🧪 Run tests
if: ${{ inputs.run-tests }}
run: ./gradlew runTest
- name: 🔐 Getting github sign key
if: ${{ startsWith(inputs.keystore-file-name, 'github_key.jks') }}
uses: timheuer/base64-to-file@v1.1
with:
fileName: ${{ inputs.keystore-file-name }}
fileDir: './app/'
encodedString: ${{ env.CI_GITHUB_KEYSTORE_KEY_FILE }}
- name: 🔐 Getting market sign key
if: ${{ startsWith(inputs.keystore-file-name, 'market_key.jks') }}
uses: timheuer/base64-to-file@v1.1
with:
fileName: ${{ inputs.keystore-file-name }}
fileDir: './app/'
encodedString: ${{ env.CI_MARKET_KEY_FILE }}
- name: 🏗 Build app
if: ${{ !startsWith(inputs.gradlew-command, 'false') }}
run: ./gradlew ${{ inputs.gradlew-command }}
- name: 🏗 Build debug tests
if: ${{ inputs.build-debug-tests }}
run: ./gradlew assembleDebugAndroidTest
- name: 🧹 Delete key after building
if: ${{ !startsWith(inputs.keystore-file-name, 'false') }}
run: rm ./app/${{ inputs.keystore-file-name }}
- name: ➡️ Upload build artifacts
if: ${{ !startsWith(inputs.gradlew-command, 'false') }}
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.upload-name }}
path: app/build/outputs/apk/
+68
View File
@@ -0,0 +1,68 @@
name: Appium Mobile Tests
on:
workflow_call:
inputs:
app_url:
type: string
description: URL to download the app from
required: true
test_grep:
type: string
description: Test pattern to run (pytest marker or test name)
required: false
default: "android"
allure_job_run_id:
type: string
description: ALLURE_JOB_RUN_ID service parameter. Leave blank.
required: false
default: ""
allure_username:
type: string
description: ALLURE_USERNAME service parameter. Leave blank.
required: false
default: ""
secrets:
WORKFLOW_TOKEN:
required: true
ALLURE_TOKEN:
required: false
env:
PYTHON_VERSION: '3.9'
CI: true
ALLURE_ENDPOINT: https://pezkuwi.testops.cloud/
ALLURE_PROJECT_ID: 103
jobs:
trigger-tests:
runs-on: ubuntu-latest
steps:
- name: Trigger mobile tests in test repository
uses: actions/github-script@v7
with:
github-token: ${{ secrets.WORKFLOW_TOKEN }}
script: |
const response = await github.rest.actions.createWorkflowDispatch({
owner: 'pezkuwichain',
repo: 'appium-mobile-tests',
workflow_id: 'browserstack-tests.yml',
ref: 'master',
inputs: {
app_url: '${{ inputs.app_url }}',
ALLURE_JOB_RUN_ID: '${{ inputs.allure_job_run_id }}',
ALLURE_USERNAME: '${{ inputs.allure_username }}'
}
});
console.log('Mobile tests triggered successfully');
console.log('App URL:', '${{ inputs.app_url }}');
- name: Wait for test completion (optional)
if: false
uses: actions/github-script@v7
with:
github-token: ${{ secrets.WORKFLOW_TOKEN }}
script: |
console.log('Waiting for test completion...');
+93
View File
@@ -0,0 +1,93 @@
name: Run balances tests
on:
workflow_dispatch:
schedule:
- cron: '0 */8 * * *'
jobs:
build-app:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@main
with:
branch: ${{github.head_ref}}
gradlew-command: assembleDebug
upload-name: develop-apk
run-tests: false
build-debug-tests: true
secrets: inherit
run-tests:
needs: [build-app]
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: develop-apk
path: app
- name: Debug path
run: |
ls -laR app
- name: Add permissions
run: chmod +x .github/scripts/run_balances_test.sh
- name: Run tests
uses: reactivecircus/android-emulator-runner@v2
with:
disable-animations: true
profile: Nexus 6
api-level: 29
script: .github/scripts/run_balances_test.sh
- uses: actions/upload-artifact@v4
if: always()
with:
name: anroid-results
path: ./allure-results.tar
report:
needs: [run-tests]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Unzip results
run: |
find artifacts -name allure-results.tar -exec tar -xvf {} \;
- name: Debug path
run: |
ls -laR
- name: Generate report
uses: ./.github/workflows/report/
with:
token: ${{ secrets.ACTIONS_DEPLOY_KEY }}
keep-reports-history: 30
telegram-notification:
needs: [report]
runs-on: ubuntu-latest
if: failure()
steps:
- name: Notify Telegram channel
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_TO }}
token: ${{ secrets.TELEGRAM_TOKEN }}
format: html
message: |
💸 Balances tests failed.
Test Results: https://pezkuwichain.github.io/balances_test_result/${{ github.run_number }}/index.html
Github run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@@ -0,0 +1,56 @@
name: Distribute app to Play Store
on:
workflow_dispatch:
inputs:
app_version:
description: 'Version of application'
required: true
default: v*.*.*
branch:
description: 'From which branch the application will be built'
required: true
default: main
jobs:
build:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@main
with:
branch: ${{ github.event.inputs.branch }}
gradlew-command: assembleReleaseMarket
keystore-file-name: market_key.jks
secrets: inherit
upload:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Set Environment Variables
uses: tw3lveparsecs/github-actions-setvars@v0.1
with:
envFilePath: .github/workflows/variables/android.env
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: Rename artifacts
run: mv app/releaseMarket/app-releaseMarket.apk app/releaseMarket/pezkuwi-wallet-android-${{ github.event.inputs.app_version }}.apk
- name: Market publication
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} # The contents of your service-account.json
packageName: io.pezkuwichain.wallet
releaseFiles: app/releaseMarket/pezkuwi-wallet-android-${{ github.event.inputs.app_version }}.apk
track: production # One of production, beta, alpha, internalsharing, internal, or a custom track name (case sensitive)
status: draft # One of "completed", "inProgress", "halted", "draft"
inAppUpdatePriority: 2
userFraction: 1.0 # Percentage of users who should get the staged version of the app. Defaults to 1.0
whatsNewDirectory: distribution/whatsnew # The directory of localized "whats new" files to upload as the release notes. The files contained in the whatsNewDirectory MUST use the pattern whatsnew-<LOCALE> where LOCALE is using the BCP 47 format
mappingFile: app/build/outputs/mapping/release/mapping.txt # The mapping.txt file used to de-obfuscate your stack traces from crash reports
debugSymbols: app/intermediates/merged_native_libs/release/out/lib
+37
View File
@@ -0,0 +1,37 @@
name: Install dependencies for Android build
description: Contains all dependencies for Android build
runs:
using: "composite"
steps:
- name: ☕️ Install Java
uses: actions/setup-java@v4.0.0
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
cmdline-tools-version: 12266719
- name: Install NDK
run: echo "y" | sudo ${ANDROID_SDK_ROOT}/cmdline-tools/16.0/bin/sdkmanager --install "ndk;26.1.10909125" --sdk_root=${ANDROID_SDK_ROOT}
shell: bash
- name: Set ndk.dir in local.properties
run: echo "ndk.dir=${{ steps.setup-ndk.outputs.ndk-path }}" >> local.properties
shell: bash
- name: 🦀 Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Add targets
run: |
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add x86_64-linux-android
rustup target add aarch64-linux-android
shell: bash
@@ -0,0 +1,42 @@
name: Manual Firebase distribution
on:
workflow_dispatch:
inputs:
firebase_group:
description: 'Firebase group'
required: true
default: dev-team
branch:
description: 'From which branch the application will be built'
required: true
default: main
jobs:
build:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@main
with:
branch: ${{ github.event.inputs.branch }}
gradlew-command: assembleDevelop
secrets: inherit
upload:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: 🗳 Upload to Firebase
uses: ./.github/workflows/upload-to-firebase
with:
appId: ${{ secrets.ANDROID_DEVELOP_FIREBASE_APP_ID }}
firebase-token: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
releaseNotes: ${{ github.event.head_commit.message }}
test-groups: ${{ github.event.inputs.firebase_group }}
upload-file: app/develop/app-develop.apk
+76
View File
@@ -0,0 +1,76 @@
name: PR Workflow
on:
pull_request:
branches:
- 'master'
pull_request_review_comment:
types: [created, edited, deleted]
jobs:
checkRef:
if: github.event.pull_request.base.ref == 'master' || github.event_name == 'pull_request'
runs-on: ubuntu-latest
outputs:
is_rc: ${{ steps.check_ref.outputs.ref_contains_rc }}
steps:
- uses: actions/checkout@v4
- name: Check if "rc" or "hotfix" is present in github.ref
id: check_ref
run: |
echo ${{ github.head_ref || github.ref_name }}
if [[ "${{ github.head_ref || github.ref_name }}" == "rc/"* || "${{ github.head_ref || github.ref_name }}" == "hotfix/"* ]]; then
echo "ref_contains_rc=1" >> $GITHUB_OUTPUT
else
echo "ref_contains_rc=0" >> $GITHUB_OUTPUT
fi
- name: Output check result
run: |
echo "Output: ${{ steps.check_ref.outputs.ref_contains_rc }}"
make-or-update-pr:
runs-on: ubuntu-latest
permissions: write-all
needs: checkRef
if: needs.checkRef.outputs.is_rc == '1'
steps:
- uses: actions/checkout@v4
- name: Find Comment
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Release notes
- name: Create comment link
id: create_link
run: |
echo "COMMENT_LINK=https://api.github.com/repos/${{ github.repository }}/issues/comments/${{ steps.fc.outputs.comment-id }}" >> $GITHUB_ENV
shell: bash
- name: Extract version from branch name
id: extract_version
run: |
VERSION=${{ github.head_ref }}
VERSION=${VERSION/hotfix/rc} # Replace "hotfix" with "rc"
echo "version=${VERSION#*rc/}" >> $GITHUB_OUTPUT
- uses: tibdex/github-app-token@v2
id: generate-token
with:
app_id: ${{ secrets.PR_APP_ID }}
private_key: ${{ secrets.PR_APP_TOKEN }}
- name: Run Python script
run: python .github/scripts/pr_comment_extract_data.py
- name: Create new branch and file in pezkuwi-wallet-android-releases repo
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ steps.generate-token.outputs.token }}
repository: pezkuwichain/pezkuwi-wallet-android-releases
event-type: create-pr
client-payload: '{"version": "${{ steps.extract_version.outputs.version }}", "comment_link": "${{ env.COMMENT_LINK }}", "time": "${{ env.TIME }}", "severity": "${{ env.SEVERITY }}"}'
@@ -0,0 +1,88 @@
name: Publish GitHub release
on:
push:
tags:
- '*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
jobs:
build:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@main
with:
branch: master
gradlew-command: assembleReleaseGithub
keystore-file-name: github_key.jks
secrets: inherit
create-release:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: Rename artifacts
run: mv app/releaseGithub/app-releaseGithub.apk app/releaseGithub/pezkuwi-wallet-android-${{ github.ref_name }}-github.apk
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
with:
name: Release ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
generate_release_notes: true
draft: true
files: app/releaseGithub/pezkuwi-wallet-android-${{ github.ref_name }}-github.apk
deploy-to-vps:
runs-on: ubuntu-latest
needs: build
steps:
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: deploy
- name: Prepare APK for VPS
run: |
mkdir -p ./vps-deploy
mv deploy/releaseGithub/app-releaseGithub.apk ./vps-deploy/pezkuwi-wallet.apk
- name: Create version.json
run: |
TAG="${{ github.ref_name }}"
VERSION="${TAG#v}"
cat > ./vps-deploy/version.json << EOF
{
"version": "$VERSION",
"tag": "$TAG",
"apk": "pezkuwi-wallet.apk",
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF
- name: Deploy to VPS
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
source: 'vps-deploy/*'
target: '/var/www/wallet.pezkuwichain.io'
strip_components: 1
overwrite: true
+14
View File
@@ -0,0 +1,14 @@
name: Pull request
on:
pull_request:
jobs:
test:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@main
with:
branch: ${{github.head_ref}}
gradlew-command: assembleDevelop
build-debug-tests: false # TODO: Enable this, when debug build will be fixed for tests
secrets: inherit
+78
View File
@@ -0,0 +1,78 @@
name: Build test and deploy debug apk
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@main
with:
branch: main
gradlew-command: assembleDebug
secrets: inherit
upload-to-firebase:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: 🗳 Upload to Firebase
uses: ./.github/workflows/upload-to-firebase
with:
appId: ${{ secrets.ANDROID_DEBUG_FIREBASE_APP_ID }}
firebase-token: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
releaseNotes: ${{ github.event.head_commit.message }}
test-groups: dev-team
upload-file: app/debug/app-debug.apk
upload-to-s3:
runs-on: ubuntu-latest
needs: build
outputs:
s3_url: ${{ steps.s3_upload.outputs.s3_url }}
env:
S3_BUCKET: s3://pezkuwi-wallet-android-app
S3_REGION: nl-ams
steps:
- uses: actions/checkout@v4
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: ⚙️ Upload to S3
id: s3_upload
uses: ./.github/workflows/upload-to-s3
with:
s3_region: ${{ env.S3_REGION }}
s3_access_key: ${{ secrets.SCW_ACCESS_KEY }}
s3_secret_key: ${{ secrets.SCW_SECRET_KEY }}
s3_bucket: ${{ env.S3_BUCKET }}
upload_file: app/debug/app-debug.apk
- name: Show S3 URL
run: |
echo "App uploaded to: ${{ steps.s3_upload.outputs.s3_url }}"
appium-mobile-tests:
needs: [upload-to-s3]
if: ${{ always() && needs.upload-to-s3.result == 'success' }}
uses: ./.github/workflows/appium-mobile-tests.yml
with:
app_url: ${{ needs.upload-to-s3.outputs.s3_url }}
test_grep: "android"
allure_job_run_id: ""
secrets:
WORKFLOW_TOKEN: ${{ secrets.PAT_TOKEN }}
ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
+40
View File
@@ -0,0 +1,40 @@
name: Publish report to gh-pages
description: That workflow will publish report to the github-pages
inputs:
keep-reports-history:
description: "History storage depth, integer"
required: true
token:
description: "Github PAT"
required: true
runs:
using: "composite"
steps:
- name: Get Allure history
uses: actions/checkout@v4
if: always()
continue-on-error: true
with:
repository: pezkuwichain/balances_test_result
ref: gh-pages
path: gh-pages
- name: Allure Report action
uses: simple-elf/allure-report-action@master
if: always()
with:
allure_results: allure-results
allure_history: allure-history
keep_reports: ${{ inputs.keep-reports-history }}
github_repo: balances_test_result
github_repo_owner: pezkuwichain
- name: Deploy report to Github Pages
if: always()
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ inputs.token }}
publish_branch: gh-pages
publish_dir: allure-history
external_repository: pezkuwichain/balances_test_result
+34
View File
@@ -0,0 +1,34 @@
name: Sync main and master branches
on:
push:
branches:
- main
- master
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.PAT_TOKEN }}
- name: Sync branches
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "main was updated, syncing master..."
git checkout master
git reset --hard origin/main
git push origin master --force
elif [ "${{ github.ref }}" = "refs/heads/master" ]; then
echo "master was updated, syncing main..."
git checkout main
git reset --hard origin/master
git push origin main --force
fi
+43
View File
@@ -0,0 +1,43 @@
name: Bump app version
on:
push:
branches:
['master']
permissions:
contents: write
jobs:
update-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Version in build.gradle
run: |
versionName=$(grep "versionName" build.gradle | grep -o "'.*'")
versionName=${versionName//\'/}
echo Version in gradle file: $versionName
echo "GRADLE_APP_VERSION=$versionName" >> "$GITHUB_ENV"
- name: Check if tag exists
id: version
run: |
if git rev-parse "v${{ env.GRADLE_APP_VERSION }}" >/dev/null 2>&1; then
echo "Tag already exists"
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "Tag does not exist"
echo "changed=true" >> $GITHUB_OUTPUT
fi
- uses: rickstaa/action-create-tag@v1
if: steps.version.outputs.changed == 'true'
with:
tag: 'v${{ env.GRADLE_APP_VERSION }}'
message: Release v${{ env.GRADLE_APP_VERSION }}
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,49 @@
name: Deploy to Firebase
description: Deploy artifacts by provided path to firebase for provided groups
inputs:
appId:
description: 'Firebase AppID'
required: true
firebase-token:
description: 'Token from firebase CLI'
required: true
releaseNotes:
description: 'Notes which will attach to version'
required: true
default: 'update'
test-groups:
description: 'Groups which will receive the version'
required: true
upload-file:
description: 'File to uploading'
required: true
runs:
using: "composite"
steps:
- name: Upload artifact to Firebase App Distribution
id: upload
continue-on-error: true
uses: wzieba/Firebase-Distribution-Github-Action@v1.7.0
with:
appId: ${{ inputs.appId }}
serviceCredentialsFileContent: ${{ inputs.firebase-token }}
releaseNotes: ${{ inputs.releaseNotes }}
groups: ${{ inputs.test-groups }}
file: ${{ inputs.upload-file }}
- name: Sleep for 60 seconds
uses: whatnick/wait-action@master
if: steps.upload.outcome=='failure'
with:
time: '60s'
- name: Retry upload artifacts
if: steps.upload.outcome=='failure'
uses: wzieba/Firebase-Distribution-Github-Action@v1.7.0
with:
appId: ${{ inputs.appId }}
serviceCredentialsFileContent: ${{ inputs.firebase-token }}
releaseNotes: ${{ inputs.releaseNotes }}
groups: ${{ inputs.test-groups }}
file: ${{ inputs.upload-file }}
+52
View File
@@ -0,0 +1,52 @@
name: Upload to s3
description: Upload artifacts to s3
inputs:
s3_region:
description: 'S3 region'
required: true
s3_access_key:
description: 'S3 access key'
required: true
s3_secret_key:
description: 'S3 secret key'
required: true
s3_bucket:
description: 'S3 bucket'
required: true
upload_file:
description: 'File to uploading'
required: true
outputs:
s3_url:
description: 'URL of the uploaded file'
value: ${{ steps.interact_with_storage.outputs.s3_url }}
runs:
using: "composite"
steps:
- name: Set up S3cmd cli tool
uses: s3-actions/s3cmd@v1.6.1
with:
provider: scaleway
region: ${{ inputs.s3_region }}
secret_key: ${{ inputs.s3_secret_key }}
access_key: ${{ inputs.s3_access_key }}
- name: List available S3 buckets
run: s3cmd ls
shell: bash
- name: Interact with object storage
id: interact_with_storage
run: |
file="${{ inputs.upload_file }}"
destination_s3="${{ inputs.s3_bucket }}"
filename=$(basename "$file")
s3cmd sync "$file" "${destination_s3}/${filename}" --acl-public
bucket_name=$(echo "${{ inputs.s3_bucket }}" | sed 's|s3://||')
s3_url="https://${bucket_name}.s3.${{ inputs.s3_region }}.scw.cloud/${filename}"
echo "s3_url=${s3_url}" >> $GITHUB_OUTPUT
echo "Uploaded file URL: ${s3_url}"
shell: bash
+3
View File
@@ -0,0 +1,3 @@
# Android Build Variables for Pezkuwi Wallet
APP_NAME=Pezkuwi Wallet
PACKAGE_NAME=io.pezkuwichain.wallet
+34
View File
@@ -0,0 +1,34 @@
*.iml
.gradle
/local.properties
.DS_Store
/build
*/build
/captures
.externalNativeBuild
app/src/main/aidl/
app/*.apk
/.idea/
# ignore jacoco coverage reports
/coverage
*.jks
.java-version
# ignore database schemas
/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/*.json
# database schemas exceptions
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/1.json
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/2.json
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/8.json
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/9.json
# Firebase config - contains sensitive API keys
google-services.json
**/google-services.json
# Version properties
version.properties
.kotlin/
+6
View File
@@ -0,0 +1,6 @@
[submodule "nova-wallet-dapp-js"]
path = nova-wallet-dapp-js
url = git@github.com:nova-wallet/nova-wallet-dapp-js.git
[submodule "nova-wallet-metamask-js"]
path = nova-wallet-metamask-js
url = git@github.com:nova-wallet/nova-wallet-metamask-js.git
+380
View File
@@ -0,0 +1,380 @@
# PezWallet Android - Pezkuwi Uyumluluk Değişiklikleri
Bu dosya, Pezkuwi chain uyumluluğu için yapılan tüm değişiklikleri takip eder.
Context sıfırlanması durumunda referans olarak kullanılmalıdır.
---
## DEBUG KODLARI (Production öncesi KALDIRILMALI)
### 1. FeeLoaderV2Provider.kt - Hata mesajı gösterimi
**Dosya:** `feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/v2/FeeLoaderV2Provider.kt`
**Değişiklik:**
```kotlin
// ÖNCE:
message = resourceManager.getString(R.string.choose_amount_error_fee),
// SONRA (DEBUG):
message = "DEBUG: $errorMsg | Runtime: $diagnostics",
```
**Temizleme:**
- `"DEBUG: $errorMsg | Runtime: $diagnostics"``resourceManager.getString(R.string.choose_amount_error_fee)` olarak geri al
- `val diagnostics = try { ... }` bloğunu kaldır
---
### 2. RuntimeFactory.kt - Diagnostic değişken ve log'lar
**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt`
**Eklenenler:**
```kotlin
// Companion object içinde:
companion object {
@Volatile
var lastDiagnostics: String = "not yet initialized"
}
// constructRuntimeInternal içinde:
lastDiagnostics = "typesUsage=$typesUsage, ExtrinsicSig=$hasExtrinsicSignature, MultiSig=$hasMultiSignature, typeCount=${types.size}"
// Log satırları:
Log.d("RuntimeFactory", "DEBUG: TypesUsage for chain $chainId = $typesUsage")
Log.d("RuntimeFactory", "DEBUG: Loading BASE types for $chainId")
Log.d("RuntimeFactory", "DEBUG: BASE types loaded, hash=$baseHash, typeCount=${types.size}")
Log.d("RuntimeFactory", "DEBUG: Chain $chainId - ExtrinsicSignature=$hasExtrinsicSignature, MultiSignature=$hasMultiSignature, typesUsage=$typesUsage, typeCount=${types.size}")
Log.d("RuntimeFactory", "DEBUG: BaseTypes loaded, length=${baseTypesRaw.length}, contains ExtrinsicSignature=${baseTypesRaw.contains("ExtrinsicSignature")}")
Log.e("RuntimeFactory", "DEBUG: BaseTypes NOT in cache!")
```
**Temizleme:**
- `companion object { ... }` bloğunu kaldır
- `lastDiagnostics = ...` satırını kaldır
- Tüm `Log.d/Log.e("RuntimeFactory", "DEBUG: ...")` satırlarını kaldır
---
### 3. CustomTransactionExtensions.kt - Log satırları
**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/CustomTransactionExtensions.kt`
**Temizlenecek:** Tüm `Log.d(TAG, ...)` satırları ve `private const val TAG` tanımı
---
### 4. ExtrinsicBuilderFactory.kt - Log satırları
**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt`
**Temizlenecek:** Tüm `Log.d(TAG, ...)` satırları ve `private const val TAG` tanımı
---
### 5. PezkuwiAddressConstructor.kt - Log satırları
**Dosya:** `common/src/main/java/io/novafoundation/nova/common/utils/PezkuwiAddressConstructor.kt`
**Temizlenecek:** Tüm `Log.d(TAG, ...)` satırları ve `private const val TAG` tanımı
---
### 6. RealExtrinsicService.kt - Extrinsic build hata log'u
**Dosya:** `feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt`
**Eklenen:**
```kotlin
val extrinsic = try {
extrinsicBuilder.buildExtrinsic()
} catch (e: Exception) {
Log.e("RealExtrinsicService", "Failed to build extrinsic for chain ${chain.name}", e)
Log.e("RealExtrinsicService", "SigningMode: $signingMode, Chain: ${chain.id}")
throw e
}
```
**Temizleme:** try-catch bloğunu kaldır, sadece `extrinsicBuilder.buildExtrinsic()` bırak
---
## FEATURE DEĞİŞİKLİKLERİ (Kalıcı)
### 1. PezkuwiAddressConstructor.kt - YENİ DOSYA
**Dosya:** `common/src/main/java/io/novafoundation/nova/common/utils/PezkuwiAddressConstructor.kt`
**Açıklama:** Pezkuwi chain'leri için özel address constructor. SDK'nın AddressInstanceConstructor'ı "Address" type'ını ararken, Pezkuwi "pezsp_runtime::multiaddress::MultiAddress" kullanıyor.
---
### 2. RuntimeSnapshotExt.kt - Address type lookup
**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/util/RuntimeSnapshotExt.kt`
**Değişiklik:** Birden fazla address type ismi deneniyor:
```kotlin
val addressType = typeRegistry["Address"]
?: typeRegistry["MultiAddress"]
?: typeRegistry["sp_runtime::multiaddress::MultiAddress"]
?: typeRegistry["pezsp_runtime::multiaddress::MultiAddress"]
?: return false
```
---
### 3. Signed Extension Dosyaları - YENİ DOSYALAR
**Dosyalar:**
- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/AuthorizeCall.kt`
- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckNonZeroSender.kt`
- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckWeight.kt`
- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/WeightReclaim.kt`
- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckMortality.kt`
**Açıklama:** Pezkuwi chain'leri için özel signed extension'lar
---
### 3.1. PezkuwiCheckMortality.kt - YENİ DOSYA
**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckMortality.kt`
**Açıklama:** SDK'nın CheckMortality'si metadata type lookup yaparak encode ediyor ve Pezkuwi'de bu başarısız oluyordu ("failed to encode extension CheckMortality" hatası). Bu custom extension, Era ve blockHash'i doğrudan encode ediyor.
**Neden gerekli:** SDK CheckMortality, Era type'ını metadata'dan arıyor. Pezkuwi metadata'sında Era type'ı `pezsp_runtime.generic.era.Era` DictEnum olarak tanımlı ve SDK bunu handle edemiyor.
**Kod:**
```kotlin
class PezkuwiCheckMortality(
era: Era.Mortal,
blockHash: ByteArray
) : FixedValueTransactionExtension(
name = "CheckMortality",
implicit = blockHash, // blockHash goes into signer payload
explicit = createEraEntry(era) // Era as DictEnum.Entry
)
```
---
### 4. CustomTransactionExtensions.kt - Pezkuwi extension logic
**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/CustomTransactionExtensions.kt`
**Değişiklik:** `isPezkuwiChain()` fonksiyonu eklendi, Pezkuwi için farklı extension'lar kullanılıyor
---
### 5. Address encoding yaklaşımı değişikliği
**Eski yaklaşım:** `AddressInstanceConstructor` veya `PezkuwiAddressConstructor` ile type ismine göre tahmin
**Yeni yaklaşım:** `argumentType("dest").constructAccountLookupInstance(accountId)` ile metadata'dan gerçek type alınıyor
**Güncellenen dosyalar:**
1. `feature-wallet-api/.../ExtrinsicBuilderExt.kt` - **YENİ YAKLAŞIM**: metadata'dan type alıyor
2. `feature-governance-impl/.../ExtrinsicBuilderExt.kt` - Zaten doğru yaklaşımı kullanıyordu
3. Diğer dosyalar hala PezkuwiAddressConstructor kullanıyor (gerekirse güncellenecek):
- `feature-staking-impl/.../ExtrinsicBuilderExt.kt`
- `feature-staking-impl/.../NominationPoolsCalls.kt`
- `feature-proxy-api/.../ExtrinsicBuilderExt.kt`
- `feature-wallet-impl/.../StatemineAssetTransfers.kt`
- `feature-wallet-impl/.../OrmlAssetTransfers.kt`
- `feature-wallet-impl/.../NativeAssetIssuer.kt`
- `feature-wallet-impl/.../OrmlAssetIssuer.kt`
- `feature-wallet-impl/.../StatemineAssetIssuer.kt`
- `feature-account-impl/.../ProxiedSigner.kt`
---
### 6. CHAINS_URL - GitHub'a yönlendirme
**Dosya:** `runtime/build.gradle`
**Değişiklik:**
```gradle
// ÖNCE:
buildConfigField "String", "CHAINS_URL", "\"https://wallet.pezkuwichain.io/chains.json\""
// SONRA:
buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/chains/v22/android/chains.json\""
```
**Neden:** wallet.pezkuwichain.io/chains.json Telegram miniapp için kullanılıyor ve `"types": null`. Android için ayrı chains.json gerekli.
---
### 7. chains/v22/android/chains.json - Android-specific chains
**Repo:** `pezkuwi-wallet-utils`
**Dosya:** `chains/v22/android/chains.json`
**Açıklama:** Android uygulama için özel chains.json. wallet.pezkuwichain.io'dan kopyalandı ve şu değişiklikler yapıldı:
- `"types": { "overridesCommon": false }` eklendi (TypesUsage.BASE için)
- `"feeViaRuntimeCall": true` eklendi
**Etkilenen chain'ler:**
- Pezkuwi Mainnet (bb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75)
- Pezkuwi Asset Hub (00d0e1d0581c3cd5c5768652d52f4520184018b44f56a2ae1e0dc9d65c00c948)
- Pezkuwi People Chain (58269e9c184f721e0309332d90cafc410df1519a5dc27a5fd9b3bf5fd2d129f8)
- Zagros Testnet (96eb58af1bb7288115b5e4ff1590422533e749293f231974536dc6672417d06f)
---
### 8. default.json - MultiAddress inline tanımı
**Repo:** `pezkuwi-wallet-utils`
**Dosya:** `chains/types/default.json`
**Değişiklik:** MultiAddress artık GenericMultiAddress'e referans vermiyor, inline enum olarak tanımlı:
```json
"MultiAddress": {
"type": "enum",
"type_mapping": [
["Id", "AccountId"],
["Index", "Compact<u32>"],
["Raw", "Bytes"],
["Address32", "H256"],
["Address20", "H160"]
]
}
```
**Neden:** v14Preset() GenericMultiAddress içermiyor, bu yüzden type çözümlenemiyordu.
---
### 9. PezkuwiIntegrationTest.kt - YENİ DOSYA
**Dosya:** `app/src/androidTest/java/io/novafoundation/nova/PezkuwiIntegrationTest.kt`
**Açıklama:** Pezkuwi chain'leri için integration testleri:
- Runtime type kontrolü (ExtrinsicSignature, MultiSignature, Address, MultiAddress)
- ExtrinsicBuilder oluşturma
- Transfer call yapısı kontrolü
- Signed extensions kontrolü
- Utility asset kontrolü
**Çalıştırma:**
```bash
./gradlew :app:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=io.novafoundation.nova.PezkuwiIntegrationTest
```
---
### 10. GitHub Actions - Branch senkronizasyonu
**Dosya:** `.github/workflows/sync-branches.yml`
**Açıklama:** main ve master branch'lerini otomatik senkronize eder.
- main'e push → master güncellenir
- master'a push → main güncellenir
---
### 7. pezkuwi.json - Chain-specific types (ASSETS)
**Dosya:** `runtime/src/main/assets/types/pezkuwi.json`
**Açıklama:** Pezkuwi chain'leri için özel type tanımları
```json
{
"types": {
"ExtrinsicSignature": "MultiSignature",
"Address": "pezsp_runtime::multiaddress::MultiAddress",
"LookupSource": "pezsp_runtime::multiaddress::MultiAddress"
},
"typesAlias": {
"pezsp_runtime::multiaddress::MultiAddress": "MultiAddress",
"pezsp_runtime::MultiSignature": "MultiSignature",
"pezsp_runtime.generic.era.Era": "Era"
}
}
```
**NOT:** Bu dosya şu anda kullanılmıyor çünkü TypesUsage.BASE kullanılıyor. TypesUsage.BOTH veya OWN için chains.json'da URL eklenebilir.
---
## SORUN GEÇMİŞİ
1. **"Network not responding"** - Fee hesaplama hatası
- Çözüm: feeViaRuntimeCall eklendi, custom signed extension'lar eklendi
2. **"IllegalStateException: Type Address was not found"** - Address type lookup hatası
- Çözüm: RuntimeSnapshotExt.kt'de birden fazla type ismi deneniyor
3. **"EncodeDecodeException: is not a valid instance"** - Address encoding hatası
- Çözüm: `argumentType("dest").constructAccountLookupInstance(accountId)` ile metadata'dan gerçek type alınıyor (ExtrinsicBuilderExt.kt)
4. **"failed to encode extension CheckMortality"** - CheckMortality encoding hatası
- SDK'nın CheckMortality'si metadata type lookup yaparak Era'yı encode etmeye çalışıyor
- Pezkuwi Era type'ı `pezsp_runtime.generic.era.Era` DictEnum olarak tanımlı
- Çözüm: `PezkuwiCheckMortality` custom extension'ı oluşturuldu, Era'yı `DictEnum.Entry("MortalX", secondByte)` olarak veriyor
5. **"IllegalStateException: Type ExtrinsicSignature was not found"** - ExtrinsicSignature type hatası ✅ ÇÖZÜLDÜ
- SDK "ExtrinsicSignature" type'ını arıyor ama Pezkuwi chain'leri `"types": null` kullanıyordu
- `TypesUsage.NONE` olduğu için base types (default.json) yüklenmiyordu
- **Çözüm:**
- `runtime/build.gradle` içinde CHAINS_URL GitHub'a yönlendirildi
- `pezkuwi-wallet-utils/chains/v22/android/chains.json` oluşturuldu (`"types": { "overridesCommon": false }`)
- Artık `TypesUsage.BASE` kullanılıyor ve default.json yükleniyor
6. **"IllegalStateException: Type Address was not found"** - Address type hatası ✅ ÇÖZÜLDÜ
- v14Preset() `GenericMultiAddress` içermiyor
- default.json'da `"MultiAddress": "GenericMultiAddress"` tanımlıydı ama GenericMultiAddress çözümlenemiyordu
- **Çözüm:** default.json'da MultiAddress inline enum olarak tanımlandı:
```json
"MultiAddress": {
"type": "enum",
"type_mapping": [
["Id", "AccountId"],
["Index", "Compact<u32>"],
["Raw", "Bytes"],
["Address32", "H256"],
["Address20", "H160"]
]
}
```
7. **"TypeReference is null"** - Transfer onaylama hatası (DEVAM EDİYOR)
- Fee hesaplama çalışıyor ✅
- Transfer onaylama sırasında hata oluşuyor
- Muhtemelen signing sırasında bir type çözümlenemiyor
- Debug logging eklendi: `RealExtrinsicService.kt`
- Stack trace bekleniyor
---
## ÇALIŞAN İMPLEMENTASYONLAR (Referans)
### 1. pezkuwi-extension (Browser Extension)
**Konum:** `/home/mamostehp/pezkuwi-extension/`
**Nasıl çalışıyor:**
- `@pezkuwi/types` (polkadot.js fork) kullanıyor
- `TypeRegistry` ile dynamic type handling
- Custom user extensions:
```javascript
const PEZKUWI_USER_EXTENSIONS = {
AuthorizeCall: {
extrinsic: {},
payload: {}
}
};
```
- `registry.setSignedExtensions(payload.signedExtensions, PEZKUWI_USER_EXTENSIONS)` ile extension'lar ekleniyor
- Metadata'dan registry oluşturuluyor: `metadataExpand(metadata, false)`
### 2. pezkuwi-subxt (Rust)
**Konum:** `/home/mamostehp/pezkuwi-sdk/vendor/pezkuwi-subxt/`
**Nasıl çalışıyor:**
- Rust'ta compile-time type generation
- Metadata'dan otomatik type oluşturma
### 3. Telegram Miniapp
- Web tabanlı, polkadot.js kullanıyor
- `"types": null` ile çalışıyor çünkü metadata v14+ self-contained
---
## TEMİZLEME KONTROL LİSTESİ
Production release öncesi yapılacaklar:
- [ ] FeeLoaderV2Provider.kt - DEBUG mesajını ve diagnostics'i kaldır
- [ ] RuntimeFactory.kt - companion object ve debug log'ları kaldır
- [ ] CustomTransactionExtensions.kt - Log satırlarını kaldır
- [ ] ExtrinsicBuilderFactory.kt - Log satırlarını kaldır
- [ ] PezkuwiAddressConstructor.kt - Log satırlarını kaldır (varsa)
- [ ] RealExtrinsicService.kt - try-catch debug bloğunu kaldır
- [x] Test et: Fee hesaplama çalışıyor mu? ✅
- [ ] Test et: Transfer işlemi çalışıyor mu? (TypeReference hatası devam ediyor)
---
## TYPE LOADING AKIŞI (Referans)
```
chains.json
"types": { "overridesCommon": false } → TypesUsage.BASE
"types": { "url": "...", "overridesCommon": false } → TypesUsage.BOTH
"types": { "url": "...", "overridesCommon": true } → TypesUsage.OWN
"types": null → TypesUsage.NONE
RuntimeFactory.constructRuntime()
TypesUsage.BASE → constructBaseTypes() → fetch from DEFAULT_TYPES_URL
TypesUsage.BOTH → constructBaseTypes() + constructOwnTypes()
TypesUsage.OWN → constructOwnTypes() only
TypesUsage.NONE → use v14Preset() only
TypeRegistry
RuntimeSnapshot
```
**DEFAULT_TYPES_URL:** `https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/chains/types/default.json`
---
*Son güncelleme: 2026-02-03 06:30 (CHAINS_URL GitHub'a yönlendirildi, MultiAddress inline tanımlandı, Integration test eklendi, TypeReference hatası araştırılıyor)*
+176
View File
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+9
View File
@@ -0,0 +1,9 @@
Nova - Polkadot, Kusama wallet
Copyright 2022-2025 Novasama Technologies PTE. LTD.
This product includes software developed at Novasama Technologies PTE. LTD.
Some parts of this product are derived from https://github.com/soramitsu/fearless-Android, which belongs to Soramitsu K.K. and was mostly developed by our team of developers from May 1, 2020, to October 5, 2021.
Copyright 2021, Soramitsu Helvetia AG, all rights reserved.
License Rights transferred from Novasama Technologies PTE. LTD to Novasama Technologies GmbH starting from 1st of April 2023
+118
View File
@@ -0,0 +1,118 @@
# Pezkuwi Wallet Android
Next generation mobile wallet for Pezkuwichain and the Polkadot ecosystem.
[![](https://img.shields.io/twitter/follow/pezkuwichain?label=Follow&style=social)](https://twitter.com/pezkuwichain)
## About
Pezkuwi Wallet is a next-generation mobile application for the Pezkuwichain and Polkadot ecosystem. It provides a transparent, community-oriented wallet experience with convenient UX/UI, fast performance, and strong security.
**Key Features:**
- Full Pezkuwichain support (HEZ & PEZ tokens)
- Full Polkadot ecosystem compatibility
- Staking, Governance, DeFi
- NFT support
- Cross-chain transfers (XCM)
- Hardware wallet support (Ledger, Polkadot Vault)
- WalletConnect v2
- Push notifications
## Native Tokens
| Token | Network | Description |
|-------|---------|-------------|
| HEZ | Relay Chain | Native token for fees and staking |
| PEZ | Asset Hub | Governance token |
## Build Instructions
### Clone Repository
```bash
git clone git@github.com:pezkuwichain/pezkuwi-wallet-android.git
```
### Install NDK
Install NDK version `26.1.10909125` from SDK Manager:
Tools -> SDK Manager -> SDK Tools -> NDK (Side by Side)
### Install Rust
Install Rust by following [official instructions](https://www.rust-lang.org/tools/install).
Add Android build targets:
```bash
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add x86_64-linux-android
rustup target add aarch64-linux-android
```
### Update local.properties
Add the following lines to your `local.properties`:
```properties
ACALA_PROD_AUTH_TOKEN=mock
ACALA_TEST_AUTH_TOKEN=mock
CI_KEYSTORE_KEY_ALIAS=mock
CI_KEYSTORE_KEY_PASS=mock
CI_KEYSTORE_PASS=mock
DEBUG_GOOGLE_OAUTH_ID=mock
RELEASE_GOOGLE_OAUTH_ID=mock
DWELLIR_API_KEY=mock
EHTERSCAN_API_KEY_ETHEREUM=mock
EHTERSCAN_API_KEY_MOONBEAM=mock
EHTERSCAN_API_KEY_MOONRIVER=mock
INFURA_API_KEY=mock
MERCURYO_PRODUCTION_SECRET=mock
MERCURYO_TEST_SECRET=mock
MOONBEAM_PROD_AUTH_TOKEN=mock
MOONBEAM_TEST_AUTH_TOKEN=mock
MOONPAY_PRODUCTION_SECRET=mock
MOONPAY_TEST_SECRET=mock
WALLET_CONNECT_PROJECT_ID=mock
```
**Note:** Firebase and Google-related features (Notifications, Cloud Backups) require proper configuration.
### Build Types
- `debug`: Uses fixed keystore for Google services
- `debugLocal`: Uses your local debug keystore
- `release`: Production build
## Supported Languages
- English
- Turkish (Türkçe)
- Kurdish Kurmanji (Kurmancî)
- Spanish (Español)
- French (Français)
- German (Deutsch)
- Russian (Русский)
- Japanese (日本語)
- Chinese (中文)
- Korean (한국어)
- Portuguese (Português)
- Vietnamese (Tiếng Việt)
- And more...
## Resources
- Website: https://pezkuwichain.io
- Documentation: https://docs.pezkuwichain.io
- Telegram: https://t.me/pezkuwichain
- Twitter: https://twitter.com/pezkuwichain
- GitHub: https://github.com/pezkuwichain
## License
Pezkuwi Wallet Android is available under the Apache 2.0 license. See the LICENSE file for more info.
Based on Nova Wallet (https://novawallet.io) - © Novasama Technologies GmbH
© Dijital Kurdistan Tech Institute 2026
+37
View File
@@ -0,0 +1,37 @@
def APP_MODULE = 'app'
subprojects {
if (!file("$projectDir/build.gradle").exists()) {
return
}
if (APP_MODULE == project.name) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'kotlin-android'
apply plugin: "com.google.devtools.ksp"
android {
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
freeCompilerArgs = ["-Xcontext-receivers"]
}
}
}
+5
View File
@@ -0,0 +1,5 @@
/build
/release*
src/release*/google-services.json
!src/release/google-services.json
+336
View File
@@ -0,0 +1,336 @@
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlin-parcelize'
apply plugin: 'com.google.firebase.appdistribution'
apply plugin: "com.github.triplet.play"
apply from: "../scripts/versions.gradle"
apply from: "../scripts/secrets.gradle"
android {
defaultConfig {
applicationId rootProject.applicationId
versionCode computeVersionCode()
versionName computeVersionName()
// Branch.io key from local.properties or environment variable
manifestPlaceholders = [
BRANCH_KEY: readRawSecretOrNull('BRANCH_KEY') ?: "key_test_placeholder"
]
}
signingConfigs {
dev {
storeFile file('develop_key.jks')
storePassword readRawSecretOrNull('CI_KEYSTORE_PASS')
keyAlias readRawSecretOrNull('CI_KEYSTORE_KEY_ALIAS')
keyPassword readRawSecretOrNull('CI_KEYSTORE_KEY_PASS')
}
market {
storeFile file('market_key.jks')
storePassword readRawSecretOrNull('CI_MARKET_KEYSTORE_PASS')
keyAlias readRawSecretOrNull('CI_MARKET_KEYSTORE_KEY_ALIAS')
keyPassword readRawSecretOrNull('CI_MARKET_KEYSTORE_KEY_PASS')
}
github {
storeFile file('github_key.jks')
storePassword readRawSecretOrNull('CI_GITHUB_KEYSTORE_PASS')
keyAlias readRawSecretOrNull('CI_GITHUB_KEYSTORE_KEY_ALIAS')
keyPassword readRawSecretOrNull('CI_GITHUB_KEYSTORE_KEY_PASS')
}
}
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-debug'
buildConfigField "String", "BuildType", "\"debug\""
}
debugLocal {
initWith buildTypes.debug
matchingFallbacks = ['debug']
signingConfig signingConfigs.debug
}
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField "String", "BuildType", "\"release\""
}
releaseTest {
initWith buildTypes.release
matchingFallbacks = ['release']
signingConfig signingConfigs.debug
versionNameSuffix '-releaseTest'
applicationIdSuffix '.releaseTest'
buildConfigField "String", "BuildType", "\"releaseTest\""
}
releaseMarket {
initWith buildTypes.release
matchingFallbacks = ['release']
signingConfig signingConfigs.market
versionNameSuffix "-${releaseApplicationSuffix}"
applicationIdSuffix ".${releaseApplicationSuffix}"
buildConfigField "String", "BuildType", "\"releaseMarket\""
}
releaseGithub {
initWith buildTypes.release
matchingFallbacks = ['release']
signingConfig signingConfigs.github
buildConfigField "String", "BuildType", "\"releaseGithub\""
}
develop {
signingConfig signingConfigs.dev
matchingFallbacks = ['debug']
versionNameSuffix '-develop'
applicationIdSuffix '.dev'
//Init firebase
def localReleaseNotes = releaseNotes()
def localFirebaseGroup = firebaseGroup()
firebaseAppDistribution {
releaseNotes = localReleaseNotes
groups = localFirebaseGroup
}
buildConfigField "String", "BuildType", "\"develop\""
}
instrumentialTest {
initWith buildTypes.debug
matchingFallbacks = ['debug']
defaultConfig.testInstrumentationRunner "io.qameta.allure.android.runners.AllureAndroidJUnitRunner"
buildConfigField "String", "BuildType", "\"instrumentalTest\""
}
}
sourceSets {
releaseGithub {
res.srcDirs = ['src/release/res']
}
releaseMarket {
res.srcDirs = ['src/release/res']
}
releaseTest {
res.srcDirs = ['src/release/res']
}
}
bundle {
language {
enableSplit = false
}
}
applicationVariants.all { variant ->
String name = variant.buildType.name
if (name != "release" && name.startsWith("release")) {
createBindReleaseFileTask(variant.buildType.name)
}
}
packagingOptions {
resources.excludes.add("META-INF/versions/9/previous-compilation-data.bin")
resources.excludes.add("META-INF/DEPENDENCIES")
resources.excludes.add("META-INF/NOTICE.md")
}
buildFeatures {
viewBinding true
}
namespace 'io.novafoundation.nova.app'
}
void createBindReleaseFileTask(String destination) {
String taskName = "bind${destination.capitalize()}GithubGoogleServicesToRelease"
Task task = task(taskName, type: Copy) {
description = "Switches to RELEASE google-services.json for ${destination}"
from "src/release"
include "google-services.json"
into "src/${destination}"
}
afterEvaluate {
def capitalizedDestination = destination.capitalize()
def dependentTasks = [
"process${capitalizedDestination}GoogleServices".toString(),
"merge${capitalizedDestination}JniLibFolders".toString(),
"merge${capitalizedDestination}StartupProfile".toString(),
"merge${capitalizedDestination}Shaders".toString(),
"merge${capitalizedDestination}ArtProfile".toString()
]
dependentTasks.forEach {
tasks.getByName(it).dependsOn(task)
}
}
}
play {
serviceAccountCredentials = file(System.env.CI_PLAY_KEY ?: "../key/fake.json")
track = "beta"
releaseStatus = "completed"
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':core-db')
implementation project(':common')
implementation project(':feature-splash')
implementation project(':feature-onboarding-api')
implementation project(':feature-onboarding-impl')
implementation project(':feature-ledger-api')
implementation project(':feature-ledger-core')
implementation project(':feature-ledger-impl')
implementation project(':feature-account-api')
implementation project(':feature-account-impl')
implementation project(':feature-account-migration')
implementation project(':feature-wallet-api')
implementation project(':feature-wallet-impl')
implementation project(':runtime')
implementation project(':web3names')
implementation project(':feature-staking-api')
implementation project(':feature-staking-impl')
implementation project(':feature-crowdloan-api')
implementation project(':feature-crowdloan-impl')
implementation project(':feature-dapp-api')
implementation project(':feature-dapp-impl')
implementation project(':feature-nft-api')
implementation project(':feature-nft-impl')
implementation project(':feature-currency-api')
implementation project(':feature-currency-impl')
implementation project(':feature-governance-api')
implementation project(':feature-governance-impl')
implementation project(':feature-assets')
implementation project(':feature-vote')
implementation project(':feature-versions-api')
implementation project(':feature-versions-impl')
implementation project(':caip')
implementation project(':feature-external-sign-api')
implementation project(':feature-external-sign-impl')
implementation project(':feature-wallet-connect-api')
implementation project(':feature-wallet-connect-impl')
implementation project(':feature-proxy-api')
implementation project(':feature-proxy-impl')
implementation project(':feature-settings-api')
implementation project(':feature-settings-impl')
implementation project(":feature-swap-core")
implementation project(':feature-swap-api')
implementation project(':feature-swap-impl')
implementation project(":feature-buy-api")
implementation project(":feature-buy-impl")
implementation project(':feature-push-notifications')
implementation project(':feature-deep-linking')
implementation project(':feature-cloud-backup-api')
implementation project(':feature-cloud-backup-impl')
implementation project(':feature-banners-api')
implementation project(':feature-banners-impl')
implementation project(':feature-ahm-api')
implementation project(':feature-ahm-impl')
implementation project(':feature-gift-api')
implementation project(':feature-gift-impl')
implementation project(':bindings:metadata_shortener')
implementation project(':bindings:sr25519-bizinikiwi')
implementation project(":feature-xcm:impl")
implementation project(":feature-multisig:operations")
implementation project(':test-shared')
implementation kotlinDep
implementation biometricDep
implementation androidDep
implementation constraintDep
implementation zXingEmbeddedDep
implementation navigationFragmentDep
implementation navigationUiDep
implementation roomDep
implementation substrateSdkDep
implementation daggerDep
ksp daggerCompiler
implementation lifecycleDep
ksp lifecycleCompiler
implementation lifeCycleKtxDep
implementation retrofitDep
implementation gsonConvertedDep
implementation gifDep
compileOnly wsDep
implementation coroutinesDep
testImplementation project(':test-shared')
implementation insetterDep
implementation liveDataKtxDep
implementation platform(firebaseBomDep)
implementation firestoreDep
implementation firebaseCloudMessagingDep
implementation firebaseAppCheck
implementation walletConnectCoreDep, withoutTransitiveAndroidX
implementation walletConnectWalletDep, withoutTransitiveAndroidX
kspAndroidTest daggerCompiler
androidTestImplementation androidTestRunnerDep
androidTestImplementation androidTestRulesDep
androidTestImplementation androidJunitDep
androidTestImplementation allureKotlinModel
androidTestImplementation allureKotlinCommons
androidTestImplementation allureKotlinJunit4
androidTestImplementation allureKotlinAndroid
}
task printVersion {
doLast {
println "versionName:${computeVersionName()}"
}
}
+170
View File
@@ -0,0 +1,170 @@
# ============================================================
# Pezkuwi Wallet ProGuard Rules
# ============================================================
# Keep line numbers for debugging crash reports
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
# ============================================================
# Kotlin
# ============================================================
-dontwarn kotlin.**
-keep class kotlin.Metadata { *; }
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
# Kotlin Coroutines
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepclassmembers class kotlinx.coroutines.** {
volatile <fields>;
}
-dontwarn kotlinx.coroutines.**
# ============================================================
# Retrofit & OkHttp
# ============================================================
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
# ============================================================
# Gson
# ============================================================
-keepattributes Signature
-keepattributes *Annotation*
-dontwarn sun.misc.**
-keep class com.google.gson.** { *; }
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# ============================================================
# BouncyCastle Crypto
# ============================================================
-keep class org.bouncycastle.** { *; }
-dontwarn org.bouncycastle.**
# ============================================================
# Native JNI Bindings (Rust)
# ============================================================
# SR25519 signing
-keep class io.novafoundation.nova.** { native <methods>; }
-keepclasseswithmembernames class * {
native <methods>;
}
# Keep all JNI related classes
-keep class io.parity.** { *; }
# ============================================================
# Substrate SDK
# ============================================================
-keep class jp.co.soramitsu.** { *; }
-dontwarn jp.co.soramitsu.**
# ============================================================
# Firebase
# ============================================================
-keep class com.google.firebase.** { *; }
-dontwarn com.google.firebase.**
-keep class com.google.android.gms.** { *; }
-dontwarn com.google.android.gms.**
# ============================================================
# Branch.io Deep Linking
# ============================================================
-keep class io.branch.** { *; }
-dontwarn io.branch.**
# ============================================================
# Web3j (Ethereum)
# ============================================================
-keep class org.web3j.** { *; }
-dontwarn org.web3j.**
# ============================================================
# SQLCipher
# ============================================================
-keep class net.sqlcipher.** { *; }
-dontwarn net.sqlcipher.**
# ============================================================
# Room Database
# ============================================================
-keep class * extends androidx.room.RoomDatabase
-keep @androidx.room.Entity class *
-dontwarn androidx.room.paging.**
# ============================================================
# Data Classes & Models (Keep for serialization)
# ============================================================
-keep class io.novafoundation.nova.**.model.** { *; }
-keep class io.novafoundation.nova.**.response.** { *; }
-keep class io.novafoundation.nova.**.request.** { *; }
-keep class io.novafoundation.nova.**.dto.** { *; }
# ============================================================
# Parcelable
# ============================================================
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
# ============================================================
# Enums
# ============================================================
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# ============================================================
# Serializable
# ============================================================
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# ============================================================
# Ledger USB/Bluetooth
# ============================================================
-keep class io.novafoundation.nova.feature_ledger_impl.** { *; }
# ============================================================
# WalletConnect
# ============================================================
-keep class com.walletconnect.** { *; }
-dontwarn com.walletconnect.**
# ============================================================
# Optimization settings
# ============================================================
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
# ============================================================
# Don't warn about missing classes that we don't use
# ============================================================
-dontwarn org.conscrypt.**
-dontwarn org.slf4j.**
-dontwarn javax.naming.**
@@ -0,0 +1,50 @@
package io.novafoundation.nova
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.withChildScope
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.ext.Geneses
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
open class BaseIntegrationTest {
protected val context: Context = ApplicationProvider.getApplicationContext()
protected val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(context, RuntimeApi::class.java)
val chainRegistry = runtimeApi.chainRegistry()
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
@Before
fun setup() = runBlocking {
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
}
protected fun runTest(action: suspend CoroutineScope.() -> Unit) {
runBlocking {
withChildScope {
action()
}
}
}
protected suspend fun ChainRegistry.polkadot(): Chain {
return getChain(Chain.Geneses.POLKADOT)
}
protected suspend fun ChainRegistry.polkadotAssetHub(): Chain {
return getChain(Chain.Geneses.POLKADOT_ASSET_HUB)
}
}
@@ -0,0 +1,60 @@
package io.novafoundation.nova
import android.content.Context
import android.util.Log
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.novafoundation.nova.common.data.network.runtime.binding.bindEventRecords
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.system
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BlockParsingIntegrationTest {
private val chainGenesis = "f1cf9022c7ebb34b162d5b5e34e705a5a740b2d0ecc1009fb89023e62a488108" // Shiden
private val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(
ApplicationProvider.getApplicationContext<Context>(),
RuntimeApi::class.java
)
private val chainRegistry = runtimeApi.chainRegistry()
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
private val rpcCalls = runtimeApi.rpcCalls()
private val remoteStorage = runtimeApi.remoteStorageSource()
@Test
fun testBlockParsing() = runBlocking {
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
val chain = chainRegistry.getChain(chainGenesis)
val block = rpcCalls.getBlock(chain.id)
val logTag = this@BlockParsingIntegrationTest.LOG_TAG
Log.d(logTag, block.block.header.number.toString())
val events = remoteStorage.query(
chainId = chain.id,
keyBuilder = { it.metadata.system().storage("Events").storageKey() },
binding = { scale, runtime ->
Log.d(logTag, scale!!)
bindEventRecords(scale)
}
)
// val eventsRaw = "0x0800000000000000000000000000000002000000010000000000585f8f0900000000020000"
// val type = bindEventRecords(eventsRaw, chainRegistry.getRuntime(chain.id))
}
}
@@ -0,0 +1,129 @@
package io.novafoundation.nova
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.gson.Gson
import androidx.test.platform.app.InstrumentationRegistry
import dagger.Component
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.core_db.model.chain.ChainLocal
import io.novafoundation.nova.core_db.model.chain.NodeSelectionPreferencesLocal
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.RemoteToDomainChainMapperFacade
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainAssetToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainExplorerToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainExternalApiToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainLocalToChain
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainNodeToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapExternalApisToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapNodeSelectionPreferencesToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapRemoteAssetToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapRemoteChainToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapRemoteExplorersToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapRemoteNodesToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.remote.ChainFetcher
import io.novafoundation.nova.runtime.multiNetwork.chain.remote.model.ChainRemote
import io.novafoundation.nova.test_shared.assertAllItemsEquals
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@Component(
dependencies = [
CommonApi::class,
RuntimeApi::class
]
)
interface MappingTestAppComponent {
fun inject(test: ChainMappingIntegrationTest)
}
@RunWith(AndroidJUnit4::class)
class ChainMappingIntegrationTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
private val featureContainer = context as FeatureContainer
@Inject
lateinit var networkApiCreator: NetworkApiCreator
@Inject
lateinit var remoteToDomainChainMapperFacade: RemoteToDomainChainMapperFacade
lateinit var chainFetcher: ChainFetcher
private val gson = Gson()
@Before
fun prepare() {
val component = DaggerMappingTestAppComponent.builder()
.commonApi(featureContainer.commonApi())
.runtimeApi(featureContainer.getFeature(RuntimeApi::class.java))
.build()
component.inject(this)
chainFetcher = networkApiCreator.create(ChainFetcher::class.java)
}
@Test
fun testChainMappingIsMatch() {
runBlocking {
val chainsRemote = chainFetcher.getChains()
val remoteToDomain = chainsRemote.map { mapRemoteToDomain(it) }
val remoteToLocalToDomain = chainsRemote.map { mapRemoteToLocalToDomain(it) }
val domainToLocalToDomain = remoteToDomain.map { mapDomainToLocalToDomain(it) }
assertAllItemsEquals(listOf(remoteToDomain, remoteToLocalToDomain, domainToLocalToDomain))
}
}
private fun mapRemoteToLocalToDomain(chainRemote: ChainRemote): Chain {
val chainLocal = mapRemoteChainToLocal(chainRemote, null, ChainLocal.Source.DEFAULT, gson)
val assetsLocal = chainRemote.assets.map { mapRemoteAssetToLocal(chainRemote, it, gson, isEnabled = true) }
val nodesLocal = mapRemoteNodesToLocal(chainRemote)
val explorersLocal = mapRemoteExplorersToLocal(chainRemote)
val externalApisLocal = mapExternalApisToLocal(chainRemote)
return mapChainLocalToChain(
chainLocal = chainLocal,
nodesLocal = nodesLocal,
nodeSelectionPreferences = NodeSelectionPreferencesLocal(chainLocal.id, autoBalanceEnabled = true, selectedNodeUrl = null),
assetsLocal = assetsLocal,
explorersLocal = explorersLocal,
externalApisLocal = externalApisLocal,
gson = gson
)
}
private fun mapRemoteToDomain(chainRemote: ChainRemote): Chain {
return remoteToDomainChainMapperFacade.mapRemoteChainToDomain(chainRemote, Chain.Source.DEFAULT)
}
private fun mapDomainToLocalToDomain(chain: Chain): Chain {
val chainLocal = mapChainToLocal(chain, gson)
val nodesLocal = chain.nodes.nodes.map { mapChainNodeToLocal(it) }
val nodeSelectionPreferencesLocal = mapNodeSelectionPreferencesToLocal(chain)
val assetsLocal = chain.assets.map { mapChainAssetToLocal(it, gson) }
val explorersLocal = chain.explorers.map { mapChainExplorerToLocal(it) }
val externalApisLocal = chain.externalApis.map { mapChainExternalApiToLocal(gson, chain.id, it) }
return mapChainLocalToChain(
chainLocal = chainLocal,
nodesLocal = nodesLocal,
nodeSelectionPreferences = nodeSelectionPreferencesLocal,
assetsLocal = assetsLocal,
explorersLocal = explorersLocal,
externalApisLocal = externalApisLocal,
gson = gson
)
}
}
@@ -0,0 +1,61 @@
package io.novafoundation.nova
import androidx.room.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.google.gson.Gson
import dagger.Component
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.core_db.AppDatabase
import io.novafoundation.nova.runtime.multiNetwork.chain.ChainSyncService
import io.novafoundation.nova.runtime.multiNetwork.chain.remote.ChainFetcher
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import javax.inject.Inject
@Component(
dependencies = [
CommonApi::class,
]
)
interface TestAppComponent {
fun inject(test: ChainSyncServiceIntegrationTest)
}
@RunWith(AndroidJUnit4::class)
class ChainSyncServiceIntegrationTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
private val featureContainer = context as FeatureContainer
@Inject
lateinit var networkApiCreator: NetworkApiCreator
lateinit var chainSyncService: ChainSyncService
@Before
fun setup() {
val component = DaggerTestAppComponent.builder()
.commonApi(featureContainer.commonApi())
.build()
component.inject(this)
val chainDao = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build()
.chainDao()
chainSyncService = ChainSyncService(chainDao, networkApiCreator.create(ChainFetcher::class.java), Gson())
}
@Test
fun shouldFetchAndStoreRealChains() = runBlocking {
chainSyncService.syncUp()
}
}
@@ -0,0 +1,98 @@
package io.novafoundation.nova
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.address.intoKey
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.emptySubstrateAccountId
import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentCurrency
import io.novafoundation.nova.feature_account_api.domain.model.toDefaultSubstrateAddress
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransferBase
import io.novafoundation.nova.feature_wallet_api.data.repository.getXcmChain
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_wallet_api.domain.model.xcm.legacy.transferConfiguration
import io.novafoundation.nova.feature_wallet_api.domain.model.xcm.transferConfiguration
import io.novafoundation.nova.runtime.ext.addressOf
import io.novafoundation.nova.runtime.ext.emptyAccountId
import io.novafoundation.nova.runtime.ext.normalizeTokenSymbol
import io.novafoundation.nova.runtime.multiNetwork.findChain
import kotlinx.coroutines.runBlocking
import org.junit.Test
import java.math.BigInteger
class CrossChainTransfersIntegrationTest : BaseIntegrationTest() {
private val walletApi = FeatureUtils.getFeature<WalletFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
WalletFeatureApi::class.java
)
private val chainTransfersRepository = walletApi.crossChainTransfersRepository
private val crossChainWeigher = walletApi.crossChainWeigher
private val parachainInfoRepository = runtimeApi.parachainInfoRepository
@Test
fun testParachainToParachain() = performFeeTest(
from = "Moonriver",
what = "xcKAR",
to = "Karura"
)
@Test
fun testRelaychainToParachain() = performFeeTest(
from = "Kusama",
what = "KSM",
to = "Moonriver"
)
@Test
fun testParachainToRelaychain() = performFeeTest(
from = "Moonriver",
what = "xcKSM",
to = "Kusama"
)
@Test
fun testParachainToParachainNonReserve() = performFeeTest(
from = "Karura",
what = "BNC",
to = "Moonriver"
)
private fun performFeeTest(
from: String,
to: String,
what: String
) {
runBlocking {
val originChain = chainRegistry.findChain { it.name == from }!!
val asssetInOrigin = originChain.assets.first { it.symbol.value == what }
val destinationChain = chainRegistry.findChain { it.name == to }!!
val asssetInDestination = destinationChain.assets.first { normalizeTokenSymbol(it.symbol.value) == normalizeTokenSymbol(what) }
val crossChainConfig = chainTransfersRepository.getConfiguration()
val crossChainTransfer = crossChainConfig.transferConfiguration(
originChain = parachainInfoRepository.getXcmChain(originChain),
originAsset = asssetInOrigin,
destinationChain = parachainInfoRepository.getXcmChain(destinationChain),
)!!
val transfer = AssetTransferBase(
recipient = originChain.addressOf(originChain.emptyAccountId()),
originChain = originChain,
originChainAsset = asssetInOrigin,
destinationChain = destinationChain,
destinationChainAsset = asssetInDestination,
feePaymentCurrency = FeePaymentCurrency.Native,
amountPlanks = BigInteger.ZERO
)
val crossChainFeeResult = runCatching { crossChainWeigher.estimateFee(transfer, crossChainTransfer) }
check(crossChainFeeResult.isSuccess)
}
}
}
@@ -0,0 +1,100 @@
package io.novafoundation.nova
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.address.intoKey
import io.novafoundation.nova.common.data.network.runtime.binding.toResult
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.composeCall
import io.novafoundation.nova.common.utils.xcmPalletName
import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount
import io.novafoundation.nova.feature_xcm_api.asset.MultiAsset
import io.novafoundation.nova.feature_xcm_api.asset.MultiAssets
import io.novafoundation.nova.feature_xcm_api.di.XcmFeatureApi
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model.OriginCaller
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model.getByLocation
import io.novafoundation.nova.feature_xcm_api.multiLocation.Junctions
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction.ParachainId
import io.novafoundation.nova.feature_xcm_api.multiLocation.asLocation
import io.novafoundation.nova.feature_xcm_api.multiLocation.toMultiLocation
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
import io.novafoundation.nova.feature_xcm_api.versions.toEncodableInstance
import io.novafoundation.nova.feature_xcm_api.versions.versionedXcm
import io.novafoundation.nova.feature_xcm_api.weight.WeightLimit
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
import org.junit.Test
import java.math.BigDecimal
import java.math.BigInteger
class DryRunIntegrationTest : BaseIntegrationTest() {
private val xcmApi = FeatureUtils.getFeature<XcmFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
XcmFeatureApi::class.java
)
private val dryRunApi = xcmApi.dryRunApi
@Test
fun testDryRunXcmTeleport() = runTest {
val polkadot = chainRegistry.polkadot()
val polkadotAssetHub = chainRegistry.polkadotAssetHub()
val polkadotRuntime = chainRegistry.getRuntime(polkadot.id)
val polkadotLocation = MultiLocation.Interior.Here.asLocation()
val polkadotAssetHubLocation = Junctions(ParachainId(1000)).asLocation()
val dotLocation = polkadotLocation.toRelative()
val amount = polkadot.utilityAsset.planksFromAmount(BigDecimal.ONE)
val assets = MultiAsset.from(dotLocation, amount)
val origin = "16WWmr2Xqgy5fna35GsNHXMU7vDBM12gzHCFGibQjSmKpAN".toAccountId().intoKey()
val beneficiary = origin.toMultiLocation()
val xcmVersion = XcmVersion.V4
val pahVersionedLocation = polkadotAssetHubLocation.toRelative().versionedXcm(xcmVersion)
// Compose limited_teleport_assets call to execute on Polkadot
val call = polkadotRuntime.composeCall(
moduleName = polkadotRuntime.metadata.xcmPalletName(),
callName = "limited_teleport_assets",
arguments = mapOf(
"dest" to pahVersionedLocation.toEncodableInstance(),
"beneficiary" to beneficiary.versionedXcm(xcmVersion).toEncodableInstance(),
"assets" to MultiAssets(assets).versionedXcm(xcmVersion).toEncodableInstance(),
"fee_asset_item" to BigInteger.ZERO,
"weight_limit" to WeightLimit.Unlimited.toEncodableInstance()
)
)
// Dry run call execution
val dryRunEffects = dryRunApi.dryRunCall(
originCaller = OriginCaller.System.Signed(origin),
call = call,
chainId = polkadot.id,
xcmResultsVersion = XcmVersion.V4
)
.getOrThrow()
.toResult().getOrThrow()
// Find xcm forwarded to Polkadot Asset Hub
val forwardedXcm = dryRunEffects.forwardedXcms.getByLocation(pahVersionedLocation).first()
println(forwardedXcm)
// Dry run execution of forwarded message on Polkadot Asset Hub
val xcmDryRunEffects = dryRunApi.dryRunXcm(
xcm = forwardedXcm,
originLocation = polkadotLocation.fromPointOfViewOf(polkadotAssetHubLocation).versionedXcm(xcmVersion),
chainId = polkadotAssetHub.id
)
.getOrThrow()
.toResult().getOrThrow()
println(xcmDryRunEffects.emittedEvents)
}
}
@@ -0,0 +1,52 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.utils.average
import io.novafoundation.nova.common.utils.divideToDecimal
import io.novafoundation.nova.runtime.ethereum.gas.LegacyGasPriceProvider
import io.novafoundation.nova.runtime.ethereum.gas.MaxPriorityFeeGasProvider
import io.novafoundation.nova.runtime.ext.Ids
import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import org.junit.Test
import java.math.BigInteger
class GasPriceProviderIntegrationTest : BaseIntegrationTest() {
@Test
fun compareLegacyAndImprovedGasPriceEstimations() = runTest {
val api = chainRegistry.getCallEthereumApiOrThrow(Chain.Ids.MOONBEAM)
val legacy = LegacyGasPriceProvider(api)
val improved = MaxPriorityFeeGasProvider(api)
val legacyStats = mutableSetOf<BigInteger>()
val improvedStats = mutableSetOf<BigInteger>()
api.newHeadsFlow().map {
legacyStats.add(legacy.getGasPrice())
improvedStats.add(improved.getGasPrice())
}
.take(1000)
.collect()
legacyStats.printStats("Legacy")
improvedStats.printStats("Improved")
}
private fun Set<BigInteger>.printStats(name: String) {
val min = min()
val max = max()
Log.d("GasPriceProviderIntegrationTest", """
Stats for $name source
Min: $min
Max: $max
Avg: ${average()}
Max/Min ratio: ${max.divideToDecimal(min)}
""")
}
}
@@ -0,0 +1,247 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.firstLoaded
import io.novafoundation.nova.common.utils.inBackground
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType
import io.novafoundation.nova.feature_governance_api.data.source.SupportedGovernanceOption
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumType
import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumTypeFilter
import io.novafoundation.nova.feature_governance_impl.data.RealGovernanceAdditionalState
import io.novafoundation.nova.runtime.ext.externalApi
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import org.junit.Test
import java.math.BigInteger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
class GovernanceIntegrationTest : BaseIntegrationTest() {
private val accountApi = FeatureUtils.getFeature<AccountFeatureApi>(context, AccountFeatureApi::class.java)
private val governanceApi = FeatureUtils.getFeature<GovernanceFeatureApi>(context, GovernanceFeatureApi::class.java)
@Test
fun shouldRetrieveOnChainReferenda() = runTest {
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val onChainReferendaRepository = source(selectedGovernance).referenda
val referenda = onChainReferendaRepository.getAllOnChainReferenda(chain.id)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, referenda.toString())
}
@Test
fun shouldRetrieveConvictionVotes() = runTest {
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val convictionVotingRepository = source(selectedGovernance).convictionVoting
val accountId = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".toAccountId()
val votes = convictionVotingRepository.votingFor(accountId, chain.id)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, votes.toString())
}
@Test
fun shouldRetrieveTrackLocks() = runTest {
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val convictionVotingRepository = source(selectedGovernance).convictionVoting
val accountId = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".toAccountId()
val fullChainAssetId = FullChainAssetId(chain.id, chain.utilityAsset.id)
val trackLocks = convictionVotingRepository.trackLocksFlow(accountId, fullChainAssetId).first()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, trackLocks.toString())
}
@Test
fun shouldRetrieveReferendaTracks() = runTest {
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val onChainReferendaRepository = source(selectedGovernance).referenda
val tracks = onChainReferendaRepository.getTracks(chain.id)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, tracks.toString())
}
@Test
fun shouldRetrieveDomainReferendaPreviews() = runTest {
val accountRepository = accountApi.provideAccountRepository()
val referendaListInteractor = governanceApi.referendaListInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val metaAccount = accountRepository.getSelectedMetaAccount()
val accountId = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".toAccountId()
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val filterFlow: Flow<ReferendumTypeFilter> = flow {
val referendaFilter = ReferendumTypeFilter(ReferendumType.ALL)
emit(referendaFilter)
}
val referendaByGroup = referendaListInteractor.referendaListStateFlow(metaAccount, accountId, selectedGovernance, this, filterFlow).firstLoaded()
val referenda = referendaByGroup.groupedReferenda.values.flatten()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, referenda.joinToString("\n"))
}
@Test
fun shouldRetrieveDomainReferendumDetails() = runTest {
val referendumDetailsInteractor = governanceApi.referendumDetailsInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val accountId = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".toAccountId()
val referendumId = ReferendumId(BigInteger.ZERO)
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val referendumDetails = referendumDetailsInteractor.referendumDetailsFlow(referendumId, selectedGovernance, accountId, CoroutineScope(Dispatchers.Main))
.first()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, referendumDetails.toString())
val callDetails = referendumDetailsInteractor.detailsFor(
preImage = referendumDetails?.onChainMetadata!!.preImage!!,
chain = chain
)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, callDetails.toString())
}
@Test
fun shouldRetrieveVoters() = runTest {
val interactor = governanceApi.referendumVotersInteractor
val referendumId = ReferendumId(BigInteger.ZERO)
val referendumVoters = interactor.votersFlow(referendumId, VoteType.AYE)
.first()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, referendumVoters.toString())
}
@Test
fun shouldFetchDelegatesList() = runTest {
val interactor = governanceApi.delegateListInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val chain = kusama()
val delegates = interactor.getDelegates(
governanceOption = supportedGovernanceOption(chain, Chain.Governance.V2),
scope = this
)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, delegates.toString())
}
@Test
fun shouldFetchDelegateDetails() = runTest {
val delegateAccountId = "DCZyhphXsRLcW84G9WmWEXtAA8DKGtVGSFZLJYty8Ajjyfa".toAccountId() // ChaosDAO
val interactor = governanceApi.delegateDetailsInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val delegate = interactor.delegateDetailsFlow(delegateAccountId)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, delegate.toString())
}
@Test
fun shouldFetchChooseTrackData() = runTest {
val interactor = governanceApi.newDelegationChooseTrackInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val trackData = interactor.observeNewDelegationTrackData().first()
Log.d(
this@GovernanceIntegrationTest.LOG_TAG,
"""
Available: ${trackData.trackPartition.available.size}
Already voted: ${trackData.trackPartition.alreadyVoted.size}
Already delegated: ${trackData.trackPartition.alreadyDelegated.size}
Presets: ${trackData.presets}
""".trimIndent()
)
}
@Test
fun shouldFetchDelegators() = runTest {
val delegateAddress = "DCZyhphXsRLcW84G9WmWEXtAA8DKGtVGSFZLJYty8Ajjyfa" // ChaosDAO
val interactor = governanceApi.delegateDelegatorsInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val delegators = interactor.delegatorsFlow(delegateAddress.toAccountId()).first()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, delegators.toString())
}
private suspend fun source(supportedGovernance: SupportedGovernanceOption) = governanceApi.governanceSourceRegistry.sourceFor(supportedGovernance)
private fun supportedGovernanceOption(chain: Chain, governance: Chain.Governance) =
SupportedGovernanceOption(
ChainWithAsset(chain, chain.utilityAsset),
RealGovernanceAdditionalState(
governance,
false
)
)
private suspend fun chain(): Chain = chainRegistry.currentChains.map { chains ->
chains.find { it.governance.isNotEmpty() }
}
.filterNotNull()
.first()
private suspend fun kusama(): Chain = chainRegistry.currentChains.mapNotNull { chains ->
chains.find { it.externalApi<Chain.ExternalApi.GovernanceDelegations>() != null }
}.first()
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,60 @@
package io.novafoundation.nova
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_nft_api.NftFeatureApi
import io.novafoundation.nova.feature_nft_api.data.model.isFullySynced
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.runBlocking
import org.junit.Test
class NftFullSyncIntegrationTest {
private val nftApi = FeatureUtils.getFeature<NftFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
NftFeatureApi::class.java
)
private val accountApi = FeatureUtils.getFeature<AccountFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
AccountFeatureApi::class.java
)
private val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(
ApplicationProvider.getApplicationContext<Context>(),
RuntimeApi::class.java
)
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
@Test
fun testFullSyncIntegration(): Unit = runBlocking {
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
val metaAccount = accountApi.accountUseCase().getSelectedMetaAccount()
val nftRepository = nftApi.nftRepository
nftRepository.initialNftSync(metaAccount, true)
nftRepository.allNftFlow(metaAccount)
.map { nfts -> nfts.filter { !it.isFullySynced } }
.takeWhile { it.isNotEmpty() }
.onEach { unsyncedNfts ->
unsyncedNfts.forEach { nftRepository.fullNftSync(it) }
}
.onCompletion {
print("Full sync done")
}
.launchIn(this)
}
}
@@ -0,0 +1,181 @@
package io.novafoundation.nova
import android.content.Context
import android.util.Log
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId
import io.novafoundation.nova.common.data.network.runtime.binding.bindBoolean
import io.novafoundation.nova.common.data.network.runtime.binding.bindString
import io.novafoundation.nova.common.data.network.runtime.binding.cast
import io.novafoundation.nova.common.data.network.runtime.binding.getTyped
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.uniques
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.ext.addressOf
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder
import io.novafoundation.nova.runtime.storage.source.query.multi
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Test
import java.math.BigInteger
data class UniquesClass(
val id: BigInteger,
val metadata: Metadata?,
val details: Details
) {
data class Metadata(
val deposit: BigInteger,
val data: String,
)
data class Details(
val instances: BigInteger,
val frozen: Boolean
)
}
data class UniquesInstance(
val collection: UniquesClass,
val id: BigInteger,
val metadata: Metadata?,
val details: Details
) {
data class Metadata(
val data: String,
)
data class Details(
val owner: String,
val frozen: Boolean,
)
}
class NftUniquesIntegrationTest {
private val chainGenesis = "48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a"
private val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(
ApplicationProvider.getApplicationContext<Context>(),
RuntimeApi::class.java
)
private val chainRegistry = runtimeApi.chainRegistry()
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
private val storageRemoteSource = runtimeApi.remoteStorageSource()
@Test
fun testUniquesIntegration(): Unit = runBlocking {
chainRegistry.currentChains.first() // wait till chains are ready
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
val chain = chainRegistry.getChain(chainGenesis)
val accountId = "JGKSibhyZgzY7jEe5a9gdybDEbqNNRSxYyJJmeeycbCbQ5v".toAccountId()
val instances = storageRemoteSource.query(chainGenesis) {
val classesWithInstances = runtime.metadata.uniques().storage("Account").keys(accountId)
.map { (_: AccountId, collection: BigInteger, instance: BigInteger) ->
listOf(collection, instance)
}
val classesIds = classesWithInstances.map { (collection, _) -> collection }.distinct()
val classDetailsDescriptor: MultiQueryBuilder.Descriptor<BigInteger, UniquesClass.Details>
val classMetadatasDescriptor: MultiQueryBuilder.Descriptor<BigInteger, UniquesClass.Metadata?>
val instancesDetailsDescriptor: MultiQueryBuilder.Descriptor<Pair<BigInteger, BigInteger>, UniquesInstance.Details>
val instancesMetadataDescriptor: MultiQueryBuilder.Descriptor<Pair<BigInteger, BigInteger>, UniquesInstance.Metadata?>
val multiQueryResults = multi {
classDetailsDescriptor = runtime.metadata.uniques().storage("Class").querySingleArgKeys(
keysArgs = classesIds,
keyExtractor = { it.component1<BigInteger>() },
binding = { parsedValue ->
val classDetailsStruct = parsedValue.cast<Struct.Instance>()
UniquesClass.Details(
instances = classDetailsStruct.getTyped("instances"),
frozen = classDetailsStruct.getTyped("isFrozen")
)
}
)
classMetadatasDescriptor = runtime.metadata.uniques().storage("ClassMetadataOf").querySingleArgKeys(
keysArgs = classesIds,
keyExtractor = { it.component1<BigInteger>() },
binding = { parsedValue ->
parsedValue?.cast<Struct.Instance>()?.let { classMetadataStruct ->
UniquesClass.Metadata(
deposit = classMetadataStruct.getTyped("deposit"),
data = bindString(classMetadataStruct["data"])
)
}
}
)
instancesDetailsDescriptor = runtime.metadata.uniques().storage("Asset").queryKeys(
keysArgs = classesWithInstances,
keyExtractor = { it.component1<BigInteger>() to it.component2<BigInteger>() },
binding = { parsedValue ->
val instanceDetailsStruct = parsedValue.cast<Struct.Instance>()
UniquesInstance.Details(
owner = chain.addressOf(bindAccountId(instanceDetailsStruct["owner"])),
frozen = bindBoolean(instanceDetailsStruct["isFrozen"])
)
}
)
instancesMetadataDescriptor = runtime.metadata.uniques().storage("InstanceMetadataOf").queryKeys(
keysArgs = classesWithInstances,
keyExtractor = { it.component1<BigInteger>() to it.component2<BigInteger>() },
binding = { parsedValue ->
parsedValue?.cast<Struct.Instance>()?.let {
UniquesInstance.Metadata(
data = bindString(it["data"])
)
}
}
)
}
val classDetails = multiQueryResults[classDetailsDescriptor]
val classMetadatas = multiQueryResults[classMetadatasDescriptor]
val instancesDetails = multiQueryResults[instancesDetailsDescriptor]
val instancesMetadatas = multiQueryResults[instancesMetadataDescriptor]
val classes = classesIds.associateWith { classId ->
UniquesClass(
id = classId,
metadata = classMetadatas[classId],
details = classDetails.getValue(classId)
)
}
classesWithInstances.map { (collectionId, instanceId) ->
val instanceKey = collectionId to instanceId
UniquesInstance(
collection = classes.getValue(collectionId),
id = instanceId,
metadata = instancesMetadatas[instanceKey],
details = instancesDetails.getValue(instanceKey)
)
}
}
Log.d(LOG_TAG, instances.toString())
}
}
@@ -0,0 +1,49 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.Fraction
import io.novafoundation.nova.common.utils.Perbill
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_api.domain.nominationPool.model.PoolId
import io.novafoundation.nova.feature_staking_impl.data.StakingOption
import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState.OptionAdditionalData
import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureComponent
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.rewards.NominationPoolRewardCalculator
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Asset.StakingType
import kotlinx.coroutines.flow.launchIn
import org.junit.Test
class NominationPoolsRewardCalculatorIntegrationTest : BaseIntegrationTest() {
private val stakingFeatureComponent = FeatureUtils.getFeature<StakingFeatureComponent>(context, StakingFeatureApi::class.java)
private val nominationPoolRewardCalculatorFactory = stakingFeatureComponent.nominationPoolRewardCalculatorFactory
private val stakingUpdateSystem = stakingFeatureComponent.stakingUpdateSystem
private val stakingSharedState = stakingFeatureComponent.stakingSharedState
@Test
fun testRewardCalculator() = runTest {
val polkadot = chainRegistry.polkadot()
val stakingOption = StakingOption(
assetWithChain = ChainWithAsset(polkadot, polkadot.utilityAsset),
additional = OptionAdditionalData(StakingType.NOMINATION_POOLS)
)
stakingSharedState.setSelectedOption(stakingOption)
stakingUpdateSystem.start()
.launchIn(this)
val rewardCalculator = nominationPoolRewardCalculatorFactory.create(stakingOption, sharedComputationScope = this)
Log.d("NominationPoolsRewardCalculatorIntegrationTest", "Max APY: ${rewardCalculator.maxAPY}")
Log.d("NominationPoolsRewardCalculatorIntegrationTest", "APY for Nova Pool: ${rewardCalculator.apyFor(54)}")
}
private fun NominationPoolRewardCalculator.apyFor(poolId: Int): Fraction? {
return apyFor(PoolId(poolId))
}
}
@@ -0,0 +1,324 @@
package io.novafoundation.nova
import android.content.Context
import android.util.Log
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.address.AccountIdKey
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.balances
import io.novafoundation.nova.feature_account_api.data.signer.CallExecutionType
import io.novafoundation.nova.feature_account_api.data.signer.NovaSigner
import io.novafoundation.nova.feature_account_api.data.signer.SigningContext
import io.novafoundation.nova.feature_account_api.data.signer.SigningMode
import io.novafoundation.nova.feature_account_api.data.signer.SubmissionHierarchy
import io.novafoundation.nova.feature_account_api.data.signer.setSignerData
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_account_impl.domain.account.model.DefaultMetaAccount
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.nativeTransfer
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.runtime.ext.Geneses
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novasama.substrate_sdk_android.encrypt.SignatureWrapper
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.extrinsic.BatchMode
import io.novasama.substrate_sdk_android.runtime.extrinsic.Nonce
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignedRaw
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.InheritedImplication
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.CheckNonce.Companion.setNonce
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.verifySignature.GeneralTransactionSigner
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.verifySignature.VerifySignature.Companion.setVerifySignature
import io.novasama.substrate_sdk_android.runtime.metadata.callOrNull
import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test
import java.math.BigInteger
/**
* End-to-end integration tests for Pezkuwi chain compatibility.
* These tests verify that:
* 1. Runtime loads correctly with proper types
* 2. Extrinsics can be built
* 3. Fee calculation works
* 4. Transfer extrinsics can be created
*
* Run with: ./gradlew :app:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=io.novafoundation.nova.PezkuwiIntegrationTest
*/
class PezkuwiIntegrationTest : BaseIntegrationTest() {
private val walletApi = FeatureUtils.getFeature<WalletFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
WalletFeatureApi::class.java
)
private val extrinsicBuilderFactory = runtimeApi.provideExtrinsicBuilderFactory()
private val rpcCalls = runtimeApi.rpcCalls()
/**
* Test 1: Verify Pezkuwi Mainnet runtime loads with required types
*/
@Test
fun testPezkuwiMainnetRuntimeTypes() = runTest {
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
val runtime = chainRegistry.getRuntime(chain.id)
// Verify critical types exist
val extrinsicSignature = runtime.typeRegistry["ExtrinsicSignature"]
assertNotNull("ExtrinsicSignature type should exist", extrinsicSignature)
val multiSignature = runtime.typeRegistry["MultiSignature"]
assertNotNull("MultiSignature type should exist", multiSignature)
val multiAddress = runtime.typeRegistry["MultiAddress"]
assertNotNull("MultiAddress type should exist", multiAddress)
val address = runtime.typeRegistry["Address"]
assertNotNull("Address type should exist", address)
println("Pezkuwi Mainnet: All required types present")
}
/**
* Test 2: Verify Pezkuwi Asset Hub runtime loads with required types
*/
@Test
fun testPezkuwiAssetHubRuntimeTypes() = runTest {
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI_ASSET_HUB)
val runtime = chainRegistry.getRuntime(chain.id)
val extrinsicSignature = runtime.typeRegistry["ExtrinsicSignature"]
assertNotNull("ExtrinsicSignature type should exist", extrinsicSignature)
val address = runtime.typeRegistry["Address"]
assertNotNull("Address type should exist", address)
println("Pezkuwi Asset Hub: All required types present")
}
/**
* Test 3: Verify extrinsic builder can be created for Pezkuwi
*/
@Test
fun testPezkuwiExtrinsicBuilderCreation() = runTest {
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
val builder = extrinsicBuilderFactory.create(
chain = chain,
options = io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory.Options(
batchMode = BatchMode.BATCH_ALL
)
)
assertNotNull("ExtrinsicBuilder should be created", builder)
println("Pezkuwi ExtrinsicBuilder created successfully")
}
/**
* Test 4: Verify transfer call can be constructed
*/
@Test
fun testPezkuwiTransferCallConstruction() = runTest {
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
val runtime = chainRegistry.getRuntime(chain.id)
// Check if balances module exists
val balancesModule = runtime.metadata.moduleOrNull("Balances")
assertNotNull("Balances module should exist", balancesModule)
// Check transfer call exists
val hasTransferKeepAlive = balancesModule?.callOrNull("transfer_keep_alive") != null
val hasTransferAllowDeath = balancesModule?.callOrNull("transfer_allow_death") != null ||
balancesModule?.callOrNull("transfer") != null
assertTrue("Transfer call should exist", hasTransferKeepAlive || hasTransferAllowDeath)
println("Pezkuwi transfer call found: transfer_keep_alive=$hasTransferKeepAlive, transfer_allow_death=$hasTransferAllowDeath")
}
/**
* Test 5: Verify signed extensions are properly handled
*/
@Test
fun testPezkuwiSignedExtensions() = runTest {
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
val runtime = chainRegistry.getRuntime(chain.id)
val signedExtensions = runtime.metadata.extrinsic.signedExtensions.map { it.id }
println("Pezkuwi signed extensions: $signedExtensions")
// Verify Pezkuwi-specific extensions
val hasAuthorizeCall = signedExtensions.contains("AuthorizeCall")
println("Has AuthorizeCall extension: $hasAuthorizeCall")
// Standard extensions should also be present
val hasCheckMortality = signedExtensions.contains("CheckMortality")
val hasCheckNonce = signedExtensions.contains("CheckNonce")
assertTrue("CheckMortality should exist", hasCheckMortality)
assertTrue("CheckNonce should exist", hasCheckNonce)
}
/**
* Test 6: Verify utility asset is properly configured
*/
@Test
fun testPezkuwiUtilityAsset() = runTest {
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
val utilityAsset = chain.utilityAsset
assertNotNull("Utility asset should exist", utilityAsset)
println("Pezkuwi utility asset: ${utilityAsset.symbol}, precision: ${utilityAsset.precision}")
}
/**
* Test 7: Build and sign a transfer extrinsic (THIS IS THE CRITICAL TEST)
* This test will catch "TypeReference is null" errors during signing
*/
@Test
fun testPezkuwiBuildSignedTransferExtrinsic() = runTest {
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
val signer = TestSigner()
val builder = extrinsicBuilderFactory.create(
chain = chain,
options = ExtrinsicBuilderFactory.Options(BatchMode.BATCH)
)
// Add transfer call
val recipientAccountId = ByteArray(32) { 2 }
builder.nativeTransfer(accountId = recipientAccountId, amount = BigInteger.ONE)
// Set signer data (this is where TypeReference errors can occur)
try {
with(builder) {
signer.setSignerData(TestSigningContext(chain), SigningMode.SUBMISSION)
}
Log.d("PezkuwiTest", "Signer data set successfully")
} catch (e: Exception) {
Log.e("PezkuwiTest", "Failed to set signer data", e)
fail("Failed to set signer data: ${e.message}")
}
// Build the extrinsic (this is where TypeReference errors can also occur)
try {
val extrinsic = builder.buildExtrinsic()
assertNotNull("Built extrinsic should not be null", extrinsic)
Log.d("PezkuwiTest", "Extrinsic built successfully: ${extrinsic.extrinsicHex}")
println("Pezkuwi: Transfer extrinsic built and signed successfully!")
} catch (e: Exception) {
Log.e("PezkuwiTest", "Failed to build extrinsic", e)
fail("Failed to build extrinsic: ${e.message}\nCause: ${e.cause?.message}")
}
}
/**
* Test 8: Build extrinsic for fee calculation (uses fake signature)
*/
@Test
fun testPezkuwiBuildFeeExtrinsic() = runTest {
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
val signer = TestSigner()
val builder = extrinsicBuilderFactory.create(
chain = chain,
options = ExtrinsicBuilderFactory.Options(BatchMode.BATCH)
)
val recipientAccountId = ByteArray(32) { 2 }
builder.nativeTransfer(accountId = recipientAccountId, amount = BigInteger.ONE)
// Set signer data for FEE mode (uses fake signature)
try {
with(builder) {
signer.setSignerData(TestSigningContext(chain), SigningMode.FEE)
}
val extrinsic = builder.buildExtrinsic()
assertNotNull("Fee extrinsic should not be null", extrinsic)
println("Pezkuwi: Fee extrinsic built successfully!")
} catch (e: Exception) {
Log.e("PezkuwiTest", "Failed to build fee extrinsic", e)
fail("Failed to build fee extrinsic: ${e.message}")
}
}
// Helper extension
private suspend fun ChainRegistry.pezkuwiMainnet(): Chain {
return getChain(Chain.Geneses.PEZKUWI)
}
// Test signer for building extrinsics without real keys
private inner class TestSigner : NovaSigner, GeneralTransactionSigner {
val accountId = ByteArray(32) { 1 }
override suspend fun callExecutionType(): CallExecutionType {
return CallExecutionType.IMMEDIATE
}
override val metaAccount: MetaAccount = DefaultMetaAccount(
id = 0,
globallyUniqueId = "0",
substrateAccountId = accountId,
substrateCryptoType = null,
substratePublicKey = null,
ethereumAddress = null,
ethereumPublicKey = null,
isSelected = true,
name = "test",
type = LightMetaAccount.Type.SECRETS,
chainAccounts = emptyMap(),
status = LightMetaAccount.Status.ACTIVE,
parentMetaId = null
)
override suspend fun getSigningHierarchy(): SubmissionHierarchy {
return SubmissionHierarchy(metaAccount, CallExecutionType.IMMEDIATE)
}
override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw {
error("Not implemented")
}
context(ExtrinsicBuilder)
override suspend fun setSignerDataForSubmission(context: SigningContext) {
setNonce(BigInteger.ZERO)
setVerifySignature(this@TestSigner, accountId)
}
context(ExtrinsicBuilder)
override suspend fun setSignerDataForFee(context: SigningContext) {
setSignerDataForSubmission(context)
}
override suspend fun submissionSignerAccountId(chain: Chain): AccountId {
return accountId
}
override suspend fun maxCallsPerTransaction(): Int? {
return null
}
override suspend fun signInheritedImplication(
inheritedImplication: InheritedImplication,
accountId: AccountId
): SignatureWrapper {
// Return a fake Sr25519 signature for testing
return SignatureWrapper.Sr25519(ByteArray(64))
}
}
private class TestSigningContext(override val chain: Chain) : SigningContext {
override suspend fun getNonce(accountId: AccountIdKey): Nonce {
return Nonce.ZERO
}
}
}
@@ -0,0 +1,430 @@
package io.novafoundation.nova
import android.content.Context
import android.util.Log
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.address.AccountIdKey
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.deriveSeed32
import io.novafoundation.nova.feature_account_api.data.signer.CallExecutionType
import io.novafoundation.nova.feature_account_api.data.signer.NovaSigner
import io.novafoundation.nova.feature_account_api.data.signer.SigningContext
import io.novafoundation.nova.feature_account_api.data.signer.SigningMode
import io.novafoundation.nova.feature_account_api.data.signer.SubmissionHierarchy
import io.novafoundation.nova.feature_account_api.data.signer.setSignerData
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_account_impl.domain.account.model.DefaultMetaAccount
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.TransferMode
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.nativeTransfer
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.runtime.ext.Geneses
import io.novafoundation.nova.runtime.ext.requireGenesisHash
import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory
import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novafoundation.nova.runtime.network.rpc.RpcCalls
import io.novasama.substrate_sdk_android.encrypt.EncryptionType
import io.novasama.substrate_sdk_android.encrypt.MultiChainEncryption
import io.novasama.substrate_sdk_android.encrypt.SignatureWrapper
import io.novasama.substrate_sdk_android.encrypt.keypair.Keypair
import io.novasama.substrate_sdk_android.encrypt.keypair.substrate.SubstrateKeypairFactory
import io.novasama.substrate_sdk_android.encrypt.seed.substrate.SubstrateSeedFactory
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.extrinsic.BatchMode
import io.novasama.substrate_sdk_android.runtime.extrinsic.Nonce
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.KeyPairSigner
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignedRaw
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.InheritedImplication
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.CheckNonce.Companion.setNonce
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.verifySignature.GeneralTransactionSigner
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.verifySignature.VerifySignature.Companion.setVerifySignature
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.signingPayload
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
import io.novafoundation.nova.sr25519.BizinikiwSr25519
import org.junit.Assert.assertNotNull
import org.junit.Assert.fail
import org.junit.Test
import java.math.BigInteger
/**
* LIVE TRANSFER TEST - Transfers real HEZ tokens on Pezkuwi mainnet!
*
* Sender: 5DXv3Dc5xELckTgcYa2dm1TSZPgqDPxVDW3Cid4ALWpVjY3w
* Recipient: 5HdY6U2UQF8wPwczP3SoQz28kQu1WJSBqxKGePUKG4M5QYdV
* Amount: 5 HEZ
*
* Run with: ./gradlew :app:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=io.novafoundation.nova.PezkuwiLiveTransferTest
*/
class PezkuwiLiveTransferTest : BaseIntegrationTest() {
companion object {
// Test wallet mnemonic
private const val TEST_MNEMONIC = "crucial surge north silly divert throw habit fury zebra fabric tank output"
// Sender address (derived from mnemonic)
private const val SENDER_ADDRESS = "5DXv3Dc5xELckTgcYa2dm1TSZPgqDPxVDW3Cid4ALWpVjY3w"
// Recipient address
private const val RECIPIENT_ADDRESS = "5HdY6U2UQF8wPwczP3SoQz28kQu1WJSBqxKGePUKG4M5QYdV"
// Amount: 5 HEZ (with 12 decimals)
private val TRANSFER_AMOUNT = BigInteger("5000000000000") // 5 * 10^12
}
private val walletApi = FeatureUtils.getFeature<WalletFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
WalletFeatureApi::class.java
)
private val extrinsicBuilderFactory = runtimeApi.provideExtrinsicBuilderFactory()
private val rpcCalls = runtimeApi.rpcCalls()
/**
* LIVE TEST: Build and submit a real transfer on Pezkuwi mainnet
*/
@Test(timeout = 120000) // 2 minute timeout
fun testLiveTransfer5HEZ() = runTest {
Log.d("LiveTransferTest", "=== STARTING LIVE TRANSFER TEST ===")
Log.d("LiveTransferTest", "Sender: $SENDER_ADDRESS")
Log.d("LiveTransferTest", "Recipient: $RECIPIENT_ADDRESS")
Log.d("LiveTransferTest", "Amount: 5 HEZ")
// Request full sync for Pezkuwi chain specifically
Log.d("LiveTransferTest", "Requesting full sync for Pezkuwi chain...")
chainRegistry.enableFullSync(Chain.Geneses.PEZKUWI)
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
Log.d("LiveTransferTest", "Chain: ${chain.name}")
// Create keypair from mnemonic
val keypair = createKeypairFromMnemonic(TEST_MNEMONIC)
Log.d("LiveTransferTest", "Keypair created, public key: ${keypair.publicKey.toHexString()}")
// Create signer
val signer = RealSigner(keypair, chain)
Log.d("LiveTransferTest", "Signer created")
// Get recipient account ID
val recipientAccountId = RECIPIENT_ADDRESS.toAccountId()
Log.d("LiveTransferTest", "Recipient AccountId: ${recipientAccountId.toHexString()}")
// Get current nonce using sender's SS58 address
val nonce = try {
rpcCalls.getNonce(chain.id, SENDER_ADDRESS)
} catch (e: Exception) {
Log.e("LiveTransferTest", "Failed to get nonce, using 0", e)
BigInteger.ZERO
}
Log.d("LiveTransferTest", "Current nonce: $nonce")
// Create extrinsic builder
val builder = extrinsicBuilderFactory.create(
chain = chain,
options = ExtrinsicBuilderFactory.Options(BatchMode.BATCH)
)
Log.d("LiveTransferTest", "ExtrinsicBuilder created")
// Use default MORTAL era (same as @pezkuwi/api)
Log.d("LiveTransferTest", "Using MORTAL era (default, same as @pezkuwi/api)")
// Add transfer call with KEEP_ALIVE mode (same as @pezkuwi/api uses)
builder.nativeTransfer(accountId = recipientAccountId, amount = TRANSFER_AMOUNT, mode = TransferMode.KEEP_ALIVE)
Log.d("LiveTransferTest", "Transfer call added")
// Set signer data for SUBMISSION (this is where TypeReference errors occur!)
try {
with(builder) {
signer.setSignerData(RealSigningContext(chain, nonce), SigningMode.SUBMISSION)
}
Log.d("LiveTransferTest", "Signer data set successfully")
} catch (e: Exception) {
Log.e("LiveTransferTest", "FAILED to set signer data!", e)
fail("Failed to set signer data: ${e.message}\nCause: ${e.cause?.message}\nStack: ${e.stackTraceToString()}")
return@runTest
}
// Build the extrinsic
val extrinsic = try {
builder.buildExtrinsic()
} catch (e: Exception) {
Log.e("LiveTransferTest", "FAILED to build extrinsic!", e)
fail("Failed to build extrinsic: ${e.message}\nCause: ${e.cause?.message}\nStack: ${e.stackTraceToString()}")
return@runTest
}
assertNotNull("Extrinsic should not be null", extrinsic)
Log.d("LiveTransferTest", "Extrinsic built: ${extrinsic.extrinsicHex}")
// Submit the extrinsic
Log.d("LiveTransferTest", "Submitting extrinsic to network...")
try {
val hash = rpcCalls.submitExtrinsic(chain.id, extrinsic)
Log.d("LiveTransferTest", "=== TRANSFER SUBMITTED SUCCESSFULLY ===")
Log.d("LiveTransferTest", "Transaction hash: $hash")
println("LIVE TRANSFER SUCCESS! TX Hash: $hash")
} catch (e: Exception) {
Log.e("LiveTransferTest", "FAILED to submit extrinsic!", e)
fail("Failed to submit extrinsic: ${e.message}")
}
}
/**
* Test to check type resolution in the runtime
*/
@Test(timeout = 120000)
fun testTypeResolution() = runTest {
Log.d("LiveTransferTest", "=== TESTING TYPE RESOLUTION ===")
// Request full sync for Pezkuwi chain
chainRegistry.enableFullSync(Chain.Geneses.PEZKUWI)
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
val runtime = chainRegistry.getRuntime(chain.id)
// Check critical types for extrinsic encoding
val typesToCheck = listOf(
"Address",
"MultiAddress",
"GenericMultiAddress",
"ExtrinsicSignature",
"MultiSignature",
"pezsp_runtime::multiaddress::MultiAddress",
"pezsp_runtime::MultiSignature",
"pezsp_runtime.multiaddress.MultiAddress",
"pezsp_runtime.MultiSignature",
"GenericExtrinsic",
"Extrinsic"
)
val results = mutableListOf<String>()
for (typeName in typesToCheck) {
val type = runtime.typeRegistry[typeName]
val resolved = type?.let {
try {
// Try to get the actual type, not just alias
it.toString()
} catch (e: Exception) {
"ERROR: ${e.message}"
}
}
val status = if (type != null) "FOUND: $resolved" else "MISSING"
results.add(" $typeName: $status")
Log.d("LiveTransferTest", "$typeName: $status")
}
// Check if extrinsic signature type is defined in metadata
val extrinsicMeta = runtime.metadata.extrinsic
Log.d("LiveTransferTest", "Extrinsic version: ${extrinsicMeta.version}")
Log.d("LiveTransferTest", "Signed extensions: ${extrinsicMeta.signedExtensions.map { it.id }}")
// Log signed extension IDs
for (ext in extrinsicMeta.signedExtensions) {
Log.d("LiveTransferTest", "Extension: ${ext.id}")
}
// Just log the extension names - type access might be restricted
Log.d("LiveTransferTest", "Signed extensions count: ${extrinsicMeta.signedExtensions.size}")
// Log the extrinsic address type if available
Log.d("LiveTransferTest", "RuntimeFactory diagnostics: ${io.novafoundation.nova.runtime.multiNetwork.runtime.RuntimeFactory.lastDiagnostics}")
println("Type resolution results:\n${results.joinToString("\n")}")
}
/**
* Test fee calculation (doesn't submit, just builds for fee estimation)
*/
@Test(timeout = 120000)
fun testFeeCalculation() = runTest {
Log.d("LiveTransferTest", "=== TESTING FEE CALCULATION ===")
// Request full sync for Pezkuwi chain
chainRegistry.enableFullSync(Chain.Geneses.PEZKUWI)
val chain = chainRegistry.getChain(Chain.Geneses.PEZKUWI)
// First, log type registry state
val runtime = chainRegistry.getRuntime(chain.id)
Log.d("LiveTransferTest", "TypeRegistry has ExtrinsicSignature: ${runtime.typeRegistry["ExtrinsicSignature"] != null}")
Log.d("LiveTransferTest", "TypeRegistry has MultiSignature: ${runtime.typeRegistry["MultiSignature"] != null}")
Log.d("LiveTransferTest", "TypeRegistry has Address: ${runtime.typeRegistry["Address"] != null}")
Log.d("LiveTransferTest", "TypeRegistry has MultiAddress: ${runtime.typeRegistry["MultiAddress"] != null}")
val keypair = createKeypairFromMnemonic(TEST_MNEMONIC)
val signer = RealSigner(keypair, chain)
val recipientAccountId = RECIPIENT_ADDRESS.toAccountId()
val builder = extrinsicBuilderFactory.create(
chain = chain,
options = ExtrinsicBuilderFactory.Options(BatchMode.BATCH)
)
builder.nativeTransfer(accountId = recipientAccountId, amount = TRANSFER_AMOUNT)
// Set signer data for FEE mode
try {
with(builder) {
signer.setSignerData(RealSigningContext(chain, BigInteger.ZERO), SigningMode.FEE)
}
Log.d("LiveTransferTest", "Signer data set, building extrinsic...")
val extrinsic = builder.buildExtrinsic()
assertNotNull("Fee extrinsic should not be null", extrinsic)
Log.d("LiveTransferTest", "Extrinsic built, getting hex...")
// The error happens when accessing extrinsicHex
try {
val hex = extrinsic.extrinsicHex
Log.d("LiveTransferTest", "Fee extrinsic built: $hex")
println("Fee calculation test PASSED!")
} catch (e: Exception) {
Log.e("LiveTransferTest", "FAILED accessing extrinsicHex!", e)
fail("Failed to get extrinsic hex: ${e.message}\nCause: ${e.cause?.message}\nStack: ${e.stackTraceToString()}")
}
} catch (e: Exception) {
Log.e("LiveTransferTest", "Fee calculation FAILED!", e)
fail("Fee calculation failed: ${e.message}\nCause: ${e.cause?.message}")
}
}
// Helper to create keypair from mnemonic
private fun createKeypairFromMnemonic(mnemonic: String): Keypair {
val seedResult = SubstrateSeedFactory.deriveSeed32(mnemonic, password = null)
return SubstrateKeypairFactory.generate(EncryptionType.SR25519, seedResult.seed)
}
// Real signer using actual keypair with bizinikiwi context
private inner class RealSigner(
private val keypair: Keypair,
private val chain: Chain
) : NovaSigner, GeneralTransactionSigner {
val accountId: ByteArray = keypair.publicKey
// Generate proper 96-byte keypair using BizinikiwSr25519 native library
// This gives us the correct 64-byte secret key format for signing
private val bizinikiwKeypair: ByteArray by lazy {
val seedResult = SubstrateSeedFactory.deriveSeed32(TEST_MNEMONIC, password = null)
BizinikiwSr25519.keypairFromSeed(seedResult.seed)
}
// Extract 64-byte secret key (32-byte scalar + 32-byte nonce)
private val bizinikiwSecretKey: ByteArray by lazy {
BizinikiwSr25519.secretKeyFromKeypair(bizinikiwKeypair)
}
// Extract 32-byte public key
private val bizinikiwPublicKey: ByteArray by lazy {
BizinikiwSr25519.publicKeyFromKeypair(bizinikiwKeypair)
}
private val keyPairSigner = KeyPairSigner(
keypair,
MultiChainEncryption.Substrate(EncryptionType.SR25519)
)
override suspend fun callExecutionType(): CallExecutionType {
return CallExecutionType.IMMEDIATE
}
override val metaAccount: MetaAccount = DefaultMetaAccount(
id = 0,
globallyUniqueId = "test-wallet",
substrateAccountId = accountId,
substrateCryptoType = null,
substratePublicKey = keypair.publicKey,
ethereumAddress = null,
ethereumPublicKey = null,
isSelected = true,
name = "Test Wallet",
type = LightMetaAccount.Type.SECRETS,
chainAccounts = emptyMap(),
status = LightMetaAccount.Status.ACTIVE,
parentMetaId = null
)
override suspend fun getSigningHierarchy(): SubmissionHierarchy {
return SubmissionHierarchy(metaAccount, CallExecutionType.IMMEDIATE)
}
override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw {
return keyPairSigner.signRaw(payload)
}
context(ExtrinsicBuilder)
override suspend fun setSignerDataForSubmission(context: SigningContext) {
val nonce = context.getNonce(AccountIdKey(accountId))
setNonce(nonce)
setVerifySignature(this@RealSigner, accountId)
}
context(ExtrinsicBuilder)
override suspend fun setSignerDataForFee(context: SigningContext) {
setSignerDataForSubmission(context)
}
override suspend fun submissionSignerAccountId(chain: Chain): AccountId {
return accountId
}
override suspend fun maxCallsPerTransaction(): Int? {
return null
}
override suspend fun signInheritedImplication(
inheritedImplication: InheritedImplication,
accountId: AccountId
): SignatureWrapper {
// Get the SDK's signing payload (SCALE format - same as @pezkuwi/api)
val sdkPayloadBytes = inheritedImplication.signingPayload()
Log.d("LiveTransferTest", "=== SIGNING PAYLOAD (SDK - SCALE) ===")
Log.d("LiveTransferTest", "SDK Payload hex: ${sdkPayloadBytes.toHexString()}")
Log.d("LiveTransferTest", "SDK Payload length: ${sdkPayloadBytes.size} bytes")
// Debug: show first bytes to verify format
if (sdkPayloadBytes.size >= 42) {
val callData = sdkPayloadBytes.copyOfRange(0, 42)
val extensions = sdkPayloadBytes.copyOfRange(42, sdkPayloadBytes.size)
Log.d("LiveTransferTest", "Call data (42 bytes): ${callData.toHexString()}")
Log.d("LiveTransferTest", "Extensions (${extensions.size} bytes): ${extensions.toHexString()}")
}
// Use BizinikiwSr25519 native library with "bizinikiwi" signing context
Log.d("LiveTransferTest", "=== USING BIZINIKIWI CONTEXT ===")
Log.d("LiveTransferTest", "Bizinikiwi public key: ${bizinikiwPublicKey.toHexString()}")
Log.d("LiveTransferTest", "Bizinikiwi secret key size: ${bizinikiwSecretKey.size} bytes")
val signatureBytes = BizinikiwSr25519.sign(
publicKey = bizinikiwPublicKey,
secretKey = bizinikiwSecretKey,
message = sdkPayloadBytes
)
Log.d("LiveTransferTest", "=== SIGNATURE PRODUCED ===")
Log.d("LiveTransferTest", "Signature bytes: ${signatureBytes.toHexString()}")
Log.d("LiveTransferTest", "Signature length: ${signatureBytes.size} bytes")
// Verify the signature locally before sending
val verifyResult = BizinikiwSr25519.verify(signatureBytes, sdkPayloadBytes, bizinikiwPublicKey)
Log.d("LiveTransferTest", "Local verification: $verifyResult")
return SignatureWrapper.Sr25519(signatureBytes)
}
}
private class RealSigningContext(
override val chain: Chain,
private val nonceValue: BigInteger
) : SigningContext {
override suspend fun getNonce(accountId: AccountIdKey): Nonce {
return Nonce.ZERO + nonceValue
}
}
private fun ByteArray.toHexString(): String {
return joinToString("") { "%02x".format(it) }
}
}
@@ -0,0 +1,71 @@
package io.novafoundation.nova
import android.util.Log
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.domain.ExtendedLoadingState
import io.novafoundation.nova.common.utils.inBackground
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.AggregatedStakingDashboardOption
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.StakingDashboard
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.isSyncing
import kotlinx.coroutines.flow.launchIn
import org.junit.Test
import java.lang.reflect.Type
class StakingDashboardIntegrationTest: BaseIntegrationTest() {
private val stakingApi = FeatureUtils.getFeature<StakingFeatureApi>(context, StakingFeatureApi::class.java)
private val interactor = stakingApi.dashboardInteractor
private val updateSystem = stakingApi.dashboardUpdateSystem
private val gson = GsonBuilder()
.registerTypeHierarchyAdapter(AggregatedStakingDashboardOption::class.java, AggregatedStakingDashboardOptionDesirializer())
.create()
@Test
fun syncStakingDashboard() = runTest {
updateSystem.start()
.inBackground()
.launchIn(this)
interactor.stakingDashboardFlow()
.inBackground()
.collect(::logDashboard)
}
private fun logDashboard(dashboard: ExtendedLoadingState<StakingDashboard>) {
if (dashboard !is ExtendedLoadingState.Loaded) return
val serialized = gson.toJson(dashboard)
val message = """
Dashboard state:
Syncing items: ${dashboard.data.syncingItemsCount()}
$serialized
""".trimIndent()
Log.d("StakingDashboardIntegrationTest", message)
}
private class AggregatedStakingDashboardOptionDesirializer : JsonSerializer<AggregatedStakingDashboardOption<*>> {
override fun serialize(src: AggregatedStakingDashboardOption<*>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonObject().apply {
add("chain", JsonPrimitive(src.chain.name))
add("stakingState", context.serialize(src.stakingState))
add("syncing", context.serialize(src.syncingStage))
}
}
}
private fun StakingDashboard.syncingItemsCount(): Int {
return withoutStake.count { it.syncingStage.isSyncing() } + hasStake.count { it.syncingStage.isSyncing() }
}
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository.AutomationAction
import io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository.OptimalAutomationRequest
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.findChain
import kotlinx.coroutines.runBlocking
import org.junit.Test
import java.math.BigInteger
class TuringAutomationIntegrationTest : BaseIntegrationTest() {
private val stakingApi = FeatureUtils.getFeature<StakingFeatureApi>(context, StakingFeatureApi::class.java)
private val automationTasksRepository = stakingApi.turingAutomationRepository
@Test
fun calculateOptimalAutoCompounding(){
runBlocking {
val chain = chainRegistry.findTuringChain()
val request = OptimalAutomationRequest(
collator = "6AEG2WKRVvZteWWT3aMkk2ZE21FvURqiJkYpXimukub8Zb9C",
amount = BigInteger("1000000000000")
)
val response = automationTasksRepository.calculateOptimalAutomation(chain.id, request)
Log.d(LOG_TAG, response.toString())
}
}
@Test
fun calculateAutoCompoundExecutionFees(){
runBlocking {
val chain = chainRegistry.findTuringChain()
val fees = automationTasksRepository.getTimeAutomationFees(chain.id, AutomationAction.AUTO_COMPOUND_DELEGATED_STAKE, executions = 1)
Log.d(LOG_TAG, fees.toString())
}
}
private suspend fun ChainRegistry.findTuringChain() = findChain { it.name == "Turing" }!!
}
@@ -0,0 +1,137 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.second
import io.novafoundation.nova.core.ethereum.Web3Api
import io.novafoundation.nova.core.ethereum.log.Topic
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.ethereum.contract.base.querySingle
import io.novafoundation.nova.runtime.ethereum.contract.erc20.Erc20Queries
import io.novafoundation.nova.runtime.ethereum.contract.erc20.Erc20Standard
import io.novafoundation.nova.runtime.ethereum.sendSuspend
import io.novafoundation.nova.runtime.multiNetwork.getEthereumApiOrThrow
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.web3j.abi.EventEncoder
import org.web3j.abi.TypeEncoder
import org.web3j.abi.datatypes.Address
import org.web3j.protocol.core.DefaultBlockParameterName
import java.math.BigInteger
class Erc20Transfer(
val txHash: String,
val blockNumber: String,
val from: String,
val to: String,
val contract: String,
val amount: BigInteger,
)
class Web3jServiceIntegrationTest : BaseIntegrationTest() {
@Test
fun shouldFetchBalance(): Unit = runBlocking {
val web3j = moonbeamWeb3j()
val balance = web3j.ethGetBalance("0xf977814e90da44bfa03b6295a0616a897441acec", DefaultBlockParameterName.LATEST).sendSuspend()
Log.d(LOG_TAG, balance.balance.toString())
}
@Test
fun shouldFetchComplexStructure(): Unit = runBlocking {
val web3j = moonbeamWeb3j()
val block = web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, true).sendSuspend()
Log.d(LOG_TAG, block.block.hash)
}
@Test
fun shouldSubscribeToNewHeadEvents(): Unit = runBlocking {
val web3j = moonbeamWeb3j()
val newHead = web3j.newHeadsNotifications().asFlow().first()
Log.d(LOG_TAG, "New head appended to chain: ${newHead.params.result.hash}")
}
@Test
fun shouldSubscribeBalances(): Unit = runBlocking {
val web3j = moonbeamWeb3j()
val accountAddress = "0x4A43C16107591AE5Ec904e584ed4Bb05386F98f7"
val moonbeamUsdc = "0x818ec0a7fe18ff94269904fced6ae3dae6d6dc0b"
val balanceUpdates = web3j.erc20BalanceFlow(accountAddress, moonbeamUsdc).take(2).toList()
error("Initial balance: ${balanceUpdates.first()}, new balance: ${balanceUpdates.second()}")
}
private fun Web3Api.erc20BalanceFlow(account: String, contract: String): Flow<Balance> {
return flow {
val erc20 = Erc20Standard().querySingle(contract, web3j = this@erc20BalanceFlow)
val initialBalance = erc20.balanceOfAsync(account).await()
emit(initialBalance)
val changes = accountErcTransfersFlow(account).map {
erc20.balanceOfAsync(account).await()
}
emitAll(changes)
}
}
private fun Web3Api.accountErcTransfersFlow(address: String): Flow<Erc20Transfer> {
val addressTopic = TypeEncoder.encode(Address(address))
val transferEvent = Erc20Queries.TRANSFER_EVENT
val transferEventSignature = EventEncoder.encode(transferEvent)
val contractAddresses = emptyList<String>() // everything
val erc20SendTopic = listOf(
Topic.Single(transferEventSignature), // zero-th topic is event signature
Topic.AnyOf(addressTopic), // our account as `from`
)
val erc20ReceiveTopic = listOf(
Topic.Single(transferEventSignature), // zero-th topic is event signature
Topic.Any, // anyone is `from`
Topic.AnyOf(addressTopic) // out account as `to`
)
val receiveTransferNotifications = logsNotifications(contractAddresses, erc20ReceiveTopic)
val sendTransferNotifications = logsNotifications(contractAddresses, erc20SendTopic)
val transferNotifications = merge(receiveTransferNotifications, sendTransferNotifications)
return transferNotifications.map { logNotification ->
val log = logNotification.params.result
val contract = log.address
val event = Erc20Queries.parseTransferEvent(log)
Erc20Transfer(
txHash = log.transactionHash,
blockNumber = log.blockNumber,
from = event.from.value,
to = event.to.value,
contract = contract,
amount = event.amount.value,
)
}
}
private suspend fun moonbeamWeb3j(): Web3Api {
val moonbeamChainId = "fe58ea77779b7abda7da4ec526d14db9b1e9cd40a217c34892af80a9b332b76d"
return chainRegistry.getEthereumApiOrThrow(moonbeamChainId, Chain.Node.ConnectionType.WSS)
}
}
@@ -0,0 +1,164 @@
package io.novafoundation.nova.balances
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.google.gson.Gson
import io.novafoundation.nova.common.data.network.runtime.binding.AccountInfo
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountInfo
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.fromJson
import io.novafoundation.nova.common.utils.hasModule
import io.novafoundation.nova.common.utils.system
import io.novafoundation.nova.core.model.CryptoType
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent
import io.novafoundation.nova.feature_account_impl.domain.account.model.DefaultMetaAccount
import io.novafoundation.nova.runtime.BuildConfig.TEST_CHAINS_URL
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.extrinsic.systemRemark
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import io.novafoundation.nova.runtime.multiNetwork.getSocket
import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
import io.novasama.substrate_sdk_android.wsrpc.networkStateFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.math.BigInteger
import java.math.BigInteger.ZERO
import java.net.URL
import kotlin.time.Duration.Companion.seconds
@RunWith(Parameterized::class)
class BalancesIntegrationTest(
private val testChainId: String,
private val testChainName: String,
private val testAccount: String
) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): List<Array<String?>> {
val arrayOfNetworks: Array<TestData> = Gson().fromJson(URL(TEST_CHAINS_URL).readText())
return arrayOfNetworks.map { arrayOf(it.chainId, it.name, it.account) }
}
class TestData(
val chainId: String,
val name: String,
val account: String?
)
}
private val maxAmount = BigInteger.valueOf(10).pow(30)
private val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(
ApplicationProvider.getApplicationContext<Context>(),
RuntimeApi::class.java
)
private val accountApi = FeatureUtils.getFeature<AccountFeatureComponent>(
ApplicationProvider.getApplicationContext<Context>(),
AccountFeatureApi::class.java
)
private val chainRegistry = runtimeApi.chainRegistry()
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
private val remoteStorage = runtimeApi.remoteStorageSource()
private val extrinsicService = accountApi.extrinsicService()
@Before
fun before() = runBlocking {
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
}
@Test
fun testBalancesLoading() = runBlocking(Dispatchers.Default) {
val chains = chainRegistry.getChain(testChainId)
val freeBalance = testBalancesInChainAsync(chains, testAccount)?.data?.free ?: error("Balance was null")
assertTrue("Free balance: $freeBalance is less than $maxAmount", maxAmount > freeBalance)
assertTrue("Free balance: $freeBalance is greater than 0", ZERO < freeBalance)
}
@Test
fun testFeeLoading() = runBlocking(Dispatchers.Default) {
val chains = chainRegistry.getChain(testChainId)
testFeeLoadingAsync(chains)
Unit
}
private suspend fun testBalancesInChainAsync(chain: Chain, currentAccount: String): AccountInfo? {
return coroutineScope {
try {
withTimeout(80.seconds) {
remoteStorage.query(
chainId = chain.id,
keyBuilder = { it.metadata.system().storage("Account").storageKey(it, currentAccount.fromHex()) },
binding = { scale, runtime -> scale?.let { bindAccountInfo(scale, runtime) } }
)
}
} catch (e: Exception) {
throw Exception("Socket state: ${chainRegistry.getSocket(chain.id).networkStateFlow().first()}, error: ${e.message}", e)
}
}
}
private suspend fun testFeeLoadingAsync(chain: Chain) {
return coroutineScope {
withTimeout(80.seconds) {
extrinsicService.estimateFee(chain, testTransactionOrigin()) {
systemRemark(byteArrayOf(0))
val haveBatch = runtime.metadata.hasModule("Utility")
if (haveBatch) {
systemRemark(byteArrayOf(0))
}
}
}
}
}
private fun testTransactionOrigin(): TransactionOrigin = TransactionOrigin.Wallet(
createTestMetaAccount()
)
private fun createTestMetaAccount(): MetaAccount {
val metaAccount = DefaultMetaAccount(
id = 0,
globallyUniqueId = MetaAccountLocal.generateGloballyUniqueId(),
substratePublicKey = testAccount.fromHex(),
substrateCryptoType = CryptoType.SR25519,
substrateAccountId = testAccount.fromHex(),
ethereumAddress = testAccount.fromHex(),
ethereumPublicKey = testAccount.fromHex(),
isSelected = true,
name = "Test",
type = LightMetaAccount.Type.WATCH_ONLY,
status = LightMetaAccount.Status.ACTIVE,
chainAccounts = emptyMap(),
parentMetaId = null
)
return metaAccount
}
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">[Debug] Pezkuwi</string>
</resources>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">[Dev] Pezkuwi</string>
</resources>
+234
View File
@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="com.google.android.gms.permission.AD_ID"
tools:node="remove" />
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:name="io.novafoundation.nova.app.App"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules"
tools:targetApi="s">
<activity
android:name="io.novafoundation.nova.app.root.presentation.RootActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/Theme.NovaFoundation.Nova"
android:windowSoftInputMode="adjustResize">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/json" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<data
android:host="buy-success"
android:scheme="pezkuwi" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<data
android:host="@string/deep_linking_host"
android:scheme="@string/deep_linking_scheme" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:autoVerify="true">
<data android:host="request" />
<data android:scheme="pezkuwiwallet" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:autoVerify="true">
<data android:host="wc" />
<data android:scheme="pezkuwiwallet" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:autoVerify="true">
<data
android:pathPattern="/.*@2"
android:scheme="wc" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:scheme="http" />
<data android:host="app.pezkuwichain.io" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"/>
<data android:host="@string/branch_io_link_host"/>
<data android:host="@string/branch_io_link_host_alternate"/>
</intent-filter>
</activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor"
android:theme="@style/Theme.NovaFoundation.Nova"
tools:replace="android:theme,screenOrientation" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<receiver
android:name="io.novafoundation.nova.feature_account_impl.presentation.exporting.json.ShareCompletedReceiver"
android:enabled="true"
android:exported="false" />
<receiver
android:name="io.novafoundation.nova.feature_ledger_impl.sdk.connection.usb.UsbLedgerConnection$UsbPermissionReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_USB" />
<service
android:name="io.novafoundation.nova.feature_push_notifications.NovaFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_pezkuwi" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/android_system_accent" />
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="false" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<meta-data
android:name="io.novafoundation.nova.transactions_notification_channel_id"
android:value="@string/transactions_notification_channel_id" />
<meta-data
android:name="io.novafoundation.nova.governance_notification_channel_id"
android:value="@string/governance_notification_channel_id" />
<meta-data
android:name="io.novafoundation.nova.staking_notification_channel_id"
android:value="@string/staking_notification_channel_id" />
<meta-data
android:name="io.novafoundation.nova.multisigs_notification_channel_id"
android:value="@string/multisigs_notification_channel_id" />
<meta-data
android:name="io.branch.sdk.BranchKey"
android:value="${BRANCH_KEY}" />
<meta-data android:name="io.branch.sdk.TestMode" android:value="false" />
</application>
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="text/plain" />
</intent>
<!-- Allow Google Pay feature in WebView -->
<intent>
<action android:name="org.chromium.intent.action.PAY" />
</intent>
<intent>
<action android:name="org.chromium.intent.action.IS_READY_TO_PAY" />
</intent>
<intent>
<action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS" />
</intent>
</queries>
</manifest>
@@ -0,0 +1,100 @@
package io.novafoundation.nova.app
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import com.walletconnect.android.Core
import com.walletconnect.android.CoreClient
import com.walletconnect.android.relay.ConnectionType
import com.walletconnect.web3.wallet.client.Wallet
import com.walletconnect.web3.wallet.client.Web3Wallet
import io.novafoundation.nova.app.di.app.AppComponent
import io.novafoundation.nova.app.di.deps.FeatureHolderManager
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.common.resources.LanguagesHolder
import io.novafoundation.nova.common.utils.coroutines.RootScope
import io.novafoundation.nova.feature_deep_linking.presentation.handling.branchIo.BranchIOLinkHandler
import io.novafoundation.nova.feature_wallet_connect_impl.BuildConfig
import javax.inject.Inject
private const val WC_REDIRECT_URL = "pezkuwiwallet://request"
open class App : Application(), FeatureContainer {
@Inject
lateinit var featureHolderManager: FeatureHolderManager
private lateinit var appComponent: AppComponent
private val languagesHolder: LanguagesHolder = LanguagesHolder()
// App global scope using for processes that should work while app is alive
private val rootScope = RootScope()
override fun attachBaseContext(base: Context) {
val contextManager = ContextManager.getInstanceOrInit(base, languagesHolder)
super.attachBaseContext(contextManager.setLocale(base))
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val contextManager = ContextManager.getInstanceOrInit(this, languagesHolder)
contextManager.setLocale(this)
}
override fun onCreate() {
super.onCreate()
val contextManger = ContextManager.getInstanceOrInit(this, languagesHolder)
appComponent = io.novafoundation.nova.app.di.app.DaggerAppComponent
.builder()
.application(this)
.contextManager(contextManger)
.rootScope(rootScope)
.build()
appComponent.inject(this)
BranchIOLinkHandler.Initializer.init(this)
initializeWalletConnect()
}
override fun <T> getFeature(key: Class<*>): T {
return featureHolderManager.getFeature<T>(key)!!
}
override fun releaseFeature(key: Class<*>) {
featureHolderManager.releaseFeature(key)
}
override fun commonApi(): CommonApi {
return appComponent
}
private fun initializeWalletConnect() {
val projectId = BuildConfig.WALLET_CONNECT_PROJECT_ID
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=$projectId"
val connectionType = ConnectionType.MANUAL
val appMetaData = Core.Model.AppMetaData(
name = "Pezkuwi Wallet",
description = "Next-gen wallet for Pezkuwichain and Polkadot ecosystem",
url = "https://pezkuwichain.io/",
icons = listOf("https://raw.githubusercontent.com/pezkuwichain/branding/master/logos/Pezkuwi_Wallet_Sun_Color.png"),
redirect = WC_REDIRECT_URL
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = this, metaData = appMetaData) { error ->
// TODO maybe re-initialize client
}
val initParams = Wallet.Params.Init(core = CoreClient)
Web3Wallet.initialize(initParams) { error ->
// TODO maybe re-initialize client
}
}
}
@@ -0,0 +1,44 @@
package io.novafoundation.nova.app.di.app
import dagger.BindsInstance
import dagger.Component
import io.novafoundation.nova.app.App
import io.novafoundation.nova.app.di.app.navigation.NavigationModule
import io.novafoundation.nova.app.di.deps.ComponentHolderModule
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.modules.CommonModule
import io.novafoundation.nova.common.di.modules.NetworkModule
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.common.utils.coroutines.RootScope
@ApplicationScope
@Component(
modules = [
AppModule::class,
CommonModule::class,
NetworkModule::class,
NavigationModule::class,
ComponentHolderModule::class,
FeatureManagerModule::class
]
)
interface AppComponent : CommonApi {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: App): Builder
@BindsInstance
fun contextManager(contextManager: ContextManager): Builder
@BindsInstance
fun rootScope(rootScope: RootScope): Builder
fun build(): AppComponent
}
fun inject(app: App)
}
@@ -0,0 +1,29 @@
package io.novafoundation.nova.app.di.app
import android.content.Context
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.App
import io.novafoundation.nova.app.root.presentation.common.RealBuildTypeProvider
import io.novafoundation.nova.app.root.presentation.common.RootActivityIntentProvider
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
import io.novafoundation.nova.common.interfaces.BuildTypeProvider
@Module
class AppModule {
@ApplicationScope
@Provides
fun provideContext(application: App): Context {
return application
}
@Provides
@ApplicationScope
fun provideRootActivityIntentProvider(context: Context): ActivityIntentProvider = RootActivityIntentProvider(context)
@Provides
@ApplicationScope
fun provideBuildTypeProvider(): BuildTypeProvider = RealBuildTypeProvider()
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.di.deps.FeatureHolderManager
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.scope.ApplicationScope
@Module
class FeatureManagerModule {
@ApplicationScope
@Provides
fun provideFeatureHolderManager(featureApiHolderMap: @JvmSuppressWildcards Map<Class<*>, FeatureApiHolder>): FeatureHolderManager {
return FeatureHolderManager(featureApiHolderMap)
}
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.accountmigration.AccountMigrationNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_account_migration.presentation.AccountMigrationRouter
@Module
class AccountMigrationNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry
): AccountMigrationRouter = AccountMigrationNavigator(
navigationHoldersRegistry = navigationHoldersRegistry
)
}
@@ -0,0 +1,103 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.account.PolkadotVaultVariantSignCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.ScanSeedCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.SelectAddressCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.SelectMultipleWalletsCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.SelectSingleWalletCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.SelectWalletCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.ChangeBackupPasswordCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.RestoreBackupPasswordCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.SyncWalletsBackupPasswordCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.pincode.PinCodeTwoFactorVerificationCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.ChangeBackupPasswordCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.RestoreBackupPasswordCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator
import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.createPassword.SyncWalletsBackupPasswordCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectSingleWallet.SelectSingleWalletCommunicator
import io.novafoundation.nova.feature_account_impl.presentation.seedScan.ScanSeedCommunicator
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
@Module
class AccountNavigationModule {
@Provides
@ApplicationScope
fun providePinCodeTwoFactorVerificationCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): PinCodeTwoFactorVerificationCommunicator = PinCodeTwoFactorVerificationCommunicatorImpl(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideSelectWalletCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectWalletCommunicator = SelectWalletCommunicatorImpl(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideParitySignerCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): PolkadotVaultVariantSignCommunicator = PolkadotVaultVariantSignCommunicatorImpl(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideSelectAddressCommunicator(
router: AssetsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectAddressCommunicator = SelectAddressCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideScanSeedCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): ScanSeedCommunicator = ScanSeedCommunicatorImpl(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideSelectSingleWalletCommunicator(
router: AssetsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectSingleWalletCommunicator = SelectSingleWalletCommunicatorImpl(router)
@Provides
@ApplicationScope
fun provideSelectMultipleWalletsCommunicator(
router: AssetsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectMultipleWalletsCommunicator = SelectMultipleWalletsCommunicatorImpl(router, navigationHoldersRegistry)
@ApplicationScope
@Provides
fun provideAccountRouter(navigator: Navigator): AccountRouter = navigator
@Provides
@ApplicationScope
fun providePushGovernanceSettingsCommunicator(
router: AccountRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SyncWalletsBackupPasswordCommunicator = SyncWalletsBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideChangeBackupPasswordCommunicator(
router: AccountRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): ChangeBackupPasswordCommunicator = ChangeBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideRestoreBackupPasswordCommunicator(
router: AccountRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): RestoreBackupPasswordCommunicator = RestoreBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry)
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.topup.TopUpAddressCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_assets.presentation.topup.TopUpAddressCommunicator
@Module
class AssetNavigationModule {
@ApplicationScope
@Provides
fun provideTopUpAddressCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): TopUpAddressCommunicator {
return TopUpAddressCommunicatorImpl(navigationHoldersRegistry)
}
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.buy.BuyNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_buy_impl.presentation.BuyRouter
@Module
class BuyNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): BuyRouter =
BuyNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.chainMigration.ChainMigrationNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_ahm_impl.presentation.ChainMigrationRouter
@Module
class ChainMigrationNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): ChainMigrationRouter =
ChainMigrationNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.CloudBackupNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_cloud_backup_impl.presentation.CloudBackupRouter
@Module
class CloudBackupNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): CloudBackupRouter =
CloudBackupNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.wallet.CurrencyNavigator
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_currency_api.presentation.CurrencyRouter
@Module
class CurrencyNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
rootRouter: RootRouter,
navigationHoldersRegistry: NavigationHoldersRegistry,
): CurrencyRouter = CurrencyNavigator(rootRouter, navigationHoldersRegistry)
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.dApp.DAppNavigator
import io.novafoundation.nova.app.root.navigation.navigators.dApp.DAppSearchCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator
@Module
class DAppNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry
): DAppRouter = DAppNavigator(navigationHoldersRegistry)
@ApplicationScope
@Provides
fun provideSearchDappCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): DAppSearchCommunicator {
return DAppSearchCommunicatorImpl(navigationHoldersRegistry)
}
}
@@ -0,0 +1,29 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.externalSign.ExternalSignCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.externalSign.ExternalSignNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator
import io.novafoundation.nova.feature_external_sign_impl.ExternalSignRouter
@Module
class ExternalSignNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): ExternalSignRouter =
ExternalSignNavigator(navigationHoldersRegistry)
@ApplicationScope
@Provides
fun provideSignExtrinsicCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry,
automaticInteractionGate: AutomaticInteractionGate,
): ExternalSignCommunicator {
return ExternalSignCommunicatorImpl(navigationHoldersRegistry, automaticInteractionGate)
}
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.gift.GiftNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_gift_impl.presentation.GiftRouter
@Module
class GiftNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(commonDelegate: Navigator, navigationHoldersRegistry: NavigationHoldersRegistry): GiftRouter =
GiftNavigator(commonDelegate, navigationHoldersRegistry)
}
@@ -0,0 +1,42 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.governance.GovernanceNavigator
import io.novafoundation.nova.app.root.navigation.navigators.governance.SelectTracksCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.governance.TinderGovVoteCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteCommunicator
@Module
class GovernanceNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
commonNavigator: Navigator,
contextManager: ContextManager,
dAppRouter: DAppRouter
): GovernanceRouter = GovernanceNavigator(navigationHoldersRegistry, commonNavigator, contextManager, dAppRouter)
@Provides
@ApplicationScope
fun provideSelectTracksCommunicator(
router: GovernanceRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectTracksCommunicator = SelectTracksCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideTinderGovVoteCommunicator(
router: GovernanceRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): TinderGovVoteCommunicator = TinderGovVoteCommunicatorImpl(router, navigationHoldersRegistry)
}
@@ -0,0 +1,34 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.ledger.LedgerNavigator
import io.novafoundation.nova.app.root.navigation.navigators.ledger.LedgerSignCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.ledger.SelectLedgerAddressCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.SelectLedgerAddressInterScreenCommunicator
@Module
class LedgerNavigationModule {
@ApplicationScope
@Provides
fun provideSelectLedgerAddressCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectLedgerAddressInterScreenCommunicator {
return SelectLedgerAddressCommunicatorImpl(navigationHoldersRegistry)
}
@Provides
@ApplicationScope
fun provideLedgerSignerCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): LedgerSignCommunicator = LedgerSignCommunicatorImpl(navigationHoldersRegistry)
@ApplicationScope
@Provides
fun provideRouter(router: AccountRouter, navigationHoldersRegistry: NavigationHoldersRegistry): LedgerRouter =
LedgerNavigator(router, navigationHoldersRegistry)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.multisig.MultisigOperationsNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_multisig_operations.presentation.MultisigOperationsRouter
@Module
class MultisigNavigationModule {
@ApplicationScope
@Provides
fun provideOperationsRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
commonDelegate: Navigator
): MultisigOperationsRouter = MultisigOperationsNavigator(navigationHoldersRegistry, commonDelegate)
}
@@ -0,0 +1,100 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.di.app.navigation.staking.StakingNavigationModule
import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder
import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.navigation.DelayedNavigationRouter
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
import io.novafoundation.nova.feature_onboarding_impl.OnboardingRouter
import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter
import io.novafoundation.nova.splash.SplashRouter
@Module(
includes = [
AccountNavigationModule::class,
AssetNavigationModule::class,
DAppNavigationModule::class,
NftNavigationModule::class,
StakingNavigationModule::class,
LedgerNavigationModule::class,
CurrencyNavigationModule::class,
GovernanceNavigationModule::class,
WalletConnectNavigationModule::class,
VoteNavigationModule::class,
VersionsNavigationModule::class,
ExternalSignNavigationModule::class,
SettingsNavigationModule::class,
SwapNavigationModule::class,
BuyNavigationModule::class,
PushNotificationsNavigationModule::class,
CloudBackupNavigationModule::class,
AssetNavigationModule::class,
AccountMigrationNavigationModule::class,
MultisigNavigationModule::class,
ChainMigrationNavigationModule::class,
WalletNavigationModule::class,
GiftNavigationModule::class
]
)
class NavigationModule {
@ApplicationScope
@Provides
fun provideMainNavigatorHolder(
contextManager: ContextManager
): SplitScreenNavigationHolder = SplitScreenNavigationHolder(contextManager)
@ApplicationScope
@Provides
fun provideDappNavigatorHolder(
contextManager: ContextManager
): RootNavigationHolder = RootNavigationHolder(contextManager)
@ApplicationScope
@Provides
fun provideNavigationHoldersRegistry(
rootNavigatorHolder: RootNavigationHolder,
splitScreenNavigationHolder: SplitScreenNavigationHolder,
): NavigationHoldersRegistry {
return NavigationHoldersRegistry(splitScreenNavigationHolder, rootNavigatorHolder)
}
@ApplicationScope
@Provides
fun provideNavigator(
navigationHoldersRegistry: NavigationHoldersRegistry,
walletConnectRouter: WalletConnectRouter
): Navigator = Navigator(navigationHoldersRegistry, walletConnectRouter)
@Provides
@ApplicationScope
fun provideRootRouter(navigator: Navigator): RootRouter = navigator
@ApplicationScope
@Provides
fun provideSplashRouter(navigator: Navigator): SplashRouter = navigator
@ApplicationScope
@Provides
fun provideOnboardingRouter(navigator: Navigator): OnboardingRouter = navigator
@ApplicationScope
@Provides
fun provideAssetsRouter(navigator: Navigator): AssetsRouter = navigator
@ApplicationScope
@Provides
fun provideCrowdloanRouter(navigator: Navigator): CrowdloanRouter = navigator
@ApplicationScope
@Provides
fun provideDelayedNavigationRouter(navigator: Navigator): DelayedNavigationRouter = navigator
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.nft.NftNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_nft_impl.NftRouter
@Module
class NftNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): NftRouter =
NftNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,44 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.push.PushGovernanceSettingsCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.push.PushMultisigSettingsCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.push.PushNotificationsNavigator
import io.novafoundation.nova.app.root.navigation.navigators.push.PushStakingSettingsCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsCommunicator
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsCommunicator
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsCommunicator
@Module
class PushNotificationsNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): PushNotificationsRouter =
PushNotificationsNavigator(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun providePushGovernanceSettingsCommunicator(
router: PushNotificationsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): PushGovernanceSettingsCommunicator = PushGovernanceSettingsCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun providePushStakingSettingsCommunicator(
router: PushNotificationsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): PushStakingSettingsCommunicator = PushStakingSettingsCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun providePushMultisigSettingsCommunicator(
router: PushNotificationsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): PushMultisigSettingsCommunicator = PushMultisigSettingsCommunicatorImpl(router, navigationHoldersRegistry)
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.settings.SettingsNavigator
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_settings_impl.SettingsRouter
import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter
@Module
class SettingsNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
rootRouter: RootRouter,
navigationHoldersRegistry: NavigationHoldersRegistry,
walletConnectRouter: WalletConnectRouter,
navigator: Navigator,
): SettingsRouter = SettingsNavigator(navigationHoldersRegistry, rootRouter, walletConnectRouter, navigator)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.swap.SwapNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_swap_impl.presentation.SwapRouter
@Module
class SwapNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
commonDelegate: Navigator
): SwapRouter = SwapNavigator(navigationHoldersRegistry, commonDelegate)
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.versions.VersionsNavigator
import io.novafoundation.nova.common.data.network.AppLinksProvider
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.feature_versions_api.presentation.VersionsRouter
@Module
class VersionsNavigationModule {
@Provides
@ApplicationScope
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
contextManager: ContextManager,
appLinksProvider: AppLinksProvider
): VersionsRouter = VersionsNavigator(navigationHoldersRegistry, contextManager, appLinksProvider.storeUrl)
}
@@ -0,0 +1,16 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.vote.VoteNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_vote.presentation.VoteRouter
@Module
class VoteNavigationModule {
@Provides
@ApplicationScope
fun provideVoteRouter(navigator: Navigator): VoteRouter = VoteNavigator(navigator)
}
@@ -0,0 +1,27 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.walletConnect.ApproveSessionCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.walletConnect.WalletConnectNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter
import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.approve.ApproveSessionCommunicator
@Module
class WalletConnectNavigationModule {
@Provides
@ApplicationScope
fun provideApproveSessionCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry,
automaticInteractionGate: AutomaticInteractionGate,
): ApproveSessionCommunicator = ApproveSessionCommunicatorImpl(navigationHoldersRegistry, automaticInteractionGate)
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): WalletConnectRouter =
WalletConnectNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.wallet.WalletNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_wallet_impl.presentation.WalletRouter
@Module
class WalletNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
commonDelegate: Navigator
): WalletRouter = WalletNavigator(commonDelegate, navigationHoldersRegistry)
}
@@ -0,0 +1,38 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.staking.mythos.MythosStakingNavigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.mythos.SelectMythCollatorSettingsInterScreenCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.staking.mythos.SelectMythosCollatorInterScreenCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.MythosStakingRouter
import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter
import io.novafoundation.nova.feature_staking_impl.presentation.mythos.SelectMythosInterScreenCommunicator
import io.novafoundation.nova.feature_staking_impl.presentation.mythos.start.selectCollatorSettings.SelectMythCollatorSettingsInterScreenCommunicator
@Module
class MythosStakingNavigationModule {
@Provides
@ApplicationScope
fun provideMythosStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
stakingDashboardRouter: StakingDashboardRouter,
): MythosStakingRouter {
return MythosStakingNavigator(navigationHoldersRegistry, stakingDashboardRouter)
}
@Provides
@ApplicationScope
fun provideSelectCollatorCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectMythosInterScreenCommunicator {
return SelectMythosCollatorInterScreenCommunicatorImpl(navigationHoldersRegistry)
}
@Provides
@ApplicationScope
fun provideSelectSettingsCollatorCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectMythCollatorSettingsInterScreenCommunicator {
return SelectMythCollatorSettingsInterScreenCommunicatorImpl(navigationHoldersRegistry)
}
}
@@ -0,0 +1,19 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.nominationPools.NominationPoolsStakingNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.NominationPoolsRouter
@Module
class NominationPoolsStakingNavigationModule {
@Provides
@ApplicationScope
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry, navigator: Navigator): NominationPoolsRouter {
return NominationPoolsStakingNavigator(navigationHoldersRegistry, navigator)
}
}
@@ -0,0 +1,38 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.ParachainStakingNavigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.SelectCollatorInterScreenCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.SelectCollatorSettingsInterScreenCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.ParachainStakingRouter
import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.common.SelectCollatorInterScreenCommunicator
import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.settings.SelectCollatorSettingsInterScreenCommunicator
@Module
class ParachainStakingNavigationModule {
@Provides
@ApplicationScope
fun provideParachainStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
navigator: Navigator
): ParachainStakingRouter {
return ParachainStakingNavigator(navigationHoldersRegistry, navigator)
}
@Provides
@ApplicationScope
fun provideSelectCollatorCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectCollatorInterScreenCommunicator {
return SelectCollatorInterScreenCommunicatorImpl(navigationHoldersRegistry)
}
@Provides
@ApplicationScope
fun provideSelectCollatorSettingsCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectCollatorSettingsInterScreenCommunicator {
return SelectCollatorSettingsInterScreenCommunicatorImpl(navigationHoldersRegistry)
}
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.relaychain.RelayStakingNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter
import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter
@Module
class RelayStakingNavigationModule {
@Provides
@ApplicationScope
fun provideRelayStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
navigator: Navigator,
dashboardRouter: StakingDashboardRouter,
dAppRouter: DAppRouter
): StakingRouter {
return RelayStakingNavigator(navigationHoldersRegistry, navigator, dashboardRouter, dAppRouter)
}
}
@@ -0,0 +1,42 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.StartMultiStakingNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter
import io.novafoundation.nova.feature_staking_impl.presentation.StartMultiStakingRouter
@Module(
includes = [
ParachainStakingNavigationModule::class,
RelayStakingNavigationModule::class,
NominationPoolsStakingNavigationModule::class,
MythosStakingNavigationModule::class
]
)
class StakingNavigationModule {
@Provides
@ApplicationScope
fun provideStakingDashboardNavigator(navigationHoldersRegistry: NavigationHoldersRegistry): StakingDashboardNavigator {
return StakingDashboardNavigator(navigationHoldersRegistry)
}
@Provides
@ApplicationScope
fun provideStakingDashboardRouter(relayStakingNavigator: StakingDashboardNavigator): StakingDashboardRouter = relayStakingNavigator
@Provides
@ApplicationScope
fun provideStartMultiStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
dashboardRouter: StakingDashboardRouter,
commonNavigator: Navigator
): StartMultiStakingRouter {
return StartMultiStakingNavigator(navigationHoldersRegistry, dashboardRouter, commonNavigator)
}
}
@@ -0,0 +1,306 @@
package io.novafoundation.nova.app.di.deps
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import io.novafoundation.nova.app.App
import io.novafoundation.nova.app.root.di.RootApi
import io.novafoundation.nova.app.root.di.RootFeatureHolder
import io.novafoundation.nova.caip.di.CaipApi
import io.novafoundation.nova.caip.di.CaipFeatureHolder
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.core_db.di.DbApi
import io.novafoundation.nova.core_db.di.DbHolder
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_impl.di.AccountFeatureHolder
import io.novafoundation.nova.feature_account_migration.di.AccountMigrationFeatureApi
import io.novafoundation.nova.feature_account_migration.di.AccountMigrationFeatureHolder
import io.novafoundation.nova.feature_ahm_api.di.ChainMigrationFeatureApi
import io.novafoundation.nova.feature_ahm_impl.di.ChainMigrationFeatureHolder
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
import io.novafoundation.nova.feature_assets.di.AssetsFeatureHolder
import io.novafoundation.nova.feature_banners_api.di.BannersFeatureApi
import io.novafoundation.nova.feature_banners_impl.di.BannersFeatureHolder
import io.novafoundation.nova.feature_buy_api.di.BuyFeatureApi
import io.novafoundation.nova.feature_buy_impl.di.BuyFeatureHolder
import io.novafoundation.nova.feature_cloud_backup_api.di.CloudBackupFeatureApi
import io.novafoundation.nova.feature_cloud_backup_impl.di.CloudBackupFeatureHolder
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
import io.novafoundation.nova.feature_crowdloan_impl.di.CrowdloanFeatureHolder
import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi
import io.novafoundation.nova.feature_currency_impl.di.CurrencyFeatureHolder
import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi
import io.novafoundation.nova.feature_dapp_impl.di.DAppFeatureHolder
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureHolder
import io.novafoundation.nova.feature_external_sign_api.di.ExternalSignFeatureApi
import io.novafoundation.nova.feature_external_sign_impl.di.ExternalSignFeatureHolder
import io.novafoundation.nova.feature_gift_api.di.GiftFeatureApi
import io.novafoundation.nova.feature_gift_impl.di.GiftFeatureHolder
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_impl.di.GovernanceFeatureHolder
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
import io.novafoundation.nova.feature_ledger_core.LedgerCoreHolder
import io.novafoundation.nova.feature_ledger_core.di.LedgerCoreApi
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureHolder
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureHolder
import io.novafoundation.nova.feature_nft_api.NftFeatureApi
import io.novafoundation.nova.feature_nft_impl.di.NftFeatureHolder
import io.novafoundation.nova.feature_onboarding_api.di.OnboardingFeatureApi
import io.novafoundation.nova.feature_onboarding_impl.di.OnboardingFeatureHolder
import io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi
import io.novafoundation.nova.feature_proxy_impl.di.ProxyFeatureHolder
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureHolder
import io.novafoundation.nova.feature_settings_api.SettingsFeatureApi
import io.novafoundation.nova.feature_settings_impl.di.SettingsFeatureHolder
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureHolder
import io.novafoundation.nova.feature_swap_api.di.SwapFeatureApi
import io.novafoundation.nova.feature_swap_core.di.SwapCoreHolder
import io.novafoundation.nova.feature_swap_core_api.di.SwapCoreApi
import io.novafoundation.nova.feature_swap_impl.di.SwapFeatureHolder
import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi
import io.novafoundation.nova.feature_versions_impl.di.VersionsFeatureHolder
import io.novafoundation.nova.feature_vote.di.VoteFeatureApi
import io.novafoundation.nova.feature_vote.di.VoteFeatureHolder
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_wallet_connect_api.di.WalletConnectFeatureApi
import io.novafoundation.nova.feature_wallet_connect_impl.di.WalletConnectFeatureHolder
import io.novafoundation.nova.feature_wallet_impl.di.WalletFeatureHolder
import io.novafoundation.nova.feature_xcm_api.di.XcmFeatureApi
import io.novafoundation.nova.feature_xcm_impl.di.XcmFeatureHolder
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeHolder
import io.novafoundation.nova.splash.di.SplashFeatureApi
import io.novafoundation.nova.splash.di.SplashFeatureHolder
import io.novafoundation.nova.web3names.di.Web3NamesApi
import io.novafoundation.nova.web3names.di.Web3NamesHolder
@Module
interface ComponentHolderModule {
@ApplicationScope
@Binds
fun provideFeatureContainer(application: App): FeatureContainer
@ApplicationScope
@Binds
@ClassKey(SplashFeatureApi::class)
@IntoMap
fun provideSplashFeatureHolder(splashFeatureHolder: SplashFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(DbApi::class)
@IntoMap
fun provideDbFeature(dbHolder: DbHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(OnboardingFeatureApi::class)
@IntoMap
fun provideOnboardingFeature(onboardingFeatureHolder: OnboardingFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(DAppFeatureApi::class)
@IntoMap
fun provideDAppFeature(dAppFeatureHolder: DAppFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(LedgerFeatureApi::class)
@IntoMap
fun provideLedgerFeature(accountFeatureHolder: LedgerFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(LedgerCoreApi::class)
@IntoMap
fun provideLedgerCore(accountFeatureHolder: LedgerCoreHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(GovernanceFeatureApi::class)
@IntoMap
fun provideGovernanceFeature(accountFeatureHolder: GovernanceFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(AccountFeatureApi::class)
@IntoMap
fun provideAccountFeature(accountFeatureHolder: AccountFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(AssetsFeatureApi::class)
@IntoMap
fun provideAssetsFeature(holder: AssetsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(VoteFeatureApi::class)
@IntoMap
fun provideVoteFeature(holder: VoteFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(WalletFeatureApi::class)
@IntoMap
fun provideWalletFeature(walletFeatureHolder: WalletFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(CurrencyFeatureApi::class)
@IntoMap
fun provideCurrencyFeature(currencyFeatureHolder: CurrencyFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(RootApi::class)
@IntoMap
fun provideMainFeature(accountFeatureHolder: RootFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(StakingFeatureApi::class)
@IntoMap
fun provideStakingFeature(holder: StakingFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(RuntimeApi::class)
@IntoMap
fun provideRuntimeFeature(runtimeHolder: RuntimeHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(Web3NamesApi::class)
@IntoMap
fun provideWeb3Names(web3NamesHolder: Web3NamesHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(CrowdloanFeatureApi::class)
@IntoMap
fun provideCrowdloanFeature(holder: CrowdloanFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(NftFeatureApi::class)
@IntoMap
fun provideNftFeature(holder: NftFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(VersionsFeatureApi::class)
@IntoMap
fun provideVersionsFeature(holder: VersionsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(CaipApi::class)
@IntoMap
fun provideCaipFeature(holder: CaipFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(ExternalSignFeatureApi::class)
@IntoMap
fun provideExternalSignFeature(holder: ExternalSignFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(WalletConnectFeatureApi::class)
@IntoMap
fun provideWalletConnectFeature(holder: WalletConnectFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(SettingsFeatureApi::class)
@IntoMap
fun provideSettingsFeature(holder: SettingsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(SwapFeatureApi::class)
@IntoMap
fun provideSwapFeature(holder: SwapFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(BuyFeatureApi::class)
@IntoMap
fun provideBuyFeature(holder: BuyFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(PushNotificationsFeatureApi::class)
@IntoMap
fun providePushNotificationsFeature(holder: PushNotificationsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(ProxyFeatureApi::class)
@IntoMap
fun provideProxyFeature(holder: ProxyFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(DeepLinkingFeatureApi::class)
@IntoMap
fun provideDeepLinkingFeatureHolder(holder: DeepLinkingFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(CloudBackupFeatureApi::class)
@IntoMap
fun provideCloudBackupFeatureHolder(holder: CloudBackupFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(SwapCoreApi::class)
@IntoMap
fun provideSwapCoreFeatureHolder(holder: SwapCoreHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(BannersFeatureApi::class)
@IntoMap
fun provideBannersFeatureApi(holder: BannersFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(XcmFeatureApi::class)
@IntoMap
fun provideXcmFeatureHolder(holder: XcmFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(MultisigOperationsFeatureApi::class)
@IntoMap
fun provideMultisigOperationsFeatureHolder(holder: MultisigOperationsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(AccountMigrationFeatureApi::class)
@IntoMap
fun provideAccountMigrationFeatureHolder(holder: AccountMigrationFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(ChainMigrationFeatureApi::class)
@IntoMap
fun provideChainMigrationFeatureHolder(holder: ChainMigrationFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(GiftFeatureApi::class)
@IntoMap
fun provideGiftFeature(giftFeatureHolder: GiftFeatureHolder): FeatureApiHolder
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.app.di.deps
import io.novafoundation.nova.common.di.FeatureApiHolder
class FeatureHolderManager(
private val mFeatureHolders: Map<Class<*>, FeatureApiHolder>
) {
fun <T> getFeature(key: Class<*>): T? {
val featureApiHolder = mFeatureHolders[key] ?: throw IllegalStateException()
return featureApiHolder.getFeatureApi<T>()
}
fun releaseFeature(key: Class<*>) {
val featureApiHolder = mFeatureHolders[key] ?: throw IllegalStateException()
featureApiHolder.releaseFeatureApi()
}
}
@@ -0,0 +1,30 @@
package io.novafoundation.nova.app.root.di
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
import io.novafoundation.nova.app.root.presentation.common.FirebaseServiceInitializer
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.interfaces.CompoundExternalServiceInitializer
import io.novafoundation.nova.common.interfaces.ExternalServiceInitializer
@Module
class ExternalServiceInitializersModule {
@Provides
@IntoSet
fun provideFirebaseServiceInitializer(
context: Context
): ExternalServiceInitializer {
return FirebaseServiceInitializer(context)
}
@Provides
@FeatureScope
fun provideCompoundExternalServiceInitializer(
initializers: Set<@JvmSuppressWildcards ExternalServiceInitializer>
): ExternalServiceInitializer {
return CompoundExternalServiceInitializer(initializers)
}
}
@@ -0,0 +1,3 @@
package io.novafoundation.nova.app.root.di
interface RootApi
@@ -0,0 +1,103 @@
package io.novafoundation.nova.app.root.di
import dagger.BindsInstance
import dagger.Component
import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder
import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder
import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.app.root.presentation.di.RootActivityComponent
import io.novafoundation.nova.app.root.presentation.main.di.MainFragmentComponent
import io.novafoundation.nova.app.root.presentation.splitScreen.di.SplitScreenFragmentComponent
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.navigation.DelayedNavigationRouter
import io.novafoundation.nova.core_db.di.DbApi
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_account_migration.di.AccountMigrationFeatureApi
import io.novafoundation.nova.feature_ahm_api.di.ChainMigrationFeatureApi
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_buy_api.di.BuyFeatureApi
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi
import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi
import io.novafoundation.nova.feature_gift_api.di.GiftFeatureApi
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter
import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_wallet_connect_api.di.WalletConnectFeatureApi
import io.novafoundation.nova.runtime.di.RuntimeApi
@Component(
dependencies = [
RootDependencies::class
],
modules = [
RootFeatureModule::class
]
)
@FeatureScope
interface RootComponent {
fun mainActivityComponentFactory(): RootActivityComponent.Factory
fun splitScreenFragmentComponentFactory(): SplitScreenFragmentComponent.Factory
fun mainFragmentComponentFactory(): MainFragmentComponent.Factory
@Component.Factory
interface Factory {
fun create(
@BindsInstance splitScreenNavigationHolder: SplitScreenNavigationHolder,
@BindsInstance rootNavigationHolder: RootNavigationHolder,
@BindsInstance rootRouter: RootRouter,
@BindsInstance governanceRouter: GovernanceRouter,
@BindsInstance dAppRouter: DAppRouter,
@BindsInstance assetsRouter: AssetsRouter,
@BindsInstance accountRouter: AccountRouter,
@BindsInstance stakingRouter: StakingRouter,
@BindsInstance stakingDashboardNavigator: StakingDashboardNavigator,
@BindsInstance delayedNavigationRouter: DelayedNavigationRouter,
deps: RootDependencies
): RootComponent
}
@Component(
dependencies = [
AccountFeatureApi::class,
WalletFeatureApi::class,
StakingFeatureApi::class,
CrowdloanFeatureApi::class,
AssetsFeatureApi::class,
CurrencyFeatureApi::class,
GovernanceFeatureApi::class,
DAppFeatureApi::class,
DbApi::class,
CommonApi::class,
RuntimeApi::class,
VersionsFeatureApi::class,
WalletConnectFeatureApi::class,
PushNotificationsFeatureApi::class,
DeepLinkingFeatureApi::class,
LedgerFeatureApi::class,
BuyFeatureApi::class,
DeepLinkingFeatureApi::class,
AccountMigrationFeatureApi::class,
MultisigOperationsFeatureApi::class,
ChainMigrationFeatureApi::class,
GiftFeatureApi::class
]
)
interface RootFeatureDependenciesComponent : RootDependencies
}
@@ -0,0 +1,192 @@
package io.novafoundation.nova.app.root.di
import android.content.Context
import coil.ImageLoader
import io.novafoundation.nova.common.data.network.AppLinksProvider
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
import io.novafoundation.nova.common.mixin.api.NetworkStateMixin
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.sequrity.SafeModeService
import io.novafoundation.nova.common.utils.DialogMessageManager
import io.novafoundation.nova.common.utils.ToastMessageManager
import io.novafoundation.nova.common.utils.coroutines.RootScope
import io.novafoundation.nova.common.utils.network.DeviceNetworkStateObserver
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver
import io.novafoundation.nova.common.utils.systemCall.SystemCallExecutor
import io.novafoundation.nova.common.view.bottomSheet.action.ActionBottomSheetLauncher
import io.novafoundation.nova.common.view.bottomSheet.action.ActionBottomSheetLauncherFactory
import io.novafoundation.nova.core_db.dao.BrowserTabsDao
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus
import io.novafoundation.nova.feature_account_api.data.externalAccounts.ExternalAccountsSyncService
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.data.multisig.validation.MultisigExtrinsicValidationRequestBus
import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus
import io.novafoundation.nova.feature_account_api.di.deeplinks.AccountDeepLinks
import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults
import io.novafoundation.nova.feature_account_api.domain.cloudBackup.ApplyLocalSnapshotToCloudBackupUseCase
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_account_migration.di.deeplinks.AccountMigrationDeepLinks
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
import io.novafoundation.nova.feature_ahm_api.di.deeplinks.ChainMigrationDeepLinks
import io.novafoundation.nova.feature_ahm_api.domain.ChainMigrationDetailsSelectToShowUseCase
import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem
import io.novafoundation.nova.feature_assets.di.modules.deeplinks.AssetDeepLinks
import io.novafoundation.nova.feature_buy_api.di.deeplinks.BuyDeepLinks
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor
import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor
import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository
import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository
import io.novafoundation.nova.feature_dapp_api.di.deeplinks.DAppDeepLinks
import io.novafoundation.nova.feature_deep_linking.presentation.handling.PendingDeepLinkProvider
import io.novafoundation.nova.feature_deep_linking.presentation.handling.branchIo.BranchIoLinkConverter
import io.novafoundation.nova.feature_deep_linking.presentation.handling.common.DeepLinkingPreferences
import io.novafoundation.nova.feature_gift_api.di.GiftDeepLinks
import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState
import io.novafoundation.nova.feature_governance_api.di.deeplinks.GovernanceDeepLinks
import io.novafoundation.nova.feature_multisig_operations.di.deeplink.MultisigDeepLinks
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
import io.novafoundation.nova.feature_push_notifications.domain.interactor.WelcomePushNotificationsInteractor
import io.novafoundation.nova.feature_push_notifications.presentation.multisigsWarning.MultisigPushNotificationsAlertMixinFactory
import io.novafoundation.nova.feature_staking_api.di.deeplinks.StakingDeepLinks
import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository
import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
import io.novafoundation.nova.feature_wallet_api.domain.validation.MultisigExtrinsicValidationFactory
import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory
import io.novafoundation.nova.feature_wallet_connect_api.di.deeplinks.WalletConnectDeepLinks
import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase
import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import kotlinx.coroutines.flow.MutableStateFlow
interface RootDependencies {
val stakingDeepLinks: StakingDeepLinks
val accountDeepLinks: AccountDeepLinks
val dAppDeepLinks: DAppDeepLinks
val governanceDeepLinks: GovernanceDeepLinks
val buyDeepLinks: BuyDeepLinks
val assetDeepLinks: AssetDeepLinks
val giftDeepLinks: GiftDeepLinks
val chainMigrationDeepLinks: ChainMigrationDeepLinks
val walletConnectDeepLinks: WalletConnectDeepLinks
val systemCallExecutor: SystemCallExecutor
val contextManager: ContextManager
val walletConnectService: WalletConnectService
val imageLoader: ImageLoader
val automaticInteractionGate: AutomaticInteractionGate
val walletConnectSessionsUseCase: WalletConnectSessionsUseCase
val pushNotificationsInteractor: PushNotificationsInteractor
val welcomePushNotificationsInteractor: WelcomePushNotificationsInteractor
val applyLocalSnapshotToCloudBackupUseCase: ApplyLocalSnapshotToCloudBackupUseCase
val actionBottomSheetLauncherFactory: ActionBottomSheetLauncherFactory
val tabsDao: BrowserTabsDao
val balancesUpdateSystem: BalancesUpdateSystem
val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
val browserTabExternalRepository: BrowserTabExternalRepository
val externalAccountsSyncService: ExternalAccountsSyncService
val multisigPendingOperationsService: MultisigPendingOperationsService
val accountMigrationDeepLinks: AccountMigrationDeepLinks
val multisigDeepLinks: MultisigDeepLinks
val deepLinkingPreferences: DeepLinkingPreferences
val branchIoLinkConverter: BranchIoLinkConverter
val pendingDeepLinkProvider: PendingDeepLinkProvider
val multisigExtrinsicValidationRequestBus: MultisigExtrinsicValidationRequestBus
val multisigExtrinsicValidationFactory: MultisigExtrinsicValidationFactory
val actionBottomSheetLauncher: ActionBottomSheetLauncher
val multisigPushNotificationsAlertMixinFactory: MultisigPushNotificationsAlertMixinFactory
val chainMigrationDetailsSelectToShowUseCase: ChainMigrationDetailsSelectToShowUseCase
val deviceNetworkStateObserver: DeviceNetworkStateObserver
fun updateNotificationsInteractor(): UpdateNotificationsInteractor
fun contributionsInteractor(): ContributionsInteractor
fun crowdloanRepository(): CrowdloanRepository
fun networkStateMixin(): NetworkStateMixin
fun externalRequirementsFlow(): MutableStateFlow<ChainConnection.ExternalRequirement>
fun accountRepository(): AccountRepository
fun walletRepository(): WalletRepository
fun appLinksProvider(): AppLinksProvider
fun resourceManager(): ResourceManager
fun currencyInteractor(): CurrencyInteractor
fun stakingRepository(): StakingRepository
fun chainRegistry(): ChainRegistry
fun backgroundAccessObserver(): BackgroundAccessObserver
fun safeModeService(): SafeModeService
fun rootScope(): RootScope
fun governanceStateUpdater(): MutableGovernanceState
fun dappMetadataRepository(): DAppMetadataRepository
fun encryptionDefaults(): EncryptionDefaults
fun proxyExtrinsicValidationRequestBus(): ProxyExtrinsicValidationRequestBus
fun metaAccountChangesRequestBus(): MetaAccountChangesEventBus
fun proxyHaveEnoughFeeValidationFactory(): ProxyHaveEnoughFeeValidationFactory
fun context(): Context
fun toastMessageManager(): ToastMessageManager
fun dialogMessageManager(): DialogMessageManager
fun chainMigrationRepository(): ChainMigrationRepository
fun migrationInfoRepository(): MigrationInfoRepository
}
@@ -0,0 +1,94 @@
package io.novafoundation.nova.app.root.di
import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder
import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.navigation.DelayedNavigationRouter
import io.novafoundation.nova.core_db.di.DbApi
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_account_migration.di.AccountMigrationFeatureApi
import io.novafoundation.nova.feature_ahm_api.di.ChainMigrationFeatureApi
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_buy_api.di.BuyFeatureApi
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi
import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi
import io.novafoundation.nova.feature_gift_api.di.GiftFeatureApi
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter
import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_wallet_connect_api.di.WalletConnectFeatureApi
import io.novafoundation.nova.runtime.di.RuntimeApi
import javax.inject.Inject
@ApplicationScope
class RootFeatureHolder @Inject constructor(
private val splitScreenNavigationHolder: SplitScreenNavigationHolder,
private val rootNavigationHolder: RootNavigationHolder,
private val navigator: Navigator,
private val governanceRouter: GovernanceRouter,
private val dAppRouter: DAppRouter,
private val accountRouter: AccountRouter,
private val assetsRouter: AssetsRouter,
private val stakingRouter: StakingRouter,
private val stakingDashboardNavigator: StakingDashboardNavigator,
private val delayedNavRouter: DelayedNavigationRouter,
featureContainer: FeatureContainer
) : FeatureApiHolder(featureContainer) {
override fun initializeDependencies(): Any {
val rootFeatureDependencies = DaggerRootComponent_RootFeatureDependenciesComponent.builder()
.commonApi(commonApi())
.dbApi(getFeature(DbApi::class.java))
.accountFeatureApi(getFeature(AccountFeatureApi::class.java))
.walletFeatureApi(getFeature(WalletFeatureApi::class.java))
.stakingFeatureApi(getFeature(StakingFeatureApi::class.java))
.assetsFeatureApi(getFeature(AssetsFeatureApi::class.java))
.currencyFeatureApi(getFeature(CurrencyFeatureApi::class.java))
.crowdloanFeatureApi(getFeature(CrowdloanFeatureApi::class.java))
.governanceFeatureApi(getFeature(GovernanceFeatureApi::class.java))
.dAppFeatureApi(getFeature(DAppFeatureApi::class.java))
.runtimeApi(getFeature(RuntimeApi::class.java))
.versionsFeatureApi(getFeature(VersionsFeatureApi::class.java))
.walletConnectFeatureApi(getFeature(WalletConnectFeatureApi::class.java))
.pushNotificationsFeatureApi(getFeature(PushNotificationsFeatureApi::class.java))
.deepLinkingFeatureApi(getFeature(DeepLinkingFeatureApi::class.java))
.ledgerFeatureApi(getFeature(LedgerFeatureApi::class.java))
.buyFeatureApi(getFeature(BuyFeatureApi::class.java))
.deepLinkingFeatureApi(getFeature(DeepLinkingFeatureApi::class.java))
.accountMigrationFeatureApi(getFeature(AccountMigrationFeatureApi::class.java))
.multisigOperationsFeatureApi(getFeature(MultisigOperationsFeatureApi::class.java))
.chainMigrationFeatureApi(getFeature(ChainMigrationFeatureApi::class.java))
.giftFeatureApi(getFeature(GiftFeatureApi::class.java))
.build()
return DaggerRootComponent.factory()
.create(
splitScreenNavigationHolder,
rootNavigationHolder,
navigator,
governanceRouter,
dAppRouter,
assetsRouter,
accountRouter,
stakingRouter,
stakingDashboardNavigator,
delayedNavRouter,
rootFeatureDependencies
)
}
}
@@ -0,0 +1,47 @@
package io.novafoundation.nova.app.root.di
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.di.busHandler.RequestBusHandlerModule
import io.novafoundation.nova.app.root.di.deeplink.DeepLinksModule
import io.novafoundation.nova.app.root.domain.RootInteractor
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.data.externalAccounts.ExternalAccountsSyncService
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
@Module(
includes = [
RequestBusHandlerModule::class,
ExternalServiceInitializersModule::class,
DeepLinksModule::class
]
)
class RootFeatureModule {
@Provides
@FeatureScope
fun provideRootInteractor(
walletRepository: WalletRepository,
accountRepository: AccountRepository,
balancesUpdateSystem: BalancesUpdateSystem,
multisigPendingOperationsService: MultisigPendingOperationsService,
externalAccountsSyncService: ExternalAccountsSyncService,
chainMigrationRepository: ChainMigrationRepository,
migrationInfoRepository: MigrationInfoRepository
): RootInteractor {
return RootInteractor(
updateSystem = balancesUpdateSystem,
walletRepository = walletRepository,
accountRepository = accountRepository,
multisigPendingOperationsService = multisigPendingOperationsService,
externalAccountsSyncService = externalAccountsSyncService,
chainMigrationRepository = chainMigrationRepository,
migrationInfoRepository = migrationInfoRepository
)
}
}
@@ -0,0 +1,98 @@
package io.novafoundation.nova.app.root.di.busHandler
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.app.root.presentation.requestBusHandler.CloudBackupSyncRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.CompoundRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.MultisigExtrinsicValidationRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.ProxyExtrinsicValidationRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.PushSettingsSyncRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.RequestBusHandler
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.common.view.bottomSheet.action.ActionBottomSheetLauncher
import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus
import io.novafoundation.nova.feature_account_api.data.multisig.validation.MultisigExtrinsicValidationRequestBus
import io.novafoundation.nova.feature_account_api.domain.cloudBackup.ApplyLocalSnapshotToCloudBackupUseCase
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
import io.novafoundation.nova.feature_wallet_api.domain.validation.MultisigExtrinsicValidationFactory
import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory
@Module
class RequestBusHandlerModule {
@Provides
@FeatureScope
@IntoSet
fun providePushSettingsSyncRequestBusHandler(
metaAccountChangesEventBus: MetaAccountChangesEventBus,
pushNotificationsInteractor: PushNotificationsInteractor
): RequestBusHandler {
return PushSettingsSyncRequestBusHandler(
metaAccountChangesEventBus,
pushNotificationsInteractor
)
}
@Provides
@FeatureScope
@IntoSet
fun provideProxyExtrinsicValidationRequestBusHandler(
proxyProxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus,
proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory
): RequestBusHandler {
return ProxyExtrinsicValidationRequestBusHandler(
proxyProxyExtrinsicValidationRequestBus,
proxyHaveEnoughFeeValidationFactory
)
}
@Provides
@FeatureScope
@IntoSet
fun provideMultisigExtrinsicValidationRequestBusHandler(
multisigExtrinsicValidationRequestBus: MultisigExtrinsicValidationRequestBus,
multisigExtrinsicValidationFactory: MultisigExtrinsicValidationFactory
): RequestBusHandler {
return MultisigExtrinsicValidationRequestBusHandler(
multisigExtrinsicValidationRequestBus,
multisigExtrinsicValidationFactory
)
}
@Provides
@FeatureScope
@IntoSet
fun provideCloudBackupSyncRequestBusHandler(
rootRouter: RootRouter,
resourceManager: ResourceManager,
metaAccountChangesEventBus: MetaAccountChangesEventBus,
applyLocalSnapshotToCloudBackupUseCase: ApplyLocalSnapshotToCloudBackupUseCase,
accountRepository: AccountRepository,
actionBottomSheetLauncher: ActionBottomSheetLauncher,
automaticInteractionGate: AutomaticInteractionGate
): RequestBusHandler {
return CloudBackupSyncRequestBusHandler(
rootRouter,
resourceManager,
metaAccountChangesEventBus,
applyLocalSnapshotToCloudBackupUseCase,
accountRepository,
actionBottomSheetLauncher,
automaticInteractionGate
)
}
@Provides
@FeatureScope
fun provideCompoundRequestBusHandler(
handlers: Set<@JvmSuppressWildcards RequestBusHandler>
): CompoundRequestBusHandler {
return CompoundRequestBusHandler(handlers)
}
}
@@ -0,0 +1,75 @@
package io.novafoundation.nova.app.root.di.deeplink
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.di.deeplinks.AccountDeepLinks
import io.novafoundation.nova.feature_account_migration.di.deeplinks.AccountMigrationDeepLinks
import io.novafoundation.nova.feature_ahm_api.di.deeplinks.ChainMigrationDeepLinks
import io.novafoundation.nova.feature_assets.di.modules.deeplinks.AssetDeepLinks
import io.novafoundation.nova.feature_buy_api.di.deeplinks.BuyDeepLinks
import io.novafoundation.nova.feature_dapp_api.di.deeplinks.DAppDeepLinks
import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkHandler
import io.novafoundation.nova.feature_deep_linking.presentation.handling.PendingDeepLinkProvider
import io.novafoundation.nova.feature_deep_linking.presentation.handling.RootDeepLinkHandler
import io.novafoundation.nova.feature_deep_linking.presentation.handling.branchIo.BranchIOLinkHandler
import io.novafoundation.nova.feature_deep_linking.presentation.handling.branchIo.BranchIoLinkConverter
import io.novafoundation.nova.feature_gift_api.di.GiftDeepLinks
import io.novafoundation.nova.feature_governance_api.di.deeplinks.GovernanceDeepLinks
import io.novafoundation.nova.feature_multisig_operations.di.deeplink.MultisigDeepLinks
import io.novafoundation.nova.feature_staking_api.di.deeplinks.StakingDeepLinks
import io.novafoundation.nova.feature_wallet_connect_api.di.deeplinks.WalletConnectDeepLinks
@Module
class DeepLinksModule {
@Provides
@FeatureScope
fun provideDeepLinkHandlers(
stakingDeepLinks: StakingDeepLinks,
accountDeepLinks: AccountDeepLinks,
dAppDeepLinks: DAppDeepLinks,
governanceDeepLinks: GovernanceDeepLinks,
buyDeepLinks: BuyDeepLinks,
assetDeepLinks: AssetDeepLinks,
walletConnectDeepLinks: WalletConnectDeepLinks,
accountMigrationDeepLinks: AccountMigrationDeepLinks,
multisigDeepLinks: MultisigDeepLinks,
chainMigrationDeepLinks: ChainMigrationDeepLinks,
giftDeepLinks: GiftDeepLinks
): List<@JvmWildcard DeepLinkHandler> {
return buildList {
addAll(stakingDeepLinks.deepLinkHandlers)
addAll(accountDeepLinks.deepLinkHandlers)
addAll(dAppDeepLinks.deepLinkHandlers)
addAll(governanceDeepLinks.deepLinkHandlers)
addAll(buyDeepLinks.deepLinkHandlers)
addAll(assetDeepLinks.deepLinkHandlers)
addAll(walletConnectDeepLinks.deepLinkHandlers)
addAll(accountMigrationDeepLinks.deepLinkHandlers)
addAll(multisigDeepLinks.deepLinkHandlers)
addAll(chainMigrationDeepLinks.deepLinkHandlers)
addAll(giftDeepLinks.deepLinkHandlers)
}
}
@Provides
@FeatureScope
fun provideRootDeepLinkHandler(
pendingDeepLinkProvider: PendingDeepLinkProvider,
nestedHandlers: @JvmWildcard List<DeepLinkHandler>
): RootDeepLinkHandler {
return RootDeepLinkHandler(
pendingDeepLinkProvider,
nestedHandlers
)
}
@Provides
@FeatureScope
fun provideBranchIOLinkHandler(
branchIoLinkConverter: BranchIoLinkConverter
): BranchIOLinkHandler {
return BranchIOLinkHandler(branchIoLinkConverter)
}
}
@@ -0,0 +1,56 @@
package io.novafoundation.nova.app.root.domain
import io.novafoundation.nova.common.data.memory.ComputationalScope
import io.novafoundation.nova.core.updater.Updater
import io.novafoundation.nova.feature_account_api.data.externalAccounts.ExternalAccountsSyncService
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
import kotlinx.coroutines.flow.Flow
class RootInteractor(
private val updateSystem: BalancesUpdateSystem,
private val walletRepository: WalletRepository,
private val accountRepository: AccountRepository,
private val multisigPendingOperationsService: MultisigPendingOperationsService,
private val externalAccountsSyncService: ExternalAccountsSyncService,
private val chainMigrationRepository: ChainMigrationRepository,
private val migrationInfoRepository: MigrationInfoRepository
) {
fun runBalancesUpdate(): Flow<Updater.SideEffect> = updateSystem.start()
suspend fun updatePhishingAddresses() {
runCatching {
walletRepository.updatePhishingAddresses()
}
}
suspend fun isAccountSelected(): Boolean {
return accountRepository.isAccountSelected()
}
suspend fun isPinCodeSet(): Boolean {
return accountRepository.isCodeSet()
}
fun syncExternalAccounts() {
externalAccountsSyncService.sync()
}
context(ComputationalScope)
fun syncPendingMultisigOperations(): Flow<Unit> {
return multisigPendingOperationsService.performMultisigOperationsSync()
}
suspend fun cacheBalancesForChainMigrationDetection() {
chainMigrationRepository.cacheBalancesForChainMigrationDetection()
}
suspend fun loadMigrationDetailsConfigs() {
migrationInfoRepository.loadConfigs()
}
}
@@ -0,0 +1,23 @@
package io.novafoundation.nova.app.root.domain
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_dapp_api.data.model.SimpleTabModel
import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
class SplitScreenInteractor(
private val repository: BrowserTabExternalRepository,
private val accountRepository: AccountRepository
) {
fun observeTabNamesById(): Flow<List<SimpleTabModel>> {
return accountRepository.selectedMetaAccountFlow()
.flatMapLatest { repository.observeTabsWithNames(it.id) }
}
suspend fun removeAllTabs() {
val metaAccount = accountRepository.getSelectedMetaAccount()
repository.removeTabsForMetaAccount(metaAccount.id)
}
}
@@ -0,0 +1,39 @@
package io.novafoundation.nova.app.root.navigation
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavGraph
import io.novafoundation.nova.app.R
import io.novafoundation.nova.app.root.navigation.delayedNavigation.NavComponentDelayedNavigation
import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator
import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenFragment
import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenPayload
@SuppressLint("RestrictedApi")
fun NavController.getBackStackEntryBefore(@IdRes id: Int): NavBackStackEntry {
val initial = getBackStackEntry(id)
val backStack = backStack.toList()
val initialIndex = backStack.indexOf(initial)
var previousIndex = initialIndex - 1
// ignore nav graphs
while (previousIndex > 0 && backStack[previousIndex].destination is NavGraph) {
previousIndex--
}
return backStack[previousIndex]
}
fun BaseNavigator.openSplitScreenWithInstantAction(actionId: Int, nestedActionExtras: Bundle? = null) {
val delayedNavigation = NavComponentDelayedNavigation(actionId, nestedActionExtras)
val splitScreenPayload = SplitScreenPayload.InstantNavigationOnAttach(delayedNavigation)
navigationBuilder().action(R.id.action_open_split_screen)
.setArgs(SplitScreenFragment.createPayload(splitScreenPayload))
.navigateInRoot()
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova.app.root.navigation
import io.novafoundation.nova.common.navigation.InterScreenCommunicator
import io.novafoundation.nova.common.utils.singleReplaySharedFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
abstract class FlowInterScreenCommunicator<I : Any, O : Any> :
InterScreenCommunicator<I, O>,
CoroutineScope by CoroutineScope(Dispatchers.Main) {
private var response: O? = null
override val responseFlow = singleReplaySharedFlow<O>()
private var _request: I? = null
override val latestResponse: O?
get() = response
override val lastState: O?
get() = latestResponse
override val lastInput: I?
get() = _request
abstract fun dispatchRequest(request: I)
@OptIn(ExperimentalCoroutinesApi::class)
override fun openRequest(request: I) {
_request = request
response = null
responseFlow.resetReplayCache()
dispatchRequest(request)
}
override fun respond(response: O) {
launch {
this@FlowInterScreenCommunicator.response = response
responseFlow.emit(response)
}
}
}
@@ -0,0 +1,78 @@
package io.novafoundation.nova.app.root.navigation
import android.os.Parcelable
import androidx.annotation.CallSuper
import androidx.lifecycle.asFlow
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.builder.NavigationBuilderRegistry
import io.novafoundation.nova.app.root.navigation.navigators.navigationBuilder
import io.novafoundation.nova.common.navigation.InterScreenCommunicator
import kotlinx.coroutines.flow.Flow
import java.util.UUID
abstract class NavStackInterScreenCommunicator<I : Parcelable, O : Parcelable>(
private val navigationHoldersRegistry: NavigationHoldersRegistry
) : InterScreenCommunicator<I, O> {
private val responseKey = UUID.randomUUID().toString()
private val requestKey = UUID.randomUUID().toString()
protected val navController: NavController
get() = navigationHoldersRegistry.firstAttachedNavController!!
// from requester - retrieve from current entry
override val latestResponse: O?
get() = navController.currentBackStackEntry!!.savedStateHandle
.get(responseKey)
// from responder - retrieve from previous (requester) entry
override val lastState: O?
get() = navController.previousBackStackEntry!!.savedStateHandle
.get(responseKey)
override val responseFlow: Flow<O>
get() = createResponseFlow()
// from responder - retrieve from previous (requester) entry
override val lastInput: I?
get() = navController.previousBackStackEntry!!.savedStateHandle
.get(requestKey)
@CallSuper
override fun openRequest(request: I) {
saveRequest(request)
}
fun clearedResponseFlow(): Flow<O> {
navController.currentBackStackEntry!!.savedStateHandle.apply {
remove<O>(requestKey)
remove<O>(responseKey)
}
return createResponseFlow()
}
override fun respond(response: O) {
// previousBackStackEntry since we want to report to previous screen
saveResultTo(navController.previousBackStackEntry!!, response)
}
protected fun saveResultTo(backStackEntry: NavBackStackEntry, response: O) {
backStackEntry.savedStateHandle.set(responseKey, response)
}
private fun saveRequest(request: I) {
navController.currentBackStackEntry!!.savedStateHandle.set(requestKey, request)
}
private fun createResponseFlow(): Flow<O> {
return navController.currentBackStackEntry!!.savedStateHandle
.getLiveData<O>(responseKey)
.asFlow()
}
protected fun navigationBuilder(): NavigationBuilderRegistry {
return navigationHoldersRegistry.navigationBuilder()
}
}
@@ -0,0 +1,7 @@
package io.novafoundation.nova.app.root.navigation.delayedNavigation
import io.novafoundation.nova.common.navigation.DelayedNavigation
import kotlinx.parcelize.Parcelize
@Parcelize
object BackDelayedNavigation : DelayedNavigation
@@ -0,0 +1,8 @@
package io.novafoundation.nova.app.root.navigation.delayedNavigation
import android.os.Bundle
import io.novafoundation.nova.common.navigation.DelayedNavigation
import kotlinx.parcelize.Parcelize
@Parcelize
class NavComponentDelayedNavigation(val globalActionId: Int, val extras: Bundle? = null) : DelayedNavigation
@@ -0,0 +1,46 @@
package io.novafoundation.nova.app.root.navigation.holders
import androidx.navigation.NavController
import io.novafoundation.nova.common.resources.ContextManager
abstract class NavigationHolder(val contextManager: ContextManager) {
var navController: NavController? = null
private set
fun isControllerAttached(): Boolean {
return navController != null
}
fun attach(navController: NavController) {
this.navController = navController
}
/**
* Detaches the current navController only if it matches the one provided.
* This check ensures that if a new screen with a navController is attached,
* it doesn't lose its navController when the previous screen calls detach.
* By verifying equality, we prevent unintended detachment.
*/
fun detachNavController(navController: NavController) {
if (this.navController == navController) {
this.navController = null
}
}
fun detach() {
navController = null
}
fun finishApp() {
contextManager.getActivity()?.finish()
}
fun executeBack() {
val popped = navController!!.popBackStack()
if (!popped) {
contextManager.getActivity()!!.finish()
}
}
}

Some files were not shown because too many files have changed in this diff Show More