Chain Abstraction
Chain abstraction, Connext'in öncü kullanım durumlarından biridir. Chain abstraction, bir dapp'in herhangi bir zincirden mantığı yürütmesine olanak tanırken, kullanıcıların ağları değiştirmelerini, farklı bir zincirde işlem imzalamalarını veya farklı bir zincirde gaz harcamalarını gerektirmez. Bu model, daha üst katmanlarda kullanılarak zincirleri tamamen kullanıcıdan soyutlayabilir ve kullanıcıların hangi zincirde olduklarını bilinçli bir şekilde düşünmek zorunda olmamalarını sağlar.
Örneğin, Aave için bir zincir soyutlama katmanı basit, iki adımlı bir süreci içerir.
- Connext SDK, kullanıcı tarafından kaynak zincirinde gönderilecek bir "xcall" işlemi oluşturmak için kullanılır.
- Hedef zincirde dağıtılan bir bağdaştırıcı sözleşmesi, "deposit" çağrısını Aave'ye iletir.
Sonunda, Aave sözleşmelerinde herhangi bir değişikliğe gerek yoktur ve maksimum düzeyde basitleştirilmiş UX'e sahip kullanıcılar için zincirler arası mevduat etkinleştirilir.
Zincir Soyutlama Katmanı Oluşturma
Zincir soyutlama iki bölümden oluşur: takaslar ve "ileri aramalar". Takaslar, herhangi bir varlığı köprülenebilir bir varlığa dönüştürmek için kullanılır ve iletme çağrıları, farklı bir zincirdeki bir sözleşmedeki bir işlevi çağırmak için kullanılır. Aşağıdaki bölümlerde bu iki bölümün nasıl uygulanacağı açıklanacaktır.
Akıllı Sözleşme Entegrasyonu
Bu rehber, Foundry kullanan bir sözleşme çalışma alanını kapsayacaktır.
Kurulum
Foundry projesi yapılandırıldıktan sonra, aşağıdaki komutu çalıştırarak connext-integration
deposunu bir altmodül olarak projenize ekleyebilirsiniz:
forge install connext/connext-integration
Kütüphane, lib/connext-integration
dizinine kurulacaktır.
SwapForwarderXReceiver
SwapForwarderXReceiver
, zincir soyutlama desenini takip eden alıcı sözleşmeler tarafından uygulanması gereken soyut bir sözleşmedir.
abstract contract SwapForwarderXReceiver is ForwarderXReceiver, SwapAdapter {
using Address for address;
/// @dev Bu etki alanındaki Connext sözleşmesinin adresi.
constructor(address _connext) ForwarderXReceiver(_connext) {}
/// INTERNAL
/**
* @notice Takas adaptörünü çağırarak verileri hazırlar. Takas edilecek verileri döndürür.
* @dev Bu işlem, xReceive işlevi tarafından çağrıldığı için giriş verileri Connext köprüsü tarafından sağlanır.
* @param _transferId İşlemin transferId'si.
* @param _data Takas edilecek veriler.
* @param _amount Takas edilecek miktar.
* @param _asset Takas edilecek gelen varlık.
*/
function _prepare(
bytes32 _transferId,
bytes memory _data,
uint256 _amount,
address _asset
) internal override returns (bytes memory) {
(address _swapper, address _toAsset, bytes memory _swapData, bytes memory _forwardCallData) = abi.decode(
_data,
(address, address, bytes, bytes)
);
uint256 _amountOut = this.exactSwap(_swapper, _amount, _asset, _toAsset, _swapData);
return abi.encode(_forwardCallData, _amountOut, _asset, _toAsset, _transferId);
}
}
Dikkat edilmesi gereken nokta, _prepare
işlevinin bytes memory _data
parametresini beklemesidir ve bu parametre aşağıdaki gibi çözümlenecektir:
_swapper
: Hedef takas için kullanılacak belirli takasçı_toAsset
: Takas edilmesi gereken varlık._swapData
: Çevrimdışı olarak oluşturulacak kodlanmış takas verileri (bir sonraki bölümde Chain Abstraction SDK kullanarak)._forwardCallData
: Alıcı sözleşmenin çağıracağı ileri çağrı verileri.
xReceiver Adaptör Sözleşmesi
xReceiver adaptör sözleşmesi, entegratörlerin oluşturduğu adaptör sözleşmesidir. Bu, kaynak tarafındaki xcall
hedefidir ve takaslar tamamlandıktan sonra fon alır. Bu sözleşme, hedef zincire dağıtılacak ve "hızlı yol yürütme" için yönlendirici ağ tarafından çağrılacaktır.
Sözleşme, basitçe SwapForwarderXReceiver
'ı miras almalı ve _forwardFunctionCall
yöntemini uygulamalıdır. Aşağıda basit bir örnek gösterilmiştir.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import {SwapForwarderXReceiver} from "../../destination/xreceivers/Swap/SwapForwarderXReceiver.sol";
interface IGreeter {
function greetWithTokens(address _token, uint256 _amount, string calldata _greeting) external;
}
contract XSwapAndGreetTarget is SwapForwarderXReceiver {
IGreeter public immutable greeter;
constructor(address _greeter, address _connext) SwapForwarderXReceiver(_connext) {
greeter = IGreeter(_greeter);
}
/// INTERNAL
function _forwardFunctionCall(
bytes memory _preparedData,
bytes32 /*_transferId*/,
uint256 /*_amount*/,
address /*_asset*/
) internal override returns (bool) {
(bytes memory _forwardCallData, uint256 _amountOut, , address _toAsset) = abi.decode(
_preparedData,
(bytes, uint256, address, address)
);
// Çağrı verilerini çözümle
string memory greeting = abi.decode(_forwardCallData, (string));
// Çağrıyı iletecek
TransferHelper.safeApprove(_toAsset, address(greeter), _amountOut);
greeter.greetWithTokens(_toAsset, _amountOut, greeting);
return true;
}
}
_forwardFunctionCall
fonksiyonu, kodlanmış verileri açar ve içerisinde işlev çağrısı için çağrı verilerini, takas sonrasında alınan token miktarını ve takas yapılan varlığın token sözleşme adresini içerir. Fonksiyon daha sonra çağrıyı greeter
sözleşmesine ileterek kullanıcıya bir mesaj ile selam verirken bazı tokenleri takas eder.
Chain Abstraction SDK
Chain Abstraction SDK, takaslar için _swapData
oluşturma sürecini çok basit hale getirir.
Kurulum
SDK'yı yüklemek için Node.js v18 kullanın. Aşağıdaki komutla SDK'yı yükleyebilirsiniz.
npm install @connext/chain-abstraction
getPoolFeeForUniV3
getPoolFeeForUniV3
fonksiyonu, verilen bir token çifti için UniV3 havuzunun poolFee'sini döndürür. Bu, tokenları takas etmek için havuz tarafından alınan ücrettir.
export const getPoolFeeForUniV3 = async (
domainId: string,
rpc: string,
token0: string,
token1: string,
):
Fonksiyon dört parametre alır:
domainId
: Hedef etki alanı kimliği.rpc
: Belirli bir etki alanı için RPC uç noktası.token0
: İlk token adresi.token1
: İkinci token adresi.
Fonksiyon, UniV3 havuzunun poolFee'sini temsil eden bir dizeye çözülen bir Promise
döndürür.
Örnek
// Varlık adresleri
const POLYGON_WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619";
const POLYGON_USDC = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
// Etki alanı ayrıntıları
const POLYGON_DOMAIN_ID = "1886350457";
const POLYGON_RPC_URL = "https://polygon.llamarpc.com";
const poolFee = await getPoolFeeForUniV3(POLYGON_DOMAIN_ID, POLYGON_RPC_URL, POLYGON_WETH, POLYGON_USDC);
console.log(poolFee);
getXCallCallData
getXCallCallData
fonksiyonu, xcall
'a iletilmek üzere çağrı verilerini oluşturur. Bu, hedef etki alanındaki bir hedef sözleşmeye yapılacak takas ve yönlendirme çağrısını içeren kodlanmış "iç" çağrı verilerini içeren "dış" çağrı verileridir.
export const getXCallCallData = async (
domainId: string,
swapper: Swapper,
forwardCallData: string,
params: DestinationCallDataParams,
)
Fonksiyon dört parametre alır.
domainId
: Hedef etki alanını temsil eden bir dize.swapper
: Kullanılacak olan swapper ı temsil eden bir dize.UniV2
,UniV3
veyaOneInch
olabilir.forwardCallData
:abiencoder
kullanarak hedef sözleşmeye iletmek için kodlanmış veriler.params
: Aşağıdaki alanları içeren bir nesneyi içeren bir nesne.{
fallback: string;
swapForwarderData: {
toAsset: string;
swapData: {
amountOutMin: string;
} | {
amountOutMin: string;
poolFee: string;
};
forwardCallData: {
cTokenAddress: string;
underlying: string;
minter: string;
} | {} | {};
}
}fallback
: İleri yönlendirme çağrısı hedef etki alanında başarısız olursa fonların gönderileceği yedek adres.swapForwarderData
: Aşağıdaki alanları içeren bir nesne.toAsset
: Hedef etki alanında takas yapılacak tokenin adresi.swapData
: Hedef etki alanındaki takasçı sözleşmesinin takas işlemini gerçekleştirmek için kullanacağı çağrı verileri.forwardCallData
: Hedef etki alanındaki xReceive hedefinde ileri çağrıda kullanılacak çağrı verileri.
Fonksiyon, kodlanmış çağrı verilerini bir dize olarak döndürür.
Örnek
const rpcURL = "https://bsc-dataseed.binance.org";
const signer = new Wallet(process.env.PRIVATE_KEY ?? "", new providers.JsonRpcProvider(rpcURL));
const signerAddress = signer.address;
// ORIGIN SIDE
const BNB_NATIVE = constants.AddressZero;
const BNB_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
const amountIn = BigNumber.from("5000000000000000");
const fromAsset = BNB_NATIVE;
const toAsset = BNB_USDC;
// DESTINATION SIDE
const POLYGON_WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619";
const POLYGON_USDC = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
const POLYGON_CTOKEN_WETH = "0xD809c769A04246855fee98423B180C7CCa6bF07c"; // https://app.midascapital.xyz/137/pool/5
const midasProtocolTarget = "0x5d7663c5483A46e7794b652aF8f155775E4F390C";
// Calldata oluşturma için parametreler const POLYGON_DOMAIN_ID = "1886350457"; const POLYGON_RPC_URL = "https://polygon.llamarpc.com"; const target = XReceiveTarget.MidasProtocol; const swapper = Swapper.UniV3; const poolFee = await getPoolFeeForUniV3(POLYGON_DOMAIN_ID, POLYGON_RPC_URL, POLYGON_WETH, POLYGON_USDC);
const params: DestinationCallDataParams = { fallback: signerAddress, swapForwarderData: { toAsset: POLYGON_WETH, swapData: { amountOutMin: "0", poolFee: poolFee, } }, }; const forwardCallData = defaultAbiCoder.encode( ["address", "address", "address"], [POLYGON_CTOKEN_WETH, POLYGON_WETH, signerAddress], ); const callDataForMidasProtocolTarget = await getXCallCallData(POLYGON_DOMAIN_ID, swapper, forwardCallData, params); const swapAndXCallParams = { originDomain: "6450786", destinationDomain: "1886350457", fromAsset, // BNB toAsset, // USDC amountIn: amountIn.toString(), to: midasProtocolTarget, relayerFeeInNativeAsset: "1000000000000000", // 0.001 BNB callData: callDataForMidasProtocolTarget, };
const txRequest = await prepareSwapAndXCall(swapAndXCallParams, signerAddress);
if (txRequest) {
const tx = await signer.sendTransaction({ ...txRequest });
console.log(SwapAndXCall işlemi tamamlandı. İşlem: ${tx.hash}
);
await tx.wait();
}
### `prepareSwapAndXCall`
`prepareSwapAndXCall` fonksiyonu, `SwapAndXCall` girişlerini hazırlar ve çağrı verilerini kodlar. RPC sağlayıcısına gönderilmek üzere bir `providers.TransactionRequest` nesnesi döndürür.
```ts
export const prepareSwapAndXCall = async (
params: SwapAndXCallParams,
signerAddress: string,
):
İki parametre alır:
signerAddress
(gerekli): İşlemin gönderileceği imzalayanın adresi."params": Aşağıdaki alanları içeren bir nesne:
"originDomain" (gerekli): Kaynak alan kimliği.
destinationDomain
(gerekli): Hedef alan kimliği.fromAsset
(gerekli): Takas edilecek varlığın adresi.toAsset
(gerekli): Takas edilecek varlığın adresi.amountIn
(gerekli): fromAsset belirteçlerinin sayısı.to
(gerekli): Varlığın gönderileceği ve hedefteki arama verileriyle aranacak adres."temsilci" (isteğe bağlı): Hedef etki alanında varsayılan olarak "to" olan yedek adres.
kayma
(isteğe bağlı): BPS'de varsayılan olarak 300 olan kabul edilebilir maksimum kayma. Örneğin, 300 değeri %3 kayma anlamına gelir.route
(isteğe bağlı): Takas sözleşmesinin adresi ve takas sözleşmesinin çağrılacağı veriler.callData
(isteğe bağlı): Çalıştırılacak çağrı verileri (boş olabilir: "0x").relayerFeeInNativeAsset
(isteğe bağlı): Yerel varlıktaki ücret tutarı.relayerFeeInTransactingAsset
(isteğe bağlı): İşlem yapan varlıktaki ücret tutarı.{
originDomain: string,
destinationDomain: string,
fromAsset: string,
toAsset: string,
amountIn: string,
to: string,
relayerFeeInNativeAsset: string | undefined,
relayerFeeInTransactingAsset: string | undefined,
delegate: string | undefined,
slippage: string | undefined,
route: {
swapper: string,
swapData: string,
} | undefined,
callData: string | undefined,
}
İşlev, RPC sağlayıcısına gönderilmek üzere bir "providers.TransactionRequest" nesnesine çözümlenen bir Promise döndürür.
Example
const rpcURL = "https://polygon.llamarpc.com";
const signer = new Wallet(process.env.PRIVATE_KEY ?? "", new providers.JsonRpcProvider(rpcURL));
const signerAddress = signer.address;
const POLYGON_WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619";
const POLYGON_USDC = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
const SWAP_AND_XCALL_ADDRESS = "0x697075f4A3Ce358d125281134e98d594D8Bb472e";
const amountIn = BigNumber.from("1000000000000000");
const fromAsset = POLYGON_WETH;
const swapAndXCallParams = {
originDomain: "1886350457",
destinationDomain: "6450786",
fromAsset, // WETH
toAsset: POLYGON_USDC, // USDC
amountIn: amountIn.toString(),
to: signerAddress,
relayerFeeInTransactingAsset: "100000", // 0.1 USDC
};
const wethContract = new Contract(POLYGON_WETH, WETH_ABI, signer);
const gasPrice = "500000000000";
const allowance = await wethContract.allowance(signerAddress, SWAP_AND_XCALL_ADDRESS);
if (amountIn.gt(allowance)) {
console.log(`Approving... amountIn: ${amountIn.toString()}, allowance: ${allowance.toString()}`);
const tx = await wethContract.approve(SWAP_AND_XCALL_ADDRESS, amountIn, { gasPrice });
console.log(`Approve tx mined... tx: ${tx.hash}`);
await tx.wait();
}
const txRequest = await prepareSwapAndXCall(swapAndXCallParams, signerAddress);
if (txRequest) {
const tx = await signer.sendTransaction({ ...txRequest, gasPrice });
console.log(`SwapAndXCall tx mined. tx: ${tx.hash}`);
await tx.wait();
}