Ruka hadi kwenye maudhui makuu

Kufadhili ada za gesi: Jinsi ya kulipia gharama za miamala kwa watumiaji wako

bila gesi
Solidity
eip-712
miamala-meta
Wastani
Ori Pomerantz
27 Februari 2026
10 soma ndani ya dakika

Utangulizi

Ikiwa tunataka Ethereum ihudumie watu bilioni moja zaidi (opens in a new tab), tunahitaji kuondoa msuguano na kuifanya iwe rahisi kutumia iwezekanavyo. Chanzo kimoja cha msuguano huu ni hitaji la ETH kulipia ada za gesi.

Ikiwa una programu tumizi iliyogatuliwa (dapp) inayotengeneza pesa kutoka kwa watumiaji, inaweza kuwa na maana kuruhusu watumiaji kuwasilisha miamala kupitia seva yako na wewe mwenyewe kulipia ada za muamala. Kwa sababu watumiaji bado wanatia saini ujumbe wa uidhinishaji wa EIP-712 (opens in a new tab) kwenye mikoba yao, wanahifadhi dhamana za uadilifu za Ethereum. Upatikanaji unategemea seva inayosambaza miamala, kwa hivyo ina kikomo zaidi. Hata hivyo, unaweza kuweka mambo ili watumiaji waweze pia kufikia mkataba mahiri moja kwa moja (ikiwa watapata ETH), na kuruhusu wengine kuanzisha seva zao wenyewe ikiwa wanataka kufadhili miamala.

Mbinu katika mafunzo haya inafanya kazi tu unapodhibiti mkataba mahiri. Kuna mbinu zingine, ikiwa ni pamoja na udhanifu wa akaunti (opens in a new tab) zinazokuruhusu kufadhili miamala kwa mikataba mahiri mingine, ambayo natumai kuishughulikia katika mafunzo yajayo.

Kumbuka: Huu sio msimbo wa kiwango cha uzalishaji. Una hatari ya kushambuliwa kwa kiasi kikubwa na unakosa vipengele muhimu. Jifunze zaidi katika sehemu ya udhaifu ya mwongozo huu.

Mahitaji ya awali

Ili kuelewa mafunzo haya unahitaji kuwa tayari unafahamu:

Programu ya mfano

Programu ya mfano hapa ni tofauti ya mkataba wa Greeter wa Hardhat. Unaweza kuiona kwenye GitHub (opens in a new tab). Mkataba mahiri tayari umesambazwa kwenye Sepolia (opens in a new tab), kwenye anwani 0xC87506C66c7896366b9E988FE0aA5B6dDE77CFfA (opens in a new tab).

Ili kuiona ikifanya kazi, fuata hatua hizi.

  1. Nakili hazina (clone repository) na usakinishe programu muhimu.

    1git clone https://github.com/qbzzt/260301-gasless.git
    2cd 260301-gasless/server
    3npm install
  2. Hariri .env ili kuweka PRIVATE_KEY kwenye mkoba ulio na ETH kwenye Sepolia. Ikiwa unahitaji Sepolia ETH, tumia bomba. Kimsingi, ufunguo wa siri huu unapaswa kuwa tofauti na ule ulio nao kwenye mkoba wa kivinjari chako.

  3. Anzisha seva.

    1npm run dev
  4. Vinjari kwenye programu kwenye URL http://localhost:5173 (opens in a new tab).

  5. Bofya Connect with Injected ili kuunganisha kwenye mkoba. Idhinisha kwenye mkoba, na idhinisha mabadiliko kwenda Sepolia ikiwa ni lazima.

  6. Andika salamu mpya na ubofye Update greeting via sponsor.

  7. Tia saini ujumbe.

  8. Subiri takriban sekunde 12 (muda wa kitalu kwenye Sepolia). Wakati unasubiri unaweza kuangalia URL kwenye kiweko cha seva ili kuona muamala.

  9. Angalia kwamba salamu imebadilika, na kwamba thamani ya anwani iliyosasishwa mwisho sasa ni anwani ya mkoba wa kivinjari chako.

Ili kuelewa jinsi hii inavyofanya kazi, tunahitaji kuangalia jinsi ujumbe unavyoundwa katika kiolesura cha mtumiaji, jinsi unavyosambazwa na seva, na jinsi mkataba mahiri unavyouchakata.

Kiolesura cha mtumiaji

Kiolesura cha mtumiaji kinategemea WAGMI (opens in a new tab); unaweza kusoma kuihusu katika mafunzo haya.

Hivi ndivyo tunavyotia saini ujumbe:

