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.
Chain abstraction is currently in experimental phase and requires the @ChainAbstractionExperimentalApi
annotation.
How It Works
When a dapp requests a transaction through eth_sendTransaction
, you need to:
- Detect if chain abstraction is needed
- Generate necessary bridging transactions
- Handle transaction execution and monitoring
- Complete the original transaction once bridging is done
The following sequence diagram illustrates the complete flow of a chain abstraction operation, from the initial dapp request to the final transaction confirmation
Methods
Following are the methods from WalletKit that you will use in implementing chain abstraction.
Prepare
This method checks if a transaction requires additional bridging transactions beforehand.
public prepare(params: {
transaction: ChainAbstractionTypes.Transaction;
}): Promise<ChainAbstractionTypes.CanFulfilResponse>;
Status
Helper method used to check the fulfilment status of a bridging operation.
public abstract fulfilmentStatus(params: {
fulfilmentId: string;
}): Promise<ChainAbstractionTypes.FulfilmentStatusResponse>;
Usage
When your wallet receives an eth_sendTransaction
request, first check if chain abstraction is needed using the prepare
method, if it is, you need to sign all the fulfilment 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, you need to broadcast the initial transaction and await for the transaction hash and receipt. If the operation is not successful, you need to send the JsonRpcError to the dapp and display the error to the user.
walletkit.on("session_request", async (event) => {
const {
id,
topic,
params: { request, chainId },
} = event;
if (request.method === "eth_sendTransaction") {
const originalTransaction = request.params[0];
// Check if bridging transactions are required
const prepareResponse = await wallet.prepare({
transaction: {
...originalTransaction,
chainId,
},
});
if (prepareResponse.status === "error") {
// Display error to user and respond to dapp
await wallet.respondSessionRequest({
topic,
response: formatJsonRpcResult(id, prepareResponse.reason),
});
return;
}
if (prepareResponse.status === "available") {
const { transactions, funding, fulfilmentId, checkIn } = prepareResponse.data;
// Display bridging information to user
// Once approved, execute bridging transactions
for (const transaction of transactions) {
const signedTransaction = await wallet.signTransaction(transaction);
await wallet.sendTransaction(signedTransaction);
}
// Monitor bridging status
const statusInterval = setInterval(async () => {
const statusReponse = await wallet.status({ fulfilmentId });
if (statusResponse.status === "completed") {
clearInterval(statusInterval);
// Proceed with original transaction
const signedTransaction = await wallet.signTransaction(originalTransaction);
const result = await wallet.sendTransaction(signedTransaction);
await wallet.respondSessionRequest({
topic,
response: formatJsonRpcResult(id, result)
});
}
}, checkIn);
} else {
// No bridging required, process transaction normally
const signedTransaction = await wallet.signTransaction(originalTransaction);
const result = await wallet.sendTransaction(signedTransaction);
await wallet.respondSessionRequest({
topic,
response: formatJsonRpcResult(id, result)
});
}
}
});
Types
Following are the types that are used in the chain abstraction methods.
namespace ChainAbstractionTypes {
type FundingFrom = {
tokenContract: string;
amount: string;
chainId: string;
symbol: string;
};
type Transaction = {
from: string;
to: string;
value: string;
chainId: string;
gas?: string;
gasPrice?: string;
data?: string;
nonce?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
};
type CanFulfilResponse =
| {
status: "not_required";
}
| {
status: "available";
data: {
fulfilmentId: string;
checkIn: number;
transactions: Transaction[];
funding: FundingFrom[];
};
}
| {
status: "error";
reason: string; // reason can be insufficientFunds | insufficientGasFunds | noRoutesAvailable
};
type FulfilmentStatusResponse = {
createdAt: number;
} & (
| {
status: "completed";
}
| { status: "pending"; checkIn: number }
);
}
For example, check our sample wallet implementation with React Native.