diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d5a88ba..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: 2 -jobs: - build: - working_directory: ~/code - docker: - - image: cimg/android:2021.10.2-node - environment: - JVM_OPTS: -Xmx3200m - steps: - - checkout - - run: - name: Generate google-services.json - command: echo $GOOGLE_SERVICES | base64 --decode > app/google-services.json - - run: - name: Download Dependencies - command: ./gradlew androidDependencies - - run: - name: Assemble - command: ./gradlew assemble - - run: - name: Unit Tests - command: ./gradlew lint test - - store_artifacts: - path: core/build/reports - destination: core/reports - - store_artifacts: - path: solana/build/reports - destination: solana/reports - - store_test_results: - path: core/build/test-results - - store_test_results: - path: solana/build/test-results - -workflows: - version: 2 - workflow: - jobs: - - build diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..5f11316 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,31 @@ +name: Setup + +description: Setup + +inputs: + google_services: + description: 'Google Services file' + required: true + +runs: + using: "composite" + steps: + - uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 17 + - uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + + - name: Check Gradle wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Put Google Services file + env: + GOOGLE_SERVICES: ${{ inputs.google_services }} + run: echo "$GOOGLE_SERVICES" | base64 -d > app/google-services.json + shell: bash diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e8ecb4c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,8 @@ +## Ticket + + +## Changes + + +## Screenshots + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3a8a9e1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,140 @@ +name: CI + +on: push + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup + with: + google_services: ${{ secrets.GOOGLE_SERVICES }} + + - name: Run lint + run: ./gradlew lint + + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup + with: + google_services: ${{ secrets.GOOGLE_SERVICES }} + + - name: Run test + run: ./gradlew test + + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [ lint, test ] + steps: + - uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup + with: + google_services: ${{ secrets.GOOGLE_SERVICES }} + + - name: Build release + run: ./gradlew clean assembleRelease --stacktrace + + distribute: + if: startsWith(github.ref_name, 'release/') + name: Distribute + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: build + steps: + - uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup + with: + google_services: ${{ secrets.GOOGLE_SERVICES }} + + - name: Put files + env: + KEYSTORE: ${{ secrets.KEYSTORE }} + SIGNING: ${{ secrets.SIGNING }} + APP_DISTRIBUTION: ${{ secrets.APP_DISTRIBUTION }} + run: | + echo "Adding files" + TMP_SECRETS_PATH=secrets + mkdir ${TMP_SECRETS_PATH} + echo $KEYSTORE | base64 -d > "${TMP_SECRETS_PATH}"/portto.jjs + echo $SIGNING | base64 -d > "${TMP_SECRETS_PATH}"/signing.properties + echo $APP_DISTRIBUTION | base64 -d > "${TMP_SECRETS_PATH}"/app-distribution.json + echo "All files Added ✅" + + - name: Fill Infura id + run: sed -i 's/INFURA_ID.*/INFURA_ID = \"${{ secrets.INFURA_ID }}\"/' app/src/main/java/com/portto/valuedapp/Config.kt + + - name: Distribute sample app + run: bash ./scripts/distribute.sh + + publish: + if: github.ref_name == 'main' + name: Publish + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: build + steps: + - uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup + with: + google_services: ${{ secrets.GOOGLE_SERVICES }} + + - name: Publish library + env: + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_PASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} + run: bash ./scripts/publish.sh + + - name: Get version + run: | + echo "version=$(grep "versionName" dependencies.gradle | awk -F: '{print $2}' | tr -d ' '\',)" >> $GITHUB_ENV + + - name: Create and push tag + run: | + git config user.name "GitHub Actions" + git config user.email noreply@github.com + git tag -a ${{ env.version }} -m "Release v${{ env.version }}" + git push origin ${{ env.version }} + + - name: Create release + uses: softprops/action-gh-release@v1 + with: + name: v${{ env.version }} + tag_name: ${{ env.version }} + draft: false + prerelease: false + + - name: Create pull request (main -> develop) + run: > + gh pr create + --base develop + --head main + --title '[${{ env.version }}] Merge main into develop' + --body 'Created by Github Actions' + --reviewer Doge-is-Dope,kihonyoo,imjacklai + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create-asana-attachment.yml b/.github/workflows/create-asana-attachment.yml new file mode 100644 index 0000000..3913a20 --- /dev/null +++ b/.github/workflows/create-asana-attachment.yml @@ -0,0 +1,17 @@ +name: Asana +on: + pull_request: + types: [opened, reopened] + +jobs: + create-asana-attachment-job: + runs-on: ubuntu-latest + name: Create pull request attachments on Asana tasks + steps: + - name: Create pull request attachments + uses: Asana/create-app-attachment-github-action@latest + id: postAttachment + with: + asana-secret: ${{ secrets.ASANA_SECRET }} + - name: Log output status + run: echo "Status is ${{ steps.postAttachment.outputs.status }}" \ No newline at end of file diff --git a/README.md b/README.md index 8a0daa2..2856ea9 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ # BloctoSDK ![Maven Central](https://img.shields.io/maven-central/v/com.portto.sdk/core) -![CircleCI](https://img.shields.io/circleci/build/gh/portto/blocto-android-sdk/main) +![Github Action](https://github.com/portto/blocto-android-sdk/actions/workflows/ci.yml/badge.svg) ![GitHub](https://img.shields.io/github/license/portto/blocto-android-sdk) Integrate Blocto service into your dApp on Android. Currently support -* Solana * Ethereum +* Arbitrum +* Optimism * BNB Chain * Polygon * Avalanche +* Solana * More blockchains are coming soon > For Flow, it's recommended to use [fcl](https://github.com/portto/fcl-android). Check the [documents](https://docs.blocto.app/blocto-sdk/android-sdk/flow) for more info. diff --git a/app/build.gradle b/app/build.gradle index 7c170b9..91efeab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,6 +18,7 @@ android { targetSdk versions.compileSdk versionCode 1 versionName "1.0.0" + versionNameSuffix "-" + getDate() } def signingFilePath = "secrets/signing.properties" @@ -67,6 +68,10 @@ android { } } +static def getDate() { + return new Date().format('yyyyMMddHHmm') +} + dependencies { implementation project(':evm') implementation project(':solana') @@ -92,9 +97,9 @@ dependencies { exclude module: 'bcprov-jdk15on' } - implementation("com.nftco:flow-jvm-sdk:0.7.1") { + implementation("com.nftco:flow-jvm-sdk:0.7.3") { exclude module: 'bcprov-jdk15on' } - implementation("io.grpc:grpc-okhttp:1.47.0") + implementation("io.grpc:grpc-okhttp:1.50.2") implementation platform("com.google.firebase:firebase-bom:29.3.1") } diff --git a/app/src/main/java/com/portto/valuedapp/Config.kt b/app/src/main/java/com/portto/valuedapp/Config.kt index b940d44..99da5f3 100644 --- a/app/src/main/java/com/portto/valuedapp/Config.kt +++ b/app/src/main/java/com/portto/valuedapp/Config.kt @@ -4,8 +4,8 @@ object Config { const val INFURA_ID = "" // Your Blocto App ID - const val APP_ID_MAINNET = "49618b1e-461b-4134-8f52-4307dd542a88" - const val APP_ID_TESTNET = "57f397df-263c-4e97-b61f-15b67b9ce285" + const val APP_ID_MAINNET = "0896e44c-20fd-443b-b664-d305b52fe8e8" + const val APP_ID_TESTNET = "0896e44c-20fd-443b-b664-d305b52fe8e8" // Required by Flow const val FLOW_APP_IDENTIFIER = "Awesome App (v0.0)" diff --git a/app/src/main/java/com/portto/valuedapp/evm/EvmChain.kt b/app/src/main/java/com/portto/valuedapp/evm/EvmChain.kt index 260954f..ce0d6f3 100644 --- a/app/src/main/java/com/portto/valuedapp/evm/EvmChain.kt +++ b/app/src/main/java/com/portto/valuedapp/evm/EvmChain.kt @@ -1,10 +1,14 @@ package com.portto.valuedapp.evm +import com.portto.sdk.core.Blockchain import com.portto.valuedapp.Config enum class EvmChain( + val blockchain: Blockchain, val title: String, val symbol: String, + val mainnetChainId: Int, + val testnetChainId: Int, val mainnetContractAddress: String, val testnetContractAddress: String, val mainnetRpcUrl: String, @@ -14,43 +18,81 @@ enum class EvmChain( ) { ETHEREUM( + blockchain = Blockchain.ETHEREUM, title = "Ethereum", symbol = "ETH", - mainnetContractAddress = "0xfde90c9Bc193F520d119302a2dB8520D3A4408c8", - testnetContractAddress = "0x58F385777aa6699b81f741Dd0d5B272A34C1c774", + mainnetChainId = 1, + testnetChainId = 5, + mainnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", + testnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", mainnetRpcUrl = "https://mainnet.infura.io/v3/${Config.INFURA_ID}", - testnetRpcUrl = "https://rinkeby.blocto.app", + testnetRpcUrl = "https://goerli.infura.io/v3/${Config.INFURA_ID}", mainnetExplorerDomain = "etherscan.io", - testnetExplorerDomain = "rinkeby.etherscan.io" + testnetExplorerDomain = "goerli.etherscan.io" ), BNB_CHAIN( + blockchain = Blockchain.BNB_CHAIN, title = "BNB Chain", symbol = "BNB", - mainnetContractAddress = "0xfde90c9Bc193F520d119302a2dB8520D3A4408c8", - testnetContractAddress = "0xfde90c9Bc193F520d119302a2dB8520D3A4408c8", + mainnetChainId = 56, + testnetChainId = 97, + mainnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", + testnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", mainnetRpcUrl = "https://bsc-dataseed.binance.org", testnetRpcUrl = "https://data-seed-prebsc-1-s1.binance.org:8545", mainnetExplorerDomain = "bscscan.com", testnetExplorerDomain = "testnet.bscscan.com" ), POLYGON( + blockchain = Blockchain.POLYGON, title = "Polygon", symbol = "MATIC", - mainnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", - testnetContractAddress = "0xfde90c9Bc193F520d119302a2dB8520D3A4408c8", + mainnetChainId = 137, + testnetChainId = 80001, + mainnetContractAddress = "0xD76bAA840e3D5AE1C5E5C7cEeF1C1A238687860e", + testnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", mainnetRpcUrl = "https://polygon-mainnet.infura.io/v3/${Config.INFURA_ID}", testnetRpcUrl = "https://polygon-mumbai.infura.io/v3/${Config.INFURA_ID}", mainnetExplorerDomain = "polygonscan.com", testnetExplorerDomain = "mumbai.polygonscan.com" ), AVALANCHE( + blockchain = Blockchain.AVALANCHE, title = "Avalanche", symbol = "AVAX", - mainnetContractAddress = "0xfde90c9Bc193F520d119302a2dB8520D3A4408c8", - testnetContractAddress = "0xfde90c9Bc193F520d119302a2dB8520D3A4408c8", + mainnetChainId = 43114, + testnetChainId = 43113, + mainnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", + testnetContractAddress = "0xD76bAA840e3D5AE1C5E5C7cEeF1C1A238687860e", mainnetRpcUrl = "https://api.avax.network/ext/bc/C/rpc", testnetRpcUrl = "https://api.avax-test.network/ext/bc/C/rpc", mainnetExplorerDomain = "snowtrace.io", testnetExplorerDomain = "testnet.snowtrace.io" - ) + ), + ARBITRUM( + blockchain = Blockchain.ARBITRUM, + title = "Arbitrum", + symbol = "ETH", + mainnetChainId = 42161, + testnetChainId = 421613, + mainnetContractAddress = "0x806243c7368a90D957592B55875eF4C3353C5bEa", + testnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", + mainnetRpcUrl = "https://arb1.arbitrum.io/rpc", + testnetRpcUrl = "https://arbitrum-goerli.public.blastapi.io", + mainnetExplorerDomain = "arbiscan.io", + testnetExplorerDomain = "goerli.arbiscan.io" + ), + OPTIMISM( + blockchain = Blockchain.OPTIMISM, + title = "Optimism", + symbol = "ETH", + mainnetChainId = 10, + testnetChainId = 420, + mainnetContractAddress = "0x806243c7368a90D957592B55875eF4C3353C5bEa", + testnetContractAddress = "0x009c403BdFaE357d82AAef2262a163287c30B739", + mainnetRpcUrl = "https://mainnet.optimism.io", + testnetRpcUrl = "https://endpoints.omniatech.io/v1/op/goerli/public", + mainnetExplorerDomain = "optimistic.etherscan.io", + testnetExplorerDomain = "goerli-optimism.etherscan.io" + ), } diff --git a/app/src/main/java/com/portto/valuedapp/evm/EvmSendTransactionFragment.kt b/app/src/main/java/com/portto/valuedapp/evm/EvmSendTransactionFragment.kt index 15f2a0c..7ecc552 100644 --- a/app/src/main/java/com/portto/valuedapp/evm/EvmSendTransactionFragment.kt +++ b/app/src/main/java/com/portto/valuedapp/evm/EvmSendTransactionFragment.kt @@ -9,10 +9,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import com.portto.sdk.core.BloctoSDK -import com.portto.sdk.evm.avalanche -import com.portto.sdk.evm.bnb -import com.portto.sdk.evm.ethereum -import com.portto.sdk.evm.polygon +import com.portto.sdk.evm.evm import com.portto.sdk.wallet.BloctoEnv import com.portto.sdk.wallet.BloctoSDKError import com.portto.valuedapp.R @@ -206,44 +203,16 @@ class EvmSendTransactionFragment : Fragment(R.layout.fragment_evm_send_transacti onSuccess: (String) -> Unit, onError: (BloctoSDKError) -> Unit ) { - when (viewModel.currentChain) { - EvmChain.ETHEREUM -> BloctoSDK.ethereum.sendTransaction( - context = requireContext(), - fromAddress = fromAddress, - toAddress = contractAddress, - data = data, - value = value, - onSuccess = onSuccess, - onError = onError - ) - EvmChain.BNB_CHAIN -> BloctoSDK.bnb.sendTransaction( - context = requireContext(), - fromAddress = fromAddress, - toAddress = contractAddress, - data = data, - value = value, - onSuccess = onSuccess, - onError = onError - ) - EvmChain.POLYGON -> BloctoSDK.polygon.sendTransaction( - context = requireContext(), - fromAddress = fromAddress, - toAddress = contractAddress, - data = data, - value = value, - onSuccess = onSuccess, - onError = onError - ) - EvmChain.AVALANCHE -> BloctoSDK.avalanche.sendTransaction( - context = requireContext(), - fromAddress = fromAddress, - toAddress = contractAddress, - data = data, - value = value, - onSuccess = onSuccess, - onError = onError - ) - } + BloctoSDK.evm.sendTransaction( + context = requireContext(), + blockchain = viewModel.currentChain.blockchain, + fromAddress = fromAddress, + toAddress = contractAddress, + data = data, + value = value, + onSuccess = onSuccess, + onError = onError + ) } private fun openExplorer(txHash: String) { diff --git a/app/src/main/java/com/portto/valuedapp/evm/EvmSignMessageFragment.kt b/app/src/main/java/com/portto/valuedapp/evm/EvmSignMessageFragment.kt index bbf5105..d489712 100644 --- a/app/src/main/java/com/portto/valuedapp/evm/EvmSignMessageFragment.kt +++ b/app/src/main/java/com/portto/valuedapp/evm/EvmSignMessageFragment.kt @@ -9,10 +9,7 @@ import androidx.lifecycle.lifecycleScope import com.portto.ethereum.sign.EthSigUtil import com.portto.sdk.core.BloctoSDK import com.portto.sdk.core.decodeHex -import com.portto.sdk.evm.avalanche -import com.portto.sdk.evm.bnb -import com.portto.sdk.evm.ethereum -import com.portto.sdk.evm.polygon +import com.portto.sdk.evm.evm import com.portto.sdk.wallet.BloctoEnv import com.portto.sdk.wallet.BloctoSDKError import com.portto.sdk.wallet.evm.EvmSignType @@ -47,41 +44,24 @@ class EvmSignMessageFragment : Fragment(R.layout.fragment_evm_sign_message) { private lateinit var binding: FragmentEvmSignMessageBinding private val viewModel: EvmViewModel by activityViewModels() + private var signType = EvmSignType.ETH_SIGN + private val rpcUrl get() = when (BloctoSDK.env) { BloctoEnv.PROD -> viewModel.currentChain.mainnetRpcUrl BloctoEnv.DEV -> viewModel.currentChain.testnetRpcUrl } + private val chainId get() = when (BloctoSDK.env) { + BloctoEnv.PROD -> viewModel.currentChain.mainnetChainId + BloctoEnv.DEV -> viewModel.currentChain.testnetChainId + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentEvmSignMessageBinding.bind(view) - var signType = EvmSignType.ETH_SIGN - - binding.chipGroup.setOnCheckedStateChangeListener { _, checkedIds -> - val checkedId = checkedIds.firstOrNull() ?: return@setOnCheckedStateChangeListener - when (checkedId) { - binding.ethSign.id -> { - signType = EvmSignType.ETH_SIGN - binding.input.setText("0x416e79206d65737361676520796f752077616e6e61207369676e") - } - binding.personalSign.id -> { - signType = EvmSignType.PERSONAL_SIGN - binding.input.setText("Any message you wanna sign") - } - binding.typedDataV3.id -> { - signType = EvmSignType.TYPED_DATA_SIGN_V3 - binding.input.setText(getString(R.string.default_typed_data_v3)) - } - binding.typedDataV4.id -> { - signType = EvmSignType.TYPED_DATA_SIGN_V4 - binding.input.setText(getString(R.string.default_typed_data_v4)) - } - binding.typedData.id -> { - signType = EvmSignType.TYPED_DATA_SIGN - binding.input.setText(getString(R.string.default_typed_data_v4)) - } - } + binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> + setupMessage() resetView() } @@ -91,6 +71,7 @@ class EvmSignMessageFragment : Fragment(R.layout.fragment_evm_sign_message) { } viewModel.resetView.observe(viewLifecycleOwner) { + setupMessage() resetView() } } @@ -100,6 +81,32 @@ class EvmSignMessageFragment : Fragment(R.layout.fragment_evm_sign_message) { view?.clearFocus() } + private fun setupMessage() { + val checkedId = binding.chipGroup.checkedChipIds.firstOrNull() ?: return + when (checkedId) { + binding.ethSign.id -> { + signType = EvmSignType.ETH_SIGN + binding.input.setText("0x416e79206d65737361676520796f752077616e6e61207369676e") + } + binding.personalSign.id -> { + signType = EvmSignType.PERSONAL_SIGN + binding.input.setText("Any message you wanna sign") + } + binding.typedDataV3.id -> { + signType = EvmSignType.TYPED_DATA_SIGN_V3 + binding.input.setText(getString(R.string.default_typed_data_v3, chainId)) + } + binding.typedDataV4.id -> { + signType = EvmSignType.TYPED_DATA_SIGN_V4 + binding.input.setText(getString(R.string.default_typed_data_v4, chainId)) + } + binding.typedData.id -> { + signType = EvmSignType.TYPED_DATA_SIGN + binding.input.setText(getString(R.string.default_typed_data_v4, chainId)) + } + } + } + private fun signMessage(signType: EvmSignType) { resetView() @@ -153,40 +160,15 @@ class EvmSignMessageFragment : Fragment(R.layout.fragment_evm_sign_message) { viewModel.showError(it) } - when (viewModel.currentChain) { - EvmChain.ETHEREUM -> BloctoSDK.ethereum.signMessage( - context = requireContext(), - fromAddress = address, - signType = signType, - message = message, - onSuccess = onSuccess, - onError = onError - ) - EvmChain.BNB_CHAIN -> BloctoSDK.bnb.signMessage( - context = requireContext(), - fromAddress = address, - signType = signType, - message = message, - onSuccess = onSuccess, - onError = onError - ) - EvmChain.POLYGON -> BloctoSDK.polygon.signMessage( - context = requireContext(), - fromAddress = address, - signType = signType, - message = message, - onSuccess = onSuccess, - onError = onError - ) - EvmChain.AVALANCHE -> BloctoSDK.avalanche.signMessage( - context = requireContext(), - fromAddress = address, - signType = signType, - message = message, - onSuccess = onSuccess, - onError = onError - ) - } + BloctoSDK.evm.signMessage( + context = requireContext(), + blockchain = viewModel.currentChain.blockchain, + fromAddress = address, + signType = signType, + message = message, + onSuccess = onSuccess, + onError = onError + ) } private fun verifySignature(hash: ByteArray, signature: ByteArray): Boolean { diff --git a/app/src/main/java/com/portto/valuedapp/evm/EvmValueDappActivity.kt b/app/src/main/java/com/portto/valuedapp/evm/EvmValueDappActivity.kt index 82b1647..94e81c1 100644 --- a/app/src/main/java/com/portto/valuedapp/evm/EvmValueDappActivity.kt +++ b/app/src/main/java/com/portto/valuedapp/evm/EvmValueDappActivity.kt @@ -8,10 +8,7 @@ import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.portto.sdk.core.BloctoSDK -import com.portto.sdk.evm.avalanche -import com.portto.sdk.evm.bnb -import com.portto.sdk.evm.ethereum -import com.portto.sdk.evm.polygon +import com.portto.sdk.evm.evm import com.portto.sdk.wallet.BloctoEnv import com.portto.sdk.wallet.BloctoSDKError import com.portto.valuedapp.Config.APP_ID_MAINNET @@ -134,27 +131,11 @@ class EvmValueDappActivity : AppCompatActivity() { viewModel.showError(it) } - when (viewModel.currentChain) { - EvmChain.ETHEREUM -> BloctoSDK.ethereum.requestAccount( - context = this, - onSuccess = requestAccountOnSuccess, - onError = requestAccountOnError - ) - EvmChain.BNB_CHAIN -> BloctoSDK.bnb.requestAccount( - context = this, - onSuccess = requestAccountOnSuccess, - onError = requestAccountOnError - ) - EvmChain.POLYGON -> BloctoSDK.polygon.requestAccount( - context = this, - onSuccess = requestAccountOnSuccess, - onError = requestAccountOnError - ) - EvmChain.AVALANCHE -> BloctoSDK.avalanche.requestAccount( - context = this, - onSuccess = requestAccountOnSuccess, - onError = requestAccountOnError - ) - } + BloctoSDK.evm.requestAccount( + context = this, + blockchain = viewModel.currentChain.blockchain, + onSuccess = requestAccountOnSuccess, + onError = requestAccountOnError + ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea4d49c..157dfe1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,8 +18,8 @@ Typed Data Composite Signatures Sign - {\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallet\",\"type\":\"address\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person\"},{\"name\":\"contents\",\"type\":\"string\"}]},\"primaryType\":\"Mail\",\"domain\":{\"name\":\"Ether Mail\",\"version\":\"1\",\"chainId\":4,\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\"},\"message\":{\"from\":{\"name\":\"Cow\",\"wallet\":\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\"},\"to\":{\"name\":\"Bob\",\"wallet\":\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\"},\"contents\":\"Hello, Bob!\"}} - {\"domain\":{\"chainId\":4,\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}]},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}} + {\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallet\",\"type\":\"address\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person\"},{\"name\":\"contents\",\"type\":\"string\"}]},\"primaryType\":\"Mail\",\"domain\":{\"name\":\"Ether Mail\",\"version\":\"1\",\"chainId\":%1$d,\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\"},\"message\":{\"from\":{\"name\":\"Cow\",\"wallet\":\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\"},\"to\":{\"name\":\"Bob\",\"wallet\":\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\"},\"contents\":\"Hello, Bob!\"}} + {\"domain\":{\"chainId\":%1$d,\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}]},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}} Show signature Signature: %1$s isAuthorizedSigner: %1$s diff --git a/build.gradle b/build.gradle index a6b8700..80cd6a6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,15 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { dependencies { - classpath "com.vanniktech:gradle-maven-publish-plugin:0.20.0" - classpath "com.google.gms:google-services:4.3.14" - classpath "com.google.firebase:firebase-appdistribution-gradle:3.0.3" + classpath "com.vanniktech:gradle-maven-publish-plugin:0.22.0" + classpath "com.google.gms:google-services:4.3.15" + classpath "com.google.firebase:firebase-appdistribution-gradle:4.0.0" } } plugins { - id 'com.android.application' version '7.3.0' apply false - id 'com.android.library' version '7.3.0' apply false - id 'org.jetbrains.kotlin.android' version '1.7.0' apply false + id 'com.android.application' version '8.0.1' apply false + id 'com.android.library' version '8.0.1' apply false + id 'org.jetbrains.kotlin.android' version '1.8.21' apply false id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.21' } task clean(type: Delete) { diff --git a/core/build.gradle b/core/build.gradle index cea44a5..e602cca 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -49,7 +49,7 @@ dependencies { implementation "com.google.android.material:material:$versions.material" implementation "com.squareup.okhttp3:okhttp:$versions.okhttp" implementation "com.squareup.okhttp3:logging-interceptor:$versions.okhttp" - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$versions.serialization" + api "org.jetbrains.kotlinx:kotlinx-serialization-json:$versions.serialization" testImplementation "junit:junit:$versions.junit" testImplementation "io.mockk:mockk:$versions.mockk" diff --git a/core/src/main/java/com/portto/sdk/core/Blockchain.kt b/core/src/main/java/com/portto/sdk/core/Blockchain.kt index 71c0251..efc48f0 100644 --- a/core/src/main/java/com/portto/sdk/core/Blockchain.kt +++ b/core/src/main/java/com/portto/sdk/core/Blockchain.kt @@ -6,5 +6,7 @@ enum class Blockchain(val value: String) { BNB_CHAIN("bsc"), POLYGON("polygon"), AVALANCHE("avalanche"), + ARBITRUM("arbitrum"), + OPTIMISM("optimism"), FLOW("flow"), } diff --git a/core/src/main/java/com/portto/sdk/core/BloctoApi.kt b/core/src/main/java/com/portto/sdk/core/BloctoApi.kt index 74f09ac..b6032ad 100644 --- a/core/src/main/java/com/portto/sdk/core/BloctoApi.kt +++ b/core/src/main/java/com/portto/sdk/core/BloctoApi.kt @@ -1,12 +1,13 @@ package com.portto.sdk.core import androidx.annotation.WorkerThread -import androidx.viewbinding.BuildConfig +import com.portto.sdk.core.BloctoApi.toErrorCode import com.portto.sdk.core.BuildConfig.VERSION_NAME -import com.portto.sdk.wallet.BloctoEnv import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -21,12 +22,6 @@ internal object BloctoApi { val jsonType = "application/json; charset=utf-8".toMediaType() - val baseUrl - get() = when (BloctoSDK.env) { - BloctoEnv.PROD -> "https://api.blocto.app" - BloctoEnv.DEV -> "https://api-dev.blocto.app/" - } - private val loggingInterceptor = HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) { HttpLoggingInterceptor.Level.BODY @@ -54,11 +49,17 @@ internal object BloctoApi { .addInterceptor(headerInterceptor) .addInterceptor(loggingInterceptor) .build() + + fun String.toErrorCode() = json.parseToJsonElement(this) + .jsonObject["error_code"] + ?.jsonPrimitive + ?.content + ?: this } -inline fun get(path: String): T { +inline fun get(url: String): T { val request = Request.Builder() - .url("${BloctoApi.baseUrl}/$path") + .url(url) .get() .build() @@ -66,24 +67,33 @@ inline fun get(path: String): T { if (it.isSuccessful) { return BloctoApi.json.decodeFromString(it.body?.string().orEmpty()) } else { - throw Exception("code: ${it.code}, message=${it.body?.string()}") + throw Exception(it.body?.string()?.toErrorCode()) } } } -inline fun post(path: String, requestBody: U): T { +inline fun post( + url: String, + requestBody: U, + headers: Map? = null +): T { val body = BloctoApi.json.encodeToString(requestBody).toRequestBody(BloctoApi.jsonType) val request = Request.Builder() - .url("${BloctoApi.baseUrl}/$path") + .url(url) .post(body) + .apply { + for ((key, value) in (headers ?: emptyMap())) { + addHeader(key, value) + } + } .build() BloctoApi.client.newCall(request).execute().use { if (it.isSuccessful) { return BloctoApi.json.decodeFromString(it.body?.string().orEmpty()) } else { - throw Exception("code: ${it.code}, message=${it.body?.string()}") + throw Exception(it.body?.string()?.toErrorCode()) } } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/portto/sdk/core/BloctoSDK.kt b/core/src/main/java/com/portto/sdk/core/BloctoSDK.kt index d93b1a8..f2c079d 100644 --- a/core/src/main/java/com/portto/sdk/core/BloctoSDK.kt +++ b/core/src/main/java/com/portto/sdk/core/BloctoSDK.kt @@ -7,14 +7,21 @@ import android.net.Uri import android.util.Log import androidx.annotation.VisibleForTesting import com.portto.sdk.core.method.Method +import com.portto.sdk.core.method.RequestAccountMethod import com.portto.sdk.wallet.BloctoEnv import com.portto.sdk.wallet.BloctoSDKError import com.portto.sdk.wallet.Const +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.util.* object BloctoSDK { private var appId: String? = null + private var webSessionId: String? = null private val requestMap = mutableMapOf>() @JvmStatic @@ -47,14 +54,33 @@ object BloctoSDK { } catch (e: ActivityNotFoundException) { if (method.blockchain == Blockchain.FLOW) Log.w("BloctoSDK", "Flow does not support web fallback") - else { - val url = method.encodeToUri( + else + launchWebSDK(context, method, appId, requestId) + } + } + + private fun launchWebSDK( + context: Context, + method: Method<*>, + appId: String, + requestId: String + ) { + val exceptionHandler = CoroutineExceptionHandler { _, error -> + val err = BloctoSDKError.values().find { it.message == error.message } + ?: BloctoSDKError.UNEXPECTED_ERROR + method.onError(err) + } + + MainScope().launch(exceptionHandler) { + val url = withContext(Dispatchers.IO) { + method.encodeToWebUri( authority = Const.webSDKUrl(env), appId = appId, - requestId = requestId + requestId = requestId, + webSessionId = webSessionId ).build().toString() - context.startActivity(WebSDKActivity.newIntent(context, requestId, url)) } + context.startActivity(WebSDKActivity.newIntent(context, requestId, url)) } } @@ -65,6 +91,9 @@ object BloctoSDK { val method = requestMap[requestId] ?: return requestMap.clear() if (handleError(method, uri)) return + if (method is RequestAccountMethod) { + webSessionId = uri.getQueryParameter(Const.KEY_SESSION_ID) + } method.handleCallback(uri) } @@ -79,6 +108,7 @@ object BloctoSDK { @VisibleForTesting fun resetForTesting() { appId = null + webSessionId = null env = BloctoEnv.PROD } } diff --git a/core/src/main/java/com/portto/sdk/core/method/Method.kt b/core/src/main/java/com/portto/sdk/core/method/Method.kt index bcfa410..70582e4 100644 --- a/core/src/main/java/com/portto/sdk/core/method/Method.kt +++ b/core/src/main/java/com/portto/sdk/core/method/Method.kt @@ -1,6 +1,7 @@ package com.portto.sdk.core.method import android.net.Uri +import androidx.annotation.WorkerThread import com.portto.sdk.core.Blockchain import com.portto.sdk.wallet.BloctoSDKError import com.portto.sdk.wallet.Const @@ -24,5 +25,20 @@ abstract class Method( .appendQueryParameter(Const.KEY_REQUEST_ID, requestId) .appendQueryParameter(Const.KEY_METHOD, name) .appendQueryParameter(Const.KEY_BLOCKCHAIN, blockchain.value) + .appendQueryParameter(Const.KEY_PLATFORM, Const.SDK_SOURCE) + } + + @WorkerThread + open fun encodeToWebUri( + authority: String, + appId: String, + requestId: String, + webSessionId: String? = null + ): Uri.Builder { + return Uri.Builder() + .scheme(Const.HTTPS_SCHEME) + .authority(authority) + .appendPath(appId) + .appendPath(blockchain.value) } } diff --git a/core/src/main/java/com/portto/sdk/core/method/RequestAccountMethod.kt b/core/src/main/java/com/portto/sdk/core/method/RequestAccountMethod.kt index b5c796b..25fc4b5 100644 --- a/core/src/main/java/com/portto/sdk/core/method/RequestAccountMethod.kt +++ b/core/src/main/java/com/portto/sdk/core/method/RequestAccountMethod.kt @@ -22,4 +22,16 @@ class RequestAccountMethod( } onSuccess(address) } + + override fun encodeToWebUri( + authority: String, + appId: String, + requestId: String, + webSessionId: String? + ): Uri.Builder { + return super.encodeToWebUri(authority, appId, requestId, webSessionId) + .appendPath(Const.PATH_AUTHN) + .appendQueryParameter(Const.KEY_REQUEST_ID, requestId) + .appendQueryParameter(Const.KEY_REQUEST_SOURCE, Const.SDK_SOURCE) + } } diff --git a/core/src/main/java/com/portto/sdk/core/model/SendTransactionResponse.kt b/core/src/main/java/com/portto/sdk/core/model/SendTransactionResponse.kt new file mode 100644 index 0000000..f239952 --- /dev/null +++ b/core/src/main/java/com/portto/sdk/core/model/SendTransactionResponse.kt @@ -0,0 +1,10 @@ +package com.portto.sdk.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SendTransactionResponse( + @SerialName("authorizationId") + val authorizationId: String +) diff --git a/core/src/main/java/com/portto/sdk/core/model/SignMessageResponse.kt b/core/src/main/java/com/portto/sdk/core/model/SignMessageResponse.kt new file mode 100644 index 0000000..309fe15 --- /dev/null +++ b/core/src/main/java/com/portto/sdk/core/model/SignMessageResponse.kt @@ -0,0 +1,10 @@ +package com.portto.sdk.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SignMessageResponse( + @SerialName("signatureId") + val signatureId: String +) diff --git a/dependencies.gradle b/dependencies.gradle index ee682c8..95e90d4 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,20 +1,20 @@ ext.versions = [ minSdk : 21, compileSdk : 33, - versionCode : 5, - versionName : '0.4.0', + versionCode : 6, + versionName : '0.5.0', - androidx_activity : '1.6.0', - androidx_appcompat : '1.5.1', - androidx_browser : '1.4.0', - androidx_core : '1.9.0', - androidx_fragment : '1.5.3', - androidx_lifecycle : '2.5.1', + androidx_activity : '1.7.1', + androidx_appcompat : '1.6.1', + androidx_browser : '1.5.0', + androidx_core : '1.10.1', + androidx_fragment : '1.5.7', + androidx_lifecycle : '2.6.1', androidx_viewmodel : '2.4.1', androidx_constraint : '2.1.4', coroutine : '1.6.4', flow_sdk : '0.7.1', - material : '1.6.1', + material : '1.9.0', okhttp : '4.10.0', serialization : '1.3.2', timber : '5.0.1', diff --git a/evm/build.gradle b/evm/build.gradle index d8fadda..9644235 100644 --- a/evm/build.gradle +++ b/evm/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' + id 'kotlinx-serialization' } apply from: "$rootDir/dependencies.gradle" diff --git a/evm/src/main/java/com/portto/sdk/evm/Avalanche.kt b/evm/src/main/java/com/portto/sdk/evm/Avalanche.kt deleted file mode 100644 index 08ee5ba..0000000 --- a/evm/src/main/java/com/portto/sdk/evm/Avalanche.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.portto.sdk.evm - -import com.portto.sdk.core.Blockchain -import com.portto.sdk.core.BloctoSDK - -val BloctoSDK.avalanche by lazy { Avalanche() } - -class Avalanche : Evm() { - - override val blockchain: Blockchain - get() = Blockchain.AVALANCHE -} diff --git a/evm/src/main/java/com/portto/sdk/evm/BNBChain.kt b/evm/src/main/java/com/portto/sdk/evm/BNBChain.kt deleted file mode 100644 index 7b43d46..0000000 --- a/evm/src/main/java/com/portto/sdk/evm/BNBChain.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.portto.sdk.evm - -import com.portto.sdk.core.Blockchain -import com.portto.sdk.core.BloctoSDK - -val BloctoSDK.bnb by lazy { BNBChain() } - -class BNBChain : Evm() { - - override val blockchain: Blockchain - get() = Blockchain.BNB_CHAIN -} diff --git a/evm/src/main/java/com/portto/sdk/evm/Ethereum.kt b/evm/src/main/java/com/portto/sdk/evm/Ethereum.kt deleted file mode 100644 index be75287..0000000 --- a/evm/src/main/java/com/portto/sdk/evm/Ethereum.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.portto.sdk.evm - -import com.portto.sdk.core.Blockchain -import com.portto.sdk.core.BloctoSDK - -val BloctoSDK.ethereum by lazy { Ethereum() } - -class Ethereum : Evm() { - - override val blockchain: Blockchain - get() = Blockchain.ETHEREUM -} diff --git a/evm/src/main/java/com/portto/sdk/evm/Evm.kt b/evm/src/main/java/com/portto/sdk/evm/Evm.kt index 5af38e0..ec5e817 100644 --- a/evm/src/main/java/com/portto/sdk/evm/Evm.kt +++ b/evm/src/main/java/com/portto/sdk/evm/Evm.kt @@ -1,9 +1,8 @@ package com.portto.sdk.evm import android.content.Context -import com.portto.sdk.core.Account +import com.portto.sdk.core.Blockchain import com.portto.sdk.core.BloctoSDK -import com.portto.sdk.core.Chain import com.portto.sdk.core.isValidHex import com.portto.sdk.core.method.RequestAccountMethod import com.portto.sdk.evm.method.SendTransactionMethod @@ -12,10 +11,13 @@ import com.portto.sdk.wallet.BloctoSDKError import com.portto.sdk.wallet.evm.EvmSignType import java.math.BigInteger -abstract class Evm : Chain, Account { +val BloctoSDK.evm by lazy { Evm() } - override fun requestAccount( +class Evm { + + fun requestAccount( context: Context, + blockchain: Blockchain, onSuccess: (String) -> Unit, onError: (BloctoSDKError) -> Unit ) { @@ -29,6 +31,7 @@ abstract class Evm : Chain, Account { fun signMessage( context: Context, + blockchain: Blockchain, fromAddress: String, signType: EvmSignType, message: String, @@ -52,6 +55,7 @@ abstract class Evm : Chain, Account { fun sendTransaction( context: Context, + blockchain: Blockchain, fromAddress: String, toAddress: String, data: String, diff --git a/evm/src/main/java/com/portto/sdk/evm/Polygon.kt b/evm/src/main/java/com/portto/sdk/evm/Polygon.kt deleted file mode 100644 index 67c8175..0000000 --- a/evm/src/main/java/com/portto/sdk/evm/Polygon.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.portto.sdk.evm - -import com.portto.sdk.core.Blockchain -import com.portto.sdk.core.BloctoSDK - -val BloctoSDK.polygon by lazy { Polygon() } - -class Polygon : Evm() { - - override val blockchain: Blockchain - get() = Blockchain.POLYGON -} diff --git a/evm/src/main/java/com/portto/sdk/evm/method/SendTransactionMethod.kt b/evm/src/main/java/com/portto/sdk/evm/method/SendTransactionMethod.kt index 03c74aa..b37eee7 100644 --- a/evm/src/main/java/com/portto/sdk/evm/method/SendTransactionMethod.kt +++ b/evm/src/main/java/com/portto/sdk/evm/method/SendTransactionMethod.kt @@ -1,8 +1,13 @@ package com.portto.sdk.evm.method import android.net.Uri +import androidx.annotation.WorkerThread import com.portto.sdk.core.Blockchain +import com.portto.sdk.core.BloctoSDK import com.portto.sdk.core.method.Method +import com.portto.sdk.core.model.SendTransactionResponse +import com.portto.sdk.core.post +import com.portto.sdk.evm.model.SendTransactionRequest import com.portto.sdk.wallet.BloctoSDKError import com.portto.sdk.wallet.Const import java.math.BigInteger @@ -12,7 +17,7 @@ class SendTransactionMethod( private val toAddress: String, private val data: String, private val value: BigInteger = BigInteger.ZERO, - blockchain: Blockchain, + private val blockchain: Blockchain, onSuccess: (String) -> Unit, onError: (BloctoSDKError) -> Unit ) : Method(blockchain, onSuccess, onError) { @@ -36,4 +41,35 @@ class SendTransactionMethod( .appendQueryParameter(Const.KEY_DATA, data) .appendQueryParameter(Const.KEY_VALUE, String.format("%#x", value)) } + + @WorkerThread + override fun encodeToWebUri( + authority: String, + appId: String, + requestId: String, + webSessionId: String? + ): Uri.Builder { + val sessionId = webSessionId ?: kotlin.run { + throw Throwable(BloctoSDKError.SESSION_ID_REQUIRED.message) + } + + val requestBody = SendTransactionRequest( + from = fromAddress, + to = toAddress, + data = data, + value = String.format("%#x", value) + ) + + val headers = mapOf( + Const.HEADER_SESSION_ID to sessionId, + Const.HEADER_REQUEST_ID to requestId, + Const.HEADER_REQUEST_SOURCE to Const.SDK_SOURCE + ) + + val url = "${Const.webApiUrl(BloctoSDK.env)}/${blockchain.value}/${Const.PATH_DAPP}/${Const.PATH_AUTHZ}" + val response: SendTransactionResponse = post(url, listOf(requestBody), headers) + return super.encodeToWebUri(authority, appId, requestId, webSessionId) + .appendPath(Const.PATH_AUTHZ) + .appendPath(response.authorizationId) + } } diff --git a/evm/src/main/java/com/portto/sdk/evm/method/SignMessageMethod.kt b/evm/src/main/java/com/portto/sdk/evm/method/SignMessageMethod.kt index c22b266..7b4ce67 100644 --- a/evm/src/main/java/com/portto/sdk/evm/method/SignMessageMethod.kt +++ b/evm/src/main/java/com/portto/sdk/evm/method/SignMessageMethod.kt @@ -1,8 +1,13 @@ package com.portto.sdk.evm.method import android.net.Uri +import androidx.annotation.WorkerThread import com.portto.sdk.core.Blockchain +import com.portto.sdk.core.BloctoSDK import com.portto.sdk.core.method.Method +import com.portto.sdk.core.model.SignMessageResponse +import com.portto.sdk.core.post +import com.portto.sdk.evm.model.SignMessageRequest import com.portto.sdk.wallet.BloctoSDKError import com.portto.sdk.wallet.Const import com.portto.sdk.wallet.evm.EvmSignType @@ -11,7 +16,7 @@ class SignMessageMethod( private val fromAddress: String, private val signType: EvmSignType, private val message: String, - blockchain: Blockchain, + private val blockchain: Blockchain, onSuccess: (String) -> Unit, onError: (BloctoSDKError) -> Unit ) : Method(blockchain, onSuccess, onError) { @@ -34,4 +39,34 @@ class SignMessageMethod( .appendQueryParameter(Const.KEY_TYPE, signType.type) .appendQueryParameter(Const.KEY_MESSAGE, message) } + + @WorkerThread + override fun encodeToWebUri( + authority: String, + appId: String, + requestId: String, + webSessionId: String? + ): Uri.Builder { + val sessionId = webSessionId ?: kotlin.run { + throw Throwable(BloctoSDKError.SESSION_ID_REQUIRED.message) + } + + val requestBody = SignMessageRequest( + from = fromAddress, + message = message, + method = signType.type + ) + + val headers = mapOf( + Const.HEADER_SESSION_ID to sessionId, + Const.HEADER_REQUEST_ID to requestId, + Const.HEADER_REQUEST_SOURCE to Const.SDK_SOURCE + ) + + val url = "${Const.webApiUrl(BloctoSDK.env)}/${blockchain.value}/${Const.PATH_DAPP}/${Const.PATH_USER_SIGNATURE}" + val response: SignMessageResponse = post(url, requestBody, headers) + return super.encodeToWebUri(authority, appId, requestId, webSessionId) + .appendPath(Const.PATH_USER_SIGNATURE) + .appendPath(response.signatureId) + } } diff --git a/evm/src/main/java/com/portto/sdk/evm/model/SendTransactionRequest.kt b/evm/src/main/java/com/portto/sdk/evm/model/SendTransactionRequest.kt new file mode 100644 index 0000000..0448132 --- /dev/null +++ b/evm/src/main/java/com/portto/sdk/evm/model/SendTransactionRequest.kt @@ -0,0 +1,16 @@ +package com.portto.sdk.evm.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class SendTransactionRequest( + @SerialName("from") + val from: String, + @SerialName("to") + val to: String, + @SerialName("data") + val data: String, + @SerialName("value") + val value: String +) diff --git a/evm/src/main/java/com/portto/sdk/evm/model/SignMessageRequest.kt b/evm/src/main/java/com/portto/sdk/evm/model/SignMessageRequest.kt new file mode 100644 index 0000000..f5b23bb --- /dev/null +++ b/evm/src/main/java/com/portto/sdk/evm/model/SignMessageRequest.kt @@ -0,0 +1,14 @@ +package com.portto.sdk.evm.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SignMessageRequest( + @SerialName("from") + val from: String, + @SerialName("message") + val message: String, + @SerialName("method") + val method: String +) diff --git a/flow/src/main/java/com/portto/sdk/flow/FlowService.kt b/flow/src/main/java/com/portto/sdk/flow/FlowService.kt index bc7661a..339f333 100644 --- a/flow/src/main/java/com/portto/sdk/flow/FlowService.kt +++ b/flow/src/main/java/com/portto/sdk/flow/FlowService.kt @@ -1,11 +1,14 @@ package com.portto.sdk.flow import androidx.annotation.WorkerThread +import com.portto.sdk.core.BloctoSDK import com.portto.sdk.core.get import com.portto.sdk.flow.model.FeePayerResponse - +import com.portto.sdk.wallet.Const object FlowService { @WorkerThread - fun getFeePayer(): FeePayerResponse = get("flow/feePayer") -} \ No newline at end of file + fun getFeePayer(): FeePayerResponse = get( + url = "${Const.bloctoApiUrl(BloctoSDK.env)}/flow/feePayer" + ) +} diff --git a/gradle.properties b/gradle.properties index 773b72f..4986f87 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,8 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -android.disableAutomaticComponentCreation=true +android.defaults.buildfeatures.buildconfig=true +android.nonFinalResIds=false SONATYPE_HOST=S01 RELEASE_SIGNING_ENABLED=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 47ccf25..83a8ada 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Mar 09 10:37:29 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/scripts/distribute.sh b/scripts/distribute.sh new file mode 100644 index 0000000..c67c112 --- /dev/null +++ b/scripts/distribute.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./gradlew clean app:assembleRelease app:appDistributionUploadRelease diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 0000000..6538c12 --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Build and upload the artifacts to 'mavenCentral'. +./gradlew clean publish --no-daemon --no-parallel diff --git a/solana/src/main/java/com/portto/sdk/solana/SolanaService.kt b/solana/src/main/java/com/portto/sdk/solana/SolanaService.kt index aba8fde..26b89de 100644 --- a/solana/src/main/java/com/portto/sdk/solana/SolanaService.kt +++ b/solana/src/main/java/com/portto/sdk/solana/SolanaService.kt @@ -1,14 +1,17 @@ package com.portto.sdk.solana import androidx.annotation.WorkerThread +import com.portto.sdk.core.BloctoSDK import com.portto.sdk.core.post import com.portto.sdk.solana.model.SolanaRawTxRequest import com.portto.sdk.solana.model.SolanaRawTxResponse +import com.portto.sdk.wallet.Const object SolanaService { @WorkerThread fun createRawTransaction(requestBody: SolanaRawTxRequest): SolanaRawTxResponse { - return post("/solana/createRawTransaction", requestBody) + val url = "${Const.bloctoApiUrl(BloctoSDK.env)}/solana/createRawTransaction" + return post(url, requestBody) } -} \ No newline at end of file +} diff --git a/solana/src/main/java/com/portto/sdk/solana/method/SignAndSendTransactionMethod.kt b/solana/src/main/java/com/portto/sdk/solana/method/SignAndSendTransactionMethod.kt index cc68615..2d66b4f 100644 --- a/solana/src/main/java/com/portto/sdk/solana/method/SignAndSendTransactionMethod.kt +++ b/solana/src/main/java/com/portto/sdk/solana/method/SignAndSendTransactionMethod.kt @@ -1,8 +1,13 @@ package com.portto.sdk.solana.method import android.net.Uri +import androidx.annotation.WorkerThread import com.portto.sdk.core.Blockchain +import com.portto.sdk.core.BloctoSDK import com.portto.sdk.core.method.Method +import com.portto.sdk.core.model.SendTransactionResponse +import com.portto.sdk.core.post +import com.portto.sdk.solana.model.SendTransactionRequest import com.portto.sdk.wallet.BloctoSDKError import com.portto.sdk.wallet.Const @@ -12,7 +17,7 @@ class SignAndSendTransactionMethod( val isInvokeWrapped: Boolean, val publicKeySignaturePairs: Map? = null, val appendTx: Map? = null, - blockchain: Blockchain, + private val blockchain: Blockchain, onSuccess: (String) -> Unit, onError: (BloctoSDKError) -> Unit ) : Method(blockchain, onSuccess, onError) { @@ -49,4 +54,36 @@ class SignAndSendTransactionMethod( } } } + + @WorkerThread + override fun encodeToWebUri( + authority: String, + appId: String, + requestId: String, + webSessionId: String? + ): Uri.Builder { + val sessionId = webSessionId ?: kotlin.run { + throw Throwable(BloctoSDKError.SESSION_ID_REQUIRED.message) + } + + val requestBody = SendTransactionRequest( + from = fromAddress, + message = message, + isInvokeWrapped = isInvokeWrapped, + publicKeySignaturePairs = publicKeySignaturePairs, + appendTx = appendTx + ) + + val headers = mapOf( + Const.HEADER_SESSION_ID to sessionId, + Const.HEADER_REQUEST_ID to requestId, + Const.HEADER_REQUEST_SOURCE to Const.SDK_SOURCE + ) + + val url = "${Const.webApiUrl(BloctoSDK.env)}/${blockchain.value}/${Const.PATH_DAPP}/${Const.PATH_AUTHZ}" + val response: SendTransactionResponse = post(url, requestBody, headers) + return super.encodeToWebUri(authority, appId, requestId, webSessionId) + .appendPath(Const.PATH_AUTHZ) + .appendPath(response.authorizationId) + } } diff --git a/solana/src/main/java/com/portto/sdk/solana/model/SendTransactionRequest.kt b/solana/src/main/java/com/portto/sdk/solana/model/SendTransactionRequest.kt new file mode 100644 index 0000000..46c0c41 --- /dev/null +++ b/solana/src/main/java/com/portto/sdk/solana/model/SendTransactionRequest.kt @@ -0,0 +1,18 @@ +package com.portto.sdk.solana.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SendTransactionRequest( + @SerialName("from") + val from: String, + @SerialName("message") + val message: String, + @SerialName("isInvokeWrapped") + val isInvokeWrapped: Boolean, + @SerialName("publicKeySignaturePairs") + val publicKeySignaturePairs: Map?, + @SerialName("appendTx") + val appendTx: Map? +) diff --git a/wallet/src/main/java/com/portto/sdk/wallet/BloctoSDKError.kt b/wallet/src/main/java/com/portto/sdk/wallet/BloctoSDKError.kt index d1bd1fe..6cf916d 100644 --- a/wallet/src/main/java/com/portto/sdk/wallet/BloctoSDKError.kt +++ b/wallet/src/main/java/com/portto/sdk/wallet/BloctoSDKError.kt @@ -8,4 +8,8 @@ enum class BloctoSDKError(val message: String) { UNEXPECTED_ERROR("unexpected_error"), ETH_SIGN_INVALID_HEX_STRING("eth_sign_invalid_hex_string"), FLOW_MISSING_ARG("flow_app_id_and_nonce_are_required_by_authn"), + SESSION_ID_REQUIRED("session_id_required"), + INVALID_SESSION_ID("invalid_session_id"), + MESSAGE_REQUIRED("message_required"), + METHOD_NOT_SUPPORTED("method_not_supported"), } diff --git a/wallet/src/main/java/com/portto/sdk/wallet/Const.kt b/wallet/src/main/java/com/portto/sdk/wallet/Const.kt index 887b857..8b63c72 100644 --- a/wallet/src/main/java/com/portto/sdk/wallet/Const.kt +++ b/wallet/src/main/java/com/portto/sdk/wallet/Const.kt @@ -17,12 +17,19 @@ object Const { private const val BLOCTO_URI_AUTHORITY_PROD = "blocto.app" private const val BLOCTO_URI_AUTHORITY_DEV = "dev.blocto.app" - private const val WEB_SDK_URL_PROD = "wallet.blocto.app" - private const val WEB_SDK_URL_DEV = "wallet-testnet.blocto.app" + private const val BLOCTO_API_URL_PROD = "https://api.blocto.app" + private const val BLOCTO_API_URL_DEV = "https://api-dev.blocto.app" + + private const val WEB_SDK_URL_PROD = "wallet-v2.blocto.app" + private const val WEB_SDK_URL_DEV = "wallet-v2-dev.blocto.app" + + private const val WEB_API_URL_PROD = "https://wallet-v2.blocto.app/api" + private const val WEB_API_URL_DEV = "https://wallet-v2-dev.blocto.app/api" const val HTTPS_SCHEME = "https" const val BLOCTO_SCHEME = "blocto" const val BLOCTO_URI_PATH = "sdk" + const val SDK_SOURCE = "sdk_android" const val KEY_APP_ID = "app_id" const val KEY_REQUEST_ID = "request_id" @@ -47,7 +54,18 @@ object Const { const val KEY_FLOW_APP_ID = "flow_app_id" // Since 0.3.0 (Flow) const val KEY_FLOW_NONCE = "flow_nonce" // Since 0.3.0 (Flow) const val KEY_FLOW_TX = "flow_transaction" // Since 0.3.0 (Flow) + const val KEY_SESSION_ID = "session_id" + const val KEY_REQUEST_SOURCE = "request_source" + const val KEY_PLATFORM = "platform" + const val PATH_DAPP = "dapp" + const val PATH_AUTHN = "authn" + const val PATH_AUTHZ = "authz" + const val PATH_USER_SIGNATURE = "user-signature" + + const val HEADER_SESSION_ID = "Blocto-Session-Identifier" + const val HEADER_REQUEST_ID = "Blocto-Request-Identifier" + const val HEADER_REQUEST_SOURCE = "Blocto-Request-Source" fun bloctoAuthority(env: BloctoEnv): String = when (env) { BloctoEnv.PROD -> BLOCTO_URI_AUTHORITY_PROD @@ -59,8 +77,18 @@ object Const { BloctoEnv.DEV -> BLOCTO_PACKAGE_DEV } + fun bloctoApiUrl(env: BloctoEnv): String = when (env) { + BloctoEnv.PROD -> BLOCTO_API_URL_PROD + BloctoEnv.DEV -> BLOCTO_API_URL_DEV + } + fun webSDKUrl(env: BloctoEnv): String = when (env) { BloctoEnv.PROD -> WEB_SDK_URL_PROD BloctoEnv.DEV -> WEB_SDK_URL_DEV } + + fun webApiUrl(env: BloctoEnv): String = when (env) { + BloctoEnv.PROD -> WEB_API_URL_PROD + BloctoEnv.DEV -> WEB_API_URL_DEV + } }