1const signGreeting = useCallback(

Hook ya React useCallback (opens in a new tab) inaturuhusu kuboresha utendaji kwa kutumia tena kipengele kile kile wakati kijenzi kinachorwa upya.

1 async (greeting) => {
2 if (!account) throw new Error("Wallet not connected")

Ikiwa hakuna akaunti, onyesha hitilafu. Hili halipaswi kutokea kamwe kwa sababu kitufe cha UI kinachoanzisha mchakato unaoita signGreeting kimezimwa katika hali hiyo. Hata hivyo, watengenezaji programu wa baadaye wanaweza kuondoa ulinzi huo, kwa hivyo ni wazo zuri kuangalia sharti hili hapa pia.

1 const domain = {
2 name: "Greeter",
3 version: "1",
4 chainId,
5 verifyingContract: contractAddr,
6 }

Vigezo vya kitenganishi cha kikoa (opens in a new tab). Thamani hii ni ya kudumu, kwa hivyo katika utekelezaji ulioboreshwa zaidi, tunaweza kuihesabu mara moja badala ya kuihesabu upya kila wakati kipengele kinapoitwa.

  • name ni jina linalosomeka na mtumiaji, kama vile jina la dapp ambalo tunatengenezea sahihi.
  • version ni toleo. Matoleo tofauti hayaendani.
  • chainId ni mnyororo tunaotumia, kama inavyotolewa na WAGMI (opens in a new tab).
  • verifyingContract ni anwani ya mkataba itakayothibitisha sahihi hii. Hatutaki sahihi hiyo hiyo itumike kwa mikataba mingi, endapo kuna mikataba kadhaa ya Greeter na tunataka iwe na salamu tofauti.
1
2 const types = {
3 GreetingRequest: [
4 { name: "greeting", type: "string" },
5 ],
6 }

Aina ya data tunayotia saini. Hapa, tuna kigezo kimoja, greeting, lakini mifumo ya maisha halisi kwa kawaida huwa na zaidi.

1 const message = { greeting }

Ujumbe halisi tunaotaka kutia saini na kutuma. greeting ni jina la uwanja na jina la kigezo kinachoujaza.

1 const signature = await signTypedDataAsync({
2 domain,
3 types,
4 primaryType: "GreetingRequest",
5 message,
6 })

Pata sahihi haswa. Kipengele hiki hakilandanishwi (asynchronous) kwa sababu watumiaji huchukua muda mrefu (kwa mtazamo wa kompyuta) kutia saini data.

1 const r = `0x${signature.slice(2, 66)}`
2 const s = `0x${signature.slice(66, 130)}`
3 const v = parseInt(signature.slice(130, 132), 16)
4
5 return {
6 req: { greeting },
7 v,
8 r,
9 s,
10 }
11 },

Kipengele kinarudisha thamani moja ya heksadesimali. Hapa tunaigawanya katika nyanja.

1 [account, chainId, contractAddr, signTypedDataAsync],
2)

Ikiwa yoyote ya vigezo hivi itabadilika, unda mfano mpya wa kipengele. Vigezo vya account na chainId vinaweza kubadilishwa na mtumiaji kwenye mkoba. contractAddr ni kipengele cha Kitambulisho cha mnyororo. signTypedDataAsync haipaswi kubadilika, lakini tunaiingiza kutoka kwa hook (opens in a new tab), kwa hivyo hatuwezi kuwa na uhakika, na ni bora kuiongeza hapa.

Sasa kwa kuwa salamu mpya imetiwa saini, tunahitaji kuituma kwa seva.

1 const sponsoredGreeting = async () => {
2 try {

Kipengele hiki kinachukua sahihi na kuituma kwa seva.

1 const signedMessage = await signGreeting(newGreeting)
2 const response = await fetch("/server/sponsor", {

Tuma kwa njia ya /server/sponsor katika seva tuliyotoka.

1 method: "POST",
2 headers: { "Content-Type": "application/json" },
3 body: JSON.stringify(signedMessage),
4 })

Tumia POST kutuma taarifa iliyosimbwa kwa JSON.

1 const data = await response.json()
2 console.log("Server response:", data)
3 } catch (err) {
4 console.error("Error:", err)
5 }
6 }

Toa majibu. Kwenye mfumo wa uzalishaji tungeonyesha pia majibu kwa mtumiaji.

Seva

Ninapenda kutumia Vite (opens in a new tab) kama upande wangu wa mbele (front-end). Inatumikia maktaba za React kiotomatiki na kusasisha kivinjari wakati msimbo wa upande wa mbele unabadilika. Hata hivyo, Vite haijumuishi zana za upande wa nyuma (backend).

