Skip to content

Commit

Permalink
Merge pull request #199 from reown-com/chain-abstraction
Browse files Browse the repository at this point in the history
added chain abstraction docs
  • Loading branch information
vrajdesai78 authored Jan 3, 2025
2 parents d273e8b + ac470ca commit f1c9c0e
Show file tree
Hide file tree
Showing 9 changed files with 726 additions and 9 deletions.
276 changes: 276 additions & 0 deletions docs/walletkit/android/experimental/chain-abstraction.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
# Chain Abstraction

Chain Abstraction in WalletKit enables users with stablecoins on any network to spend them on-the-fly on a different network. Our Chain Abstraction solution provides a toolkit for wallet developers to integrate this complex functionality using WalletKit.

For example, when an app requests a 100 USDC payment on Base network but the user only has USDC on Arbitrum, WalletKit offers methods to detect this mismatch, generate necessary transactions, track the cross-chain transfer, and complete the original transaction after bridging finishes.

## How It Works

:::note
Apps need to pass `gas` as null, while sending a transaction to allow proper gas estimation by the wallet. Refer to this [guide](../../../appkit/next/experimental/chain-abstraction.mdx) for more details.
:::

When sending a transaction, you need to:
1. Check if the required chain has enough funds to complete the transaction
2. If not, use the `prepare` method to generate necessary bridging transactions
3. Check the status of the bridging transactions and wait for them to be completed
4. Once the bridging transactions are completed, execute the initial transaction

The following sequence diagram illustrates the complete flow of a chain abstraction operation, from the initial dapp request to the final transaction confirmation

![Chain Abstraction Flow](/assets/chain-abstraction-flow.png)

## Methods

The following methods from WalletKit are used in implementing chain abstraction.

:::note
Chain abstraction is currently in an experimental phase and requires the `@ChainAbstractionExperimentalApi` annotation.
:::

### Prepare

This method is used to check if chain abstraction is needed. If it is, it will return a `PrepareSuccess.Available` object with the necessary transactions and funding information.
If it is not, it will return a `PrepareSuccess.NotRequired` object with the original transaction.

```kotlin
@ChainAbstractionExperimentalApi
fun prepare(
initialTransaction: Wallet.Model.InitialTransaction,
onSuccess: (Wallet.Model.PrepareSuccess) -> Unit,
onError: (Wallet.Model.PrepareError) -> Unit
)
```

### Status

This method is used to check the status of the fulfillment operation. It will return a `Status.Completed` object if the operation completed successfully or a `Status.Error` object if it encountered an error.

```kotlin
@ChainAbstractionExperimentalApi
fun status(
fulfilmentId: String,
checkIn: Long,
onSuccess: (Wallet.Model.Status.Completed) -> Unit,
onError: (Wallet.Model.Status.Error) -> Unit
)
```

### Estimate Fees

This method is used to estimate the fees for the fulfillment operation.

```kotlin
@Throws(Exception::class)
@ChainAbstractionExperimentalApi
fun estimateFees(chainId: String): Wallet.Model.EstimatedFees
```

### Get ERC20 Balance

This method is used to get the balance of an ERC20 token on a specific chain.

```kotlin
@Throws(Exception::class)
@ChainAbstractionExperimentalApi
fun getERC20Balance(chainId: String, tokenAddress: String, ownerAddress: String): String
```

### Get Fulfilment Details

This method is used to get the details of the fulfillment operation.

```kotlin
@ChainAbstractionExperimentalApi
fun getTransactionsDetails(
available: Wallet.Model.PrepareSuccess.Available,
onSuccess: (Wallet.Model.TransactionsDetails) -> Unit,
onError: (Wallet.Model.Error) -> Unit
)
```

### Usage

When sending a transaction, first check if chain abstraction is needed using the `prepare` method. If it is needed, you must sign all the fulfillment transactions and broadcast them in parallel.
After that, you need to call the `status` method to check the status of the fulfillment operation.

If the operation is successful, broadcast the initial transaction and await the transaction hash and receipt.
If the operation is unsuccessful, send the JsonRpcError to the dapp and display the error to the user.

```kotlin
val initialTransaction = Wallet.Model.Transaction(...)

WalletKit.prepare(
initialTransaction,
onSuccess = { prepareSuccess ->
when (prepareSuccess) {
is Wallet.Model.PrepareSuccess.Available -> {
//Sign all the fulfilment transactions
//Broadcast the fulfilment transactions in parallel
//Await for the transaction hashes and receipts

//Call the status
WalletKit.status(fulfilmentId, checkIn,
onSuccess = {
//Successfull filfilment operation
//Broadcast the inittial transaction
//Await for the tx hash ande receipt
//Send the response to the Dapp
},
onError = {
//Status error - wallet should send the JsonRpcError to a dapp for given request and display error to the user
}
)
}

is Wallet.Model.PrepareSuccess.NotRequired -> {
//Chain abstraction is not required, handle transaction as usual
}
}
},
onError = { prepareError ->
// One of the possible errors: NoRoutesAvailable, InsufficientFunds, InsufficientGasFunds - wallet should send the JsonRpcError to a dapp for given request and display error to the user
}
)
```

