Ana içeriğe geç

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.

alt text

  1. Connext SDK, kullanıcı tarafından kaynak zincirinde gönderilecek bir "xcall" işlemi oluşturmak için kullanılır.
  2. 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.

XSwapAndGreetTarget.sol
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.

XSwapAndGreetTarget.sol
// 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 veya OneInch 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();
}