Suluhisho liko katika index.js (opens in a new tab).

1 app.post("/server/sponsor", async (req, res) => {
2 ...
3 })
4
5 // Acha Vite ishughulikie kila kitu kingine
6 const vite = await createViteServer({
7 server: { middlewareMode: true }
8 })
9
10 app.use(vite.middlewares)

Kwanza tunasajili kidhibiti cha maombi tunayoshughulikia wenyewe (POST hadi /server/sponsor). Kisha tunaunda na kutumia seva ya Vite kushughulikia URL zingine zote.

1 app.post("/server/sponsor", async (req, res) => {
2 try {
3 const signed = req.body
4
5 const txHash = await sepoliaClient.writeContract({
6 address: greeterAddr,
7 abi: greeterABI,
8 functionName: 'sponsoredSetGreeting',
9 args: [signed.req, signed.v, signed.r, signed.s],
10 })
11 } ...
12 })

Huu ni wito wa kawaida tu wa mnyororo wa vitalu wa viem (opens in a new tab).

Mkataba mahiri

Hatimaye, Greeter.sol (opens in a new tab) inahitaji kuthibitisha sahihi.

1 constructor(string memory _greeting) {
2 greeting = _greeting;
3
4 DOMAIN_SEPARATOR = keccak256(
5 abi.encode(
6 keccak256(
7 "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
8 ),
9 keccak256(bytes("Greeter")),
10 keccak256(bytes("1")),
11 block.chainid,
12 address(this)
13 )
14 );
15 }

Konstrukta inaunda kitenganishi cha kikoa (opens in a new tab), sawa na msimbo wa kiolesura cha mtumiaji hapo juu. Utekelezaji wa mnyororo wa vitalu ni ghali zaidi, kwa hivyo tunaihesabu mara moja tu.

1 struct GreetingRequest {
2 string greeting;
3 }

Huu ndio muundo unaotiwa saini. Hapa tuna uwanja mmoja tu.

1 bytes32 private constant GREETING_TYPEHASH =
2 keccak256("GreetingRequest(string greeting)");

Hiki ni kitambulisho cha muundo (opens in a new tab). Kinahesabiwa kila wakati katika kiolesura cha mtumiaji.

1 function sponsoredSetGreeting(
2 GreetingRequest calldata req,
3 uint8 v,
4 bytes32 r,
5 bytes32 s
6 ) external {

Kipengele hiki kinapokea ombi lililotiwa saini na kusasisha salamu.

1 // Kokotoa muhtasari wa EIP-712
2 bytes32 digest = keccak256(
3 abi.encodePacked(
4 "\x19\x01",
5 DOMAIN_SEPARATOR,
6 keccak256(
7 abi.encode(
8 GREETING_TYPEHASH,
9 keccak256(bytes(req.greeting))
10 )
11 )
12 )
13 );

Unda muhtasari kwa mujibu wa EIP 712 (opens in a new tab).

1 // Rejesha mtia sahihi
2 address signer = ecrecover(digest, v, r, s);
3 require(signer != address(0), "Invalid signature");

Tumia ecrecover (opens in a new tab) kupata anwani ya mtia saini. Kumbuka kwamba sahihi mbaya bado inaweza kusababisha anwani halali, lakini ya kubahatisha tu.

1 // Tekeleza salamu kana kwamba mtia sahihi ameiita
2 greeting = req.greeting;
3 emit SetGreeting(signer, req.greeting);
4 }

Sasisha salamu.

Udhaifu

Huu sio msimbo wa kiwango cha uzalishaji. Una hatari ya kushambuliwa kwa kiasi kikubwa na unakosa vipengele muhimu. Hapa kuna baadhi, pamoja na jinsi ya kuzitatua.

Ili kuona baadhi ya mashambulizi haya, bofya vitufe vilivyo chini ya kichwa cha Attacks na uone kinachotokea. Kwa kitufe cha Invalid signature, angalia kiweko cha seva ili kuona majibu ya muamala.

Kunyimwa huduma kwenye seva

Shambulio rahisi zaidi ni shambulio la kunyimwa huduma (opens in a new tab) kwenye seva. Seva hupokea maombi kutoka popote kwenye Mtandao na kulingana na maombi hayo hutuma miamala. Hakuna chochote kinachozuia mshambuliaji kutoa rundo la sahihi, halali au batili. Kila moja itasababisha muamala. Hatimaye seva itaishiwa na ETH ya kulipia gesi.