For example, check out implementation of chain abstraction in [sample wallet](https://github.com/WalletConnect/WalletConnectKotlinV2/tree/master/sample/wallet) with Kotlin.

## Testing

To test Chain Abstraction, you can use the [AppKit laboratory](https://appkit-lab.reown.com/library/wagmi/) and try sending USDC with any chain abstraction supported wallet.
You can also use this [sample wallet](https://appdistribution.firebase.dev/i/076a3bc9669d3bee) for testing.

<video controls width="100%" height="100%" style={{ borderRadius: '10px' }}>
<source src="/assets/chain-abstraction-demo.mp4" type="video/mp4" />
</video>

## Types

Following are the types that are used in the chain abstraction methods.

```kotlin
data class Transaction(
var from: String,
var to: String,
var value: String,
var gasLimit: String,
var input: String,
var nonce: String,
var chainId: String
)

data class InitialTransaction(
var from: String,
var to: String,
var value: String,
var input: String,
var chainId: String
)

data class FeeEstimatedTransaction(
var from: String,
var to: String,
var value: String,
var gasLimit: String,
var input: String,
var nonce: String,
var maxFeePerGas: String,
var maxPriorityFeePerGas: String,
var chainId: String
)

sealed class PrepareSuccess {
data class Available(
val fulfilmentId: String,
val checkIn: Long,
val transactions: List<Transaction>,
val initialTransaction: Transaction,
val initialTransactionMetadata: InitialTransactionMetadata,
val funding: List<FundingMetadata>
) : PrepareSuccess()

data class NotRequired(val initialTransaction: Transaction) : PrepareSuccess()
}

data class InitialTransactionMetadata(
var symbol: String,
var amount: String,
var decimals: Int,
var tokenContract: String,
var transferTo: String
)

data class FundingMetadata(
var chainId: String,
var tokenContract: String,
var symbol: String,
var amount: String,
var bridgingFee: String,
var decimals: Int
)

sealed class PrepareError : Model() {
data object NoRoutesAvailable : PrepareError()
data object InsufficientFunds : PrepareError()
data object InsufficientGasFunds : PrepareError()
data class Unknown(val message: String) : PrepareError()
}

sealed class Status : Model() {
data class Completed(val createdAt: Long) : Status()
data class Error(val reason: String) : Status()
}

data class TransactionsDetails(
var fulfilmentDetails: List<TransactionDetails>,
var initialDetails: TransactionDetails,
var bridgeFees: List<TransactionFee>,
var localBridgeTotal: Amount,
var localFulfilmentTotal: Amount,
var localTotal: Amount
)

data class TransactionDetails(
var transaction: FeeEstimatedTransaction,
var transactionFee: TransactionFee
)

data class TransactionFee(
var fee: Amount,
var localFee: Amount
)

data class Amount(
var symbol: String,
var amount: String,
var unit: String,
var formatted: String,
var formattedAlt: String
)
```

## ProGuard rules

If you encounter issues with minification, add the below rules to your application:

```
-keepattributes *Annotation*
-keep class com.sun.jna.** { *; }
-keepclassmembers class com.sun.jna.** {
native <methods>;
*;
}
-keep class uniffi.** { *; }
# Preserve all public and protected fields and methods
-keepclassmembers class ** {
public *;
protected *;
}
-dontwarn uniffi.**
-dontwarn com.sun.jna.**
```
82 changes: 82 additions & 0 deletions docs/walletkit/features/experimental/chain-abstraction.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Wrapper from '../../../components/Home/Wrapper'
import androidLogo from '../../../../static/assets/home/androidLogo.png'
import iosLogo from '../../../../static/assets/home/iosLogo.png'
import rnLogo from '../../../../static/assets/home/rnLogo.png'

# Chain Abstraction

Chain Abstraction allows users to spend stablecoins across different networks seamlessly.
This solution provides wallet developers with a toolkit to integrate cross-chain functionality using WalletKit.

:::note
💡 Support for Chain Abstraction is currently in experimental phase.
:::

## How it works

When an application sends a `wallet_sendTransaction` request for an ERC-20 transfer (such as USDC),
the wallet checks for available tokens across all supported networks. If the user has sufficient funds on any supported network,
they can complete the transaction instantly, regardless of which network holds their tokens.

For example, consider a scenario where an app requests a transfer of 225 USDC on the Base Network.
Even if the user doesn't have USDC on Base, their wallet can automatically source the funds from other networks,
making the experience seamless for both the user and the application.

:::note
💡 Make sure you have enough gas fees in other networks from which bridging will happen.
For example, in given scenario, you need to have enough gas fees on OP Mainnet and Arbitrum.
:::

The diagram below shows an example scenario where a user is interacting with an app and is asked to transfer 225 USDC to the app on Base Network.
The user does not have any USDC on Base, but their wallet seamlessly allows them to source the funds from other networks.

![Chain Abstraction Example](/assets/chain_abstraction_demo.png)

## Get Started

<Wrapper
type="large"
fit={false}
items={[
{
name: 'Android',
type: 'android',
description: 'Get started with WalletKit in Android.',
icon: androidLogo,
href: '../../android/experimental/chain-abstraction'
},
{
name: 'iOS',
type: 'ios',
description: 'Get started with WalletKit in iOS.',
icon: iosLogo,
href: '../../ios/experimental/chain-abstraction',
isWhite: true
},
{
name: 'React Native',
type: 'react-native',
description: 'Get started with WalletKit in React Native.',
icon: rnLogo,
href: '../../react-native/experimental/chain-abstraction'
}
]}
/>

## FAQ

### What are the available networks for Chain Abstraction?

Chain Abstraction is available on the following networks:

- Base
- Arbitrum
- OP Mainnet

### What are the supported tokens for Chain Abstraction?

Currently, Chain Abstraction supports USDC but we are working on adding support for more tokens.

### What are the limitations?

We currently support 1:1 transfers i.e. sourcing funds from one address to another. Make sure that you're transferring minimum 0.55 USDC and have enough gas to pay bridging fees.
Loading

0 comments on commit f1c9c0e

Please sign in to comment.