Önceki blog yazımızda, Delegation Toolkit Nedir ve Bununla Neler İnşa Edebilirsiniz?, MetaMask'ın Delegation Toolkit'inin geliştiricilere nasıl daha esnek ve kullanıcı dostu dapp'ler oluşturma imkânı tanıdığını inceledik. Bunun ilgi çekici bir kullanım senaryosu, kullanıcıların arkadaşlarını minimum sürtüşmeyle sisteme dahil etmesini sağlayan sosyal davet özelliği oluşturmaktır.
Bu eğitim, mevcut bir kullanıcının başka birine sınırlı izinler devredebileceği bu tür bir akışın nasıl uygulanacağını adım adım açıklamaktadır. Bu sayede yeni kullanıcılar, önceden cüzdan kurma ve fonlama gibi olağan engellerle uğraşmadan bir dapp'i keşfedebilir.
Şunu hayal edin: Alice, Bob'un kullandığı bir dapp'i denemesini istiyor. Ona, belirli bir süre içinde cüzdanından küçük bir miktar ETH, örneğin 0,001 ETH talep etmesine olanak tanıyan bir davet gönderiyor. Bob, bir cüzdan eklentisi yüklemesine ya da gas ücretlerini kendisi ödemesine gerek kalmadan dapp'i hemen kullanmaya başlayabilir. Bu süreçte Alice, özel anahtarını ifşa etmeksizin cüzdanı üzerindeki tam kontrolünü korur.
Delegation Toolkit'in, izinli delegasyon ve güvenli zincir dışı imzalar kullanarak mümkün kıldığı kolaylaştırılmış bir katılım deneyimi işte budur. Şimdi inşa edeceğimiz yapının ana hatlarını hızlıca özetleyelim.
Davet Eden (Inviter)
Davet eden, başka birinin sisteme katılmasına yardımcı olmak amacıyla cüzdanına sınırlı erişim paylaşmak isteyen kullanıcıdır. Bu eğitimde davet eden, EIP-4337 tabanlı bir akıllı sözleşme cüzdanı olan MetaMask Delegator hesabı kullanacaktır. Yeni bir delegator hesabı oluşturabilir ya da daha önce kurduysanız mevcut bir hesabı kullanabilirsiniz. Dağıtım ve altyapıyı yönetmek için Pimlico'nun EIP-4337 hizmetlerinden yararlanacağız.
Davet (Invitation)
Bu bağlamda bir davet, delegator hesabı tarafından oluşturulan ve zincir dışında saklanan imzalı bir nesne olan delegasyon'dan ibarettir. Delegator hesabı dağıtılıp ETH ile fonlandıktan sonra, başka bir kullanıcının belirli bir süre içinde belirli miktarda token talep etmesine olanak tanıyan bir delegasyon oluşturabilirsiniz. Bu yapı, davet edilenin cüzdan fonlaması veya önceden gas ödemesi yapması zorunluluğunu ortadan kaldırarak katılım sürecini çok daha sorunsuz hale getirir.
İşleri basit tutmak adına açık delegasyon olarak adlandırılan yöntemi kullanacağız. Bu, imzalı delegasyona erişimi olan herkesin onu kullanabileceği anlamına gelir. Belirli bir alıcıya bağlı olmadığından, davet edenin özel anahtarını yönetmeye veya ifşa etmeye gerek yoktur.
Davet Edilen (Invitee)
Davet edilen, sisteme dahil edilecek kişidir. Uygulamanıza bağlı olarak Harici Sahipli Hesap (EOA) veya Akıllı Sözleşme Hesabı (SCA) kullanabilirler. Bu eğitimde, davet edilen kişi talep bağlantısına tıkladığında otomatik olarak bir EOA oluşturacağız. Gerçek bir dapp'te, davet edilenin zaten bağlı bir cüzdanı olup olmadığını tespit edip onu kullanmak ideal olurdu; ancak bu mantığı uygulamak bu kılavuzun kapsamı dışındadır.
Koşullar
Her davet, nasıl ve ne zaman kullanılabileceğini kontrol etmek için koşullar içerebilir. Caveat olarak da bilinen bu koşullar; bir fonlama limiti, bir kullanım son tarihi veya delegasyonun kaç kez kullanılabileceğine ilişkin kısıtlamalar belirtebilir. Bizim durumumuzda, temel güvenliği sağlamak amacıyla delegasyonu yalnızca bir kez kullanılabilir, maksimum token miktarı (X) ve zaman penceresi (Y) ile sınırlı olacak şekilde ayarlayacağız.
Daveti Kabul Etme
Davet edilen kişi talep bağlantısına tıklayıp daveti kabul ettiğinde, delegasyonu kullanmış olur. Bu süreç, devredilen fonları aktarmak için blok zincirinde bir kullanıcı işlemi (user operation) tetikler. Bu işlemi göndermek ve akışı tamamlamak için yeniden Pimlico'nun altyapısına güveneceğiz.
Akışı anladığınıza göre, uygulamaya geçelim.
Ön Koşullar
İnşa etmeye başlamadan önce aşağıdakilerin hazır olduğundan emin olun:
Node.js (sürüm 18 veya üzeri)
Sepolia test ağı için bir Infura RPC uç noktası
Paymaster, Bundler ve User Operations gibi temel kavramlar dahil olmak üzere EIP-4337 hesap soyutlaması hakkında temel bilgi
Pimlico Bundler ve Paymaster URL'leri
Bağımlılıkları Yükleyin
Önce delegation toolkit'i yükleyin:
npm install @metamask/delegation-toolkitArdından tüm Ethereum etkileşimleri için Viem'i yükleyin:
npm install viemDavet Eden Hesabını Oluşturun
Davet eden hesabı, yeni bir kullanıcının sisteme katılmasına yardımcı olmak için sınırlı izinler paylaşan cüzdandır. Davet edilenin dapp ile ilk etkileşimleri sırasında gas ücretlerini karşılayacak kadar kripto para içermelidir. Bu eğitimde bu rolü üstlenmek için genellikle Gator hesabı olarak adlandırılan bir akıllı sözleşme hesabı kullanacağız.
Bununla birlikte, Pectra yükseltmesi sayesinde EIP-7702 desteğine sahip normal EOA'lar artık izinleri devredebilmektedir. Bu durum, geleneksel cüzdanların akıllı sözleşme hesabına geçmeden izin paylaşımına katılabilmesinin önünü açmaktadır. Bu yaklaşımı ayrı bir yazıda daha ayrıntılı ele alacağız.
Başlamak için projenizin kök dizininde index.ts adlı bir dosya oluşturun ve aşağıdaki kod parçacıklarını ekleyin:
import { http, createPublicClient } from "viem";
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
import { sepolia as chain } from "viem/chains";
import {
Implementation,
toMetaMaskSmartAccount,
} from "@metamask/delegator-core-viem";
const RPCEndpoint = "<INFURA RPC ENDPOINT HERE>";
const transport = http(RPCEndpoint);
const publicClient = createPublicClient({ transport, chain });
const privateKey = generatePrivateKey();
const owner = privateKeyToAccount(privateKey);
const deploySalt = "0x";
const account = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [owner.address, [], [], []],
deploySalt,
signatory: { account: owner },
});
Bir delegator hesabı oluştururken iki temel seçeneğiniz vardır:
Hibrit hesap: Bu tür yalnızca bir imzalayan gerektirir; bu imzalayan bir EOA veya P256 uyumlu herhangi bir imzalayan olabilir. implementation: Implementation.Hybrid kullanılarak belirtilir.
Çoklu imza hesabı: Bu seçenek, delegator hesabı adına imzalamak için birden fazla EOA gerektirir. implementation: Implementation.MultiSig kullanılarak belirtilir.
Bu eğitimde hibrit hesap kullanıyor ve imzalayanı normal bir EOA olarak ayarlıyoruz. Yukarıdaki örnekte özel anahtar oluşturduğumuzu, ancak bunun programatik olarak mevcut bir cüzdanın özel anahtarını kullanacak şekilde ayarlanabileceğini unutmayın.
Delegator hesabını oluşturduktan sonra, akılda tutulması gereken önemli bir ayrıntı var: toMetaMaskSmartAccount kullanılarak oluşturulan hesaplar counterfactual'dır. Bu, hesabın dağıtılana kadar zincirde var olmadığı anlamına gelir. account.address kullanarak hesap adresini almaya devam edebilirsiniz; ancak bir blok gezgininde aratırsanız henüz bulamazsınız.
Bununla birlikte, o adrese önceden token gönderebilirsiniz. Hesap dağıtıldıktan sonra bu tokenlar erişilebilir olacak ve o adrese bağlı bekleyen işlemler hemen gerçekleştirilebilecektir.
Davet Eden Delegator Hesabını Dağıtın
İlerlemek için, blok zinciriyle etkileşime girebilmesi amacıyla counterfactual hesabı dağıtmamız gerekiyor. Çoğu dapp'te bu adım genellikle kullanıcının giriş yaptığı veya kayıt olduğu sırada katılım sürecinde tetiklenir.
Dağıtımı gerçekleştirmek için Pimlico'nun altyapısını kullanacağız. Pimlico kontrol panelinize gidin, bir API anahtarı oluşturun ve sağlanan URL'yi kopyalayın. Bu URL hem bundler (kullanıcı işlemlerini iletmek için) hem de paymaster (gas ücretlerini sponsorlamak için) olarak işlev görür.
Counterfactual hesabı dağıtmaya yönelik özel bir fonksiyon olmadığından, dağıtımı bundler'a sahte bir kullanıcı işlemi göndererek tetikleyeceğiz. Bu işlem başarılı olmayacak, ancak Pimlico onu yürütmeye çalışmadan önce hesabı dağıtacak; bu da ihtiyacımız olan tek şey.
Pimlico bundler ve paymaster'ı yapılandırın
Pimlico'nun bundler, paymaster ve kullanıcı işlemi araçlarıyla etkileşim kurmak için @pimlico/permissionless paketini yükleyin:
npm install permissionless Ardından Pimlico bundler ve paymaster'ı yapılandırıp Gator hesabını dağıtmaya geçeceğiz:
…
import {
createBundlerClient,
createPaymasterClient,
} from "viem/account-abstraction";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { zeroAddress } from "viem";
const pimlicoURL = process.env.PIMLICO_URL;
const paymasterClient = createPaymasterClient({
transport: http(pimlicoURL),
});
const bundlerClient = createBundlerClient({
transport: http(pimlicoURL),
paymaster: paymasterClient,
});
const pimlicoClient = createPimlicoClient({
transport: http(pimlicoURL),
});;
const { fast: gasPrice } = await pimlicoClient.getUserOperationGasPrice();
const isDeployed = await account.isDeployed();
if (!isDeployed) {
const hash = await bundlerClient.sendUserOperation({
account,
calls: [{ to: zeroAddress }],
...gasPrice,
});
const { receipt } = await bundlerClient.waitForUserOperationReceipt({ hash });
}
Yukarıdaki kod parçacığında önce Pimlico paymaster ve bundler istemcilerini, ardından Pimlico istemcisinin kendisini başlatıyoruz. Daha sonra Gator hesabının zaten dağıtılıp dağıtılmadığını kontrol ediyoruz. Dağıtılmamışsa, henüz dağıtılmamış hesabı gönderen olarak kullanarak sahte bir kullanıcı işlemi gönderiyoruz.
Pimlico bundler, kullanıcı işlemi paketlenip mempool'a gönderildikten sonra bir işlem hash'i döndürür. Bu hash'i, işlemin zincirde madenciliğinin yapılmasını ve onaylanmasını beklemek için kullanıyoruz.
Bu sürecin arka planda nasıl işlediğine dair daha fazla ayrıntı için Pimlico belgelerine başvurun.
Son adım, yeni dağıtılan delegator hesabınızı biraz test ETH ile fonlamaktır. Bunu MetaMask Sepolia faucet'ını ziyaret ederek yapabilirsiniz.
Davet Oluşturun
Davet eden için fonlanmış bir delegator hesabı hazır olduğuna göre, sıradaki adım açık bir delegasyon oluşturmak ve imzalamaktır. Bu delegasyon, davet edilenin davet edenin hesabından belirli miktarda test ETH talep etmesine olanak tanıyacaktır.
Delegasyonlar, aşağıdaki yapıya sahip DelegationStruct türü kullanılarak tanımlanır:
export type DelegationStruct = {
delegate: Hex;
delegator: Hex;
authority: Hex;
caveats: CaveatStruct[];
salt: bigint;
signature: Hex;
};
Bu durumda, bir delegasyon zincirindeki ilk halka olan kök delegasyonu oluşturacağız. Yeniden delegasyon zinciri oluşturmak için kök delegasyonun üzerine inşa etmek mümkün olsa da bu eğitim yalnızca kök delegasyonun kendisine odaklanacaktır.
Kök delegasyon oluşturmanın iki yolu vardır:
Delegate'in adresini önceden bilmiyorsanız (bu eğitimde olduğu gibi), herhangi bir hesap tarafından kullanılabilen açık bir delegasyon oluşturabilirsiniz.
Delegate'in adresini biliyorsanız, delegate alanını açıkça o adrese ayarlayarak kapalı bir delegasyon oluşturabilirsiniz.
Esneklik ve sadelik adına açık delegasyon yaklaşımını tercih edeceğiz:
...
import {
...
createOpenDelegation,
} from "@metamask/delegator-core-viem";
const caveatBuilder = createCaveatBuilder(account.environment);
const caveats = caveatBuilder
.addCaveat("limitedCalls", 1)
.addCaveat("nativeTokenTransferAmount", BigInt(1))
.addCaveat("timestamp", 0, Math.floor(Date.now() / 1000) + 86_400);
const openDelegation = createOpenDelegation({
from: account.address,
caveats,
});
Bu adımda, delegasyonun nasıl kullanılabileceğini kontrol etmek için üç caveat ekledik:
Yürütme limiti: limitedCalls caveat'ını kullanarak, delegate'in davet eden adına gerçekleştirebileceği işlem sayısını kısıtlıyoruz.
Token transfer üst sınırı: nativeTokenTransferAmount caveat'ı, davet edenin hesabından aktarılabilecek toplam yerel token miktarını sınırlar.
Son kullanma tarihi: Delegasyonun belirli bir süreden sonra geçersiz olmasını sağlamak için zaman damgası tabanlı bir caveat ekliyoruz. Bu durumda davet 24 saat içinde sona erer.
Ekleyebileceğiniz pek çok başka caveat bulunsa da kullanım senaryomuz için bunlar yeterlidir. Desteklenen caveat'ların tam listesini belgelerden bulabilir ve gerekirse özel caveat'lar da tanımlayabilirsiniz.
Delegasyon tanımlandıktan sonra son adım, onu imzalamak ve saklamaktır. Bir delegasyonun geçerli olabilmesi için delegator tarafından imzalanması gerekir. MetaMask delegator hesabı, bu amaçla kullanacağımız bir signDelegation yöntemi sunar.
Depolama açısından istediğiniz herhangi bir yöntemi seçebilirsiniz. Bu eğitimde işleri basit tutacak ve localStorage kullanacağız. JavaScript'in yerleşik JSON.stringify'ının BigInt değerlerini desteklememesi nedeniyle delegasyonu serileştirmek için superjson kütüphanesinden stringify kullandığımıza dikkat edin.
npm install superjson...
import { stringify } from "superjson";
const signature = await account.signDelegation({
delegation: openDelegation,
});
localStorage.setItem(
"DELEGATION",
stringify({ delegation: openDelegation, signature })
);
Daveti Kullanın
Şimdi davet edilenin perspektifine geçelim; yani delegasyonu kullanacak ve davet edenin hesabından kendi hesabına token aktarma gibi bir işlem gerçekleştirecek kullanıcının bakış açısına.
Adım 1: Delegasyonu depolamadan alın
İmzalı delegasyonu yerel olarak sakladığımızdan, davet edilen onu doğrudan localStorage'dan alabilir:
import { parse } from "superjson";
const storedData = localStorage.getItem("DELEGATION");
if (!storedData) {
throw new Error("Delegation not found in storage");
}
const { delegation, signature } = parse(storedData);
Adım 2: Bir yürütme oluşturun
Bir yürütme, delegate'in delegator'ın hesabında gerçekleştirmesine izin verilen belirli eylemi temsil eder. Delegasyonda tanımlanan tüm caveat'lara uyması gerekir.
Bir yürütme nesnesi genellikle şu şekilde görünür:
{
target,
value,
callData
}
İşleri basitleştirmek için SDK'dan createExecution yardımcı yöntemini kullanabilirsiniz:
import { createExecution } from "@codefi/delegator-core-viem";
const execution = createExecution(
target,
value,
callData
);
Bizim durumumuzda, bir akıllı sözleşme çağırmak yerine delegator'dan delegate'e ETH aktardığımızdan, yürütme şu şekilde görünür:
const execution = createExecution(
delegateAccount.address,
amount
);
Adım 3: Yürütmeyi bir UserOperation ile gönderin
Hesap soyutlama kavramlarına yeni iseniz bu kısım biraz karmaşık gelebilir, o yüzden adım adım açıklayalım.
Üst düzeyde hedefimiz, delegasyonu kullanmak için gerekli tüm işlem verilerini kapsayan bir UserOperation göndermektir. Soyut görünse de bu, sonuçta zincirde yürütülen standart bir Ethereum işlemiyle sonuçlanacaktır.
Bu işlem, Delegator Akıllı Sözleşme Hesabı üzerinde redeemDelegations adlı bir fonksiyonu çağıracaktır. Bu fonksiyon şunlardan sorumludur:
Delegasyonu doğrulamak ve caveat'larını geçerli kılmak
Yürütmenin nasıl işleneceğini belirlemek (kullanım modu). Daha fazlası burada
İstenen eylemi gerçekleştirmek (bu durumda token aktarımı)
Basitçe ifade etmek gerekirse, redeemDelegations delegasyonun hâlâ geçerli olup olmadığını kontrol eder ve her şey yolundaysa amaçlanan eylemi gerçekleştirir.
Bu işlemi göndermek için bundler istemcisi tarafından sağlanan sendUserOperation yöntemini kullanacağız. Bu yöntem, UserOperation'ı oluşturma ve imzalama karmaşıklığını soyutlar:
import { encodeFunctionData } from "viem";
import {
encodeExecutionCalldatas,
encodePermissionContexts,
SINGLE_DEFAULT_MODE,
} from "@codefi/delegator-core-viem";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { createBundlerClient } from "viem/account-abstraction";
const bundlerClient = createBundlerClient({
transport: http(process.env.NEXT_PUBLIC_BUNDLER_URL),
chain: lineaSepolia,
});
const pimlicoClient = createPimlicoClient({
transport: http(process.env.NEXT_PUBLIC_BUNDLER_URL),
chain: lineaSepolia,
});
const { fast } = await pimlicoClient.getUserOperationGasPrice();
const redeemData = encodeFunctionData({
abi: delegateAccount.abi,
functionName: "redeemDelegations",
args: [
encodePermissionContexts([delegation]),
[SINGLE_DEFAULT_MODE],
encodeExecutionCalldatas([[execution]]),
],
});
const hash = await bundlerClient.sendUserOperation({
account: delegateAccount,
calls: [
{
to: delegateAccount.address,
value: 0n,
data: redeemData,
},
],
paymaster: true,
...fast,
});
await bundlerClient.waitForUserOperationReceipt({
hash,
});