Suluhisho moja la tatizo hili ni kupunguza kiwango hadi muamala mmoja kwa kila kitalu. Ikiwa madhumuni ni kuonyesha salamu kwa akaunti zinazomilikiwa na watu wa nje, haijalishi salamu ni nini katikati ya kitalu hata hivyo.

Suluhisho jingine ni kufuatilia anwani na kuruhusu tu sahihi kutoka kwa wateja halali.

Sahihi za salamu zisizo sahihi

Unapobofya Signature for wrong greeting, unawasilisha sahihi halali kwa anwani maalum (0xaA92c5d426430D4769c9E878C1333BDe3d689b3e) na salamu (Hello). Lakini inaiwasilisha na salamu tofauti. Hii inachanganya ecrecover, ambayo inabadilisha salamu lakini ina anwani isiyo sahihi.

Ili kutatua tatizo hili, ongeza anwani kwenye muundo uliotiwa saini (opens in a new tab). Kwa njia hii, anwani ya kubahatisha ya ecrecover haitalingana na anwani iliyo kwenye sahihi, na mkataba mahiri utakataa ujumbe.

Mashambulizi ya kurudia

Unapobofya Replay attack, unawasilisha sahihi ile ile ya "Mimi ni 0xaA92c5d426430D4769c9E878C1333BDe3d689b3e, na ningependa salamu iwe Hello", lakini kwa salamu sahihi. Kama matokeo, mkataba mahiri unaamini kwamba anwani (ambayo sio yako) ilibadilisha salamu kurudi kwenye Hello. Taarifa ya kufanya hivi inapatikana kwa umma katika taarifa ya muamala (opens in a new tab).

Ikiwa hili ni tatizo, suluhisho moja ni kuongeza nonsi (opens in a new tab). Kuwa na upangaji (opens in a new tab) kati ya anwani na nambari, na uongeze uwanja wa nonsi kwenye sahihi. Ikiwa uwanja wa nonsi unalingana na upangaji wa anwani, kubali sahihi na uongeze upangaji kwa wakati ujao. Ikiwa sivyo, kataa muamala.

Suluhisho jingine ni kuongeza muhuri wa muda kwenye data iliyotiwa saini na kukubali sahihi kama halali kwa sekunde chache tu baada ya muhuri huo wa muda. Hii ni rahisi na ya bei nafuu, lakini tunahatarisha mashambulizi ya kurudia ndani ya dirisha la muda, na kushindwa kwa miamala halali ikiwa dirisha la muda limepitwa.

Vipengele vingine vinavyokosekana

Kuna vipengele vya ziada ambavyo tungeongeza katika mpangilio wa uzalishaji.

Ufikiaji kutoka kwa seva zingine

Kwa sasa, tunaruhusu anwani yoyote kuwasilisha sponsorSetGreeting. Hili linaweza kuwa hasa kile tunachotaka, kwa maslahi ya ugatuzi. Au labda tunataka kuhakikisha kwamba miamala inayofadhiliwa inapitia seva yetu, katika hali ambayo tungeangalia msg.sender katika mkataba mahiri.

Vyovyote vile, huu unapaswa kuwa uamuzi wa muundo wa makusudi, sio tu matokeo ya kutofikiria kuhusu suala hilo.

Ushughulikiaji wa hitilafu

Mtumiaji anawasilisha salamu. Labda inasasishwa kwenye kitalu kinachofuata. Labda haisasishwi. Hitilafu hazionekani. Kwenye mfumo wa uzalishaji, mtumiaji anapaswa kuwa na uwezo wa kutofautisha kati ya kesi hizi:

  • Salamu mpya bado haijawasilishwa
  • Salamu mpya imewasilishwa, na iko katika mchakato
  • Salamu mpya imekataliwa

Hitimisho

Kufikia hapa, unapaswa kuwa na uwezo wa kuunda uzoefu bila gesi kwa watumiaji wako wa dapp, kwa gharama ya kiasi fulani cha uwekaji kati (centralization).

Hata hivyo, hii inafanya kazi tu na mikataba mahiri inayoauni ERC-712. Ili kuhamisha tokeni ya ERC-20, kwa mfano, ni lazima muamala utiwe saini na mmiliki badala ya ujumbe tu. Suluhisho ni udhanifu wa akaunti (ERC-4337) (opens in a new tab). Natumai kuandika mafunzo ya baadaye kuihusu.

Tazama hapa kwa kazi zangu zaidi (opens in a new tab).

Ukurasa ulisasishwa mwisho: 3 Machi 2026

Je, mafunzo haya yalikuwa ya msaada?