मुख्य सामग्री पर जाएं
यह tutorial आपको सिखाएगा कि Tenderly Web3 Actions का उपयोग करके अपने smart contract के लिए serverless backend कैसे बनाएँ। Web3 Actions आपको अपने smart contract द्वारा शुरू किए गए on-chain या off-chain events के जवाब में custom code चलाने की अनुमति देते हैं। यह चित्रित करने के लिए कि Web3 Actions कैसे काम करते हैं, हम एक सरल Tic-Tac-Toe game बनाते हैं और इसे test network पर deploy करते हैं। smart contract game state बनाए रखता है जबकि Web3 Actions game में परिवर्तनों की निगरानी करते हैं। जब भी smart contract से कोई विशिष्ट event fire होता है, Tenderly आपका custom code NodeJS project के रूप में execute करेगा। game के परिणाम और game board को हर बार खिलाड़ी द्वारा move करने पर या game समाप्त होने पर console में print किया जाएगा।
इस project के लिए source code खोजने के लिए इस Github repo को देखें। repo को clone करने और इसके साथ खेलने, या code साथ लिखने के लिए स्वतंत्र महसूस करें।

Web3 Actions को समझना

Smart contracts आपको तब custom code execute करने की अनुमति देते हैं जब कोई event trigger होता है, कोई function invoke होता है, या शायद समय-समय पर। Tenderly आपको Web3 Actions के साथ इस प्रक्रिया को सुव्यवस्थित करने में मदद करता है। आप अपनी Web3 Actions को एक single Javascript file में लिख सकते हैं या उन्हें NodeJS project के रूप में deploy कर सकते हैं। Web3 Actions आपको वे सभी actions करने की अनुमति भी देते हैं जो आप आम तौर पर NodeJS के साथ करते हैं। यह tutorial आपको दिखाएगा कि अपनी Web3 Actions को deploy करने के लिए Tenderly CLI का उपयोग कैसे करें और यह सुनिश्चित करें कि जब विशिष्ट शर्तें पूरी हों तो वे चलें।
Web3 Actions के बारे में अधिक जानें और Web3 Actions की Basics के साथ अपने projects में उनका उपयोग कैसे कर सकते हैं इसका अन्वेषण करें।

Project अवलोकन

इस project का उद्देश्य आपको यह दिखाना है कि एक smart contract कैसे deploy करें और कई Javascript functions कैसे लिखें जिन्हें Tenderly किसी event के होने पर invoke करेगा। आप सीखेंगे कि अपनी Web3 Actions को Tenderly की infrastructure पर deploy करने के लिए Tenderly CLI का उपयोग कैसे करें। यहाँ ऐसा करने का step-by-step अवलोकन है:
  1. smart contract को test network पर deploy करें
  2. Web3 Actions चलाने के लिए आवश्यक Javascript और config files सेट अप करने के लिए Tenderly CLI का उपयोग करें
  3. game events पर प्रतिक्रिया देने वाले functions develop करें
  4. उन functions को CLI का उपयोग करके Tenderly की infrastructure पर deploy करें
इस tutorial के लिए, आपको Tenderly Dashboard तक पहुँच की आवश्यकता होगी। यदि आपके पास account नहीं है, तो यहाँ मुफ्त में sign up करें (कोई cc भी आवश्यक नहीं)।

0: smart contract deploy करें

यदि आप साथ code कर रहे हैं, तो आप इस चरण को skip कर सकते हैं और उस contract का उपयोग कर सकते हैं जिसे हमने Tenderly में deploy और verify किया
हम एक pre-written smart contract का उपयोग करते हैं। GitHub पर smart contract का source code देखें। इस smart contract को इस तरह डिज़ाइन किया गया है कि जब भी game की state में कोई परिवर्तन होता है तो यह events emit करता है। हमारे सरल Tic-Tac-Toe game में चार संभावित states हो सकती हैं:
  • Game start
  • Player joins the game
  • Player makes a move
  • Game over

पूर्वापेक्षा

दो Accounts बनाएँ - किसी भी Tenderly द्वारा समर्थित network पर smart contract deploy करें। ऐसा करने का सबसे सुविधाजनक तरीका Remix और Metamask wallet plugin के साथ है। आपको सकारात्मक Ether balance वाले दो accounts की आवश्यकता है। ये accounts दो खिलाड़ियों का प्रतिनिधित्व करेंगे। इस tutorial के प्रयोजन के लिए हम Sepolia का उपयोग करते हैं। यदि आप इस tutorial के साथ चलने की योजना बनाते हैं, तो आप accounts में Ether जोड़ने के लिए Sepolia faucet का उपयोग कर सकते हैं। contract को Compile करें - smart contract को compile करने के लिए Remix IDE का उपयोग करें। एक नया contract file बनाएँ और यहाँ पाया गया code जोड़ें।
Compiling the contract in the Remix IDE
test network पर Deploy करें - अपने Metamask browser plugin में, सुनिश्चित करें कि Sepolia आपके पसंदीदा network के रूप में चयनित है। इसके बाद, Remix में “Deploy And Run Transactions” अनुभाग पर जाएँ और Injected Web3 का चयन करें। यह सुनिश्चित करने के बाद कि account Metamask में account का reference देता है, “Deploy” पर क्लिक करें।
Deploying the contract to a testnet
Copying the contract address
deployed contract पर अधिक जानकारी प्राप्त करने के लिए, “Deployed Contracts” अनुभाग देखें। पूरा contract address (0x133...) copy करने के लिए copy icon पर क्लिक करें और इसे कहीं store करें क्योंकि हमें अगले चरणों के लिए इसकी आवश्यकता होगी। अब contract को verify करने के लिए Tenderly Dashboard पर जाएँ। इससे हमारे लिए tutorial में बाद में contract के साथ interact करना संभव हो जाएगा।

1: Tenderly CLI के माध्यम से Web3 Actions सेट अप करें

इस tutorial के साथ जारी रखने के लिए, आपके पास अपनी machine पर Tenderly CLI स्थापित होना चाहिए। इसे सेट अप करने और access authenticate करने के तरीके के बारे में जानने के लिए इस गाइड का पालन करें। CLI स्थापित होने के साथ, एक नई directory बनाएँ और cd इसमें। tenderly actions init command चलाकर अपनी Web3 Actions को initialize करें:
$> cd tdly-actions
$> tenderly actions init
आपको अपने मौजूदा Tenderly projects में से एक का चयन करने के लिए कहा जाएगा। आपकी Web3 Actions इस project पर deploy की जाएंगी और आपकी directory structure में इस तरह दिखाई देनी चाहिए:
example
$> ls actions
  example.ts     # where we write Web3 actions code
  tsconfig.json
  package.json
Typescript default भाषा है लेकिन आप plain Javascript पर switch कर सकते हैं। init command execute करने से पहले, अपनी पसंदीदा भाषा के रूप में Javascript को इस तरह सेट करें: tenderly actions init --language javascript
package.json npm dependencies रखता है जो आपके Web3 Action को deploy करने पर उपलब्ध होंगी। tsconfig.json file में केवल actions directory के भीतर .ts files से संबंधित Typescript configuration है।

1.1. Tic-Tac-Toe Contract का ABI जोड़ें

code में गहराई से जाने से पहले, compiler द्वारा उत्पन्न ABI को actions directory में copy करें। हमारे उदाहरण में, यह tdly-actions directory है। Remix में, files/artifacts/TicTacToe.json पर जाएँ, file की contents copy/paste करें और उन्हें अपने project की TicTacToe.json file में paste करें।
आपकी Web3 Action code द्वारा उपयोग की जाने वाली और referenced सभी files को actions directory में रखा जाना चाहिए (जैसे TicTacToe.json)।

1.2. Typescript को JSON File को Module के रूप में Import करने के लिए Configure करें

default रूप से, Typescript आपको JSON files को modules के रूप में import करने की अनुमति नहीं देता है। आपको Typescript को कॉन्फ़िगर करने की आवश्यकता है ताकि TicTacToe.json file को module के रूप में import कर सकें और object के रूप में access कर सकें। अपनी tsconfig.json file पर जाएँ और compilerOptions entry के तहत निम्न दो configurations शामिल करें:
example
{
    ...
    "compilerOptions": {
	...
+        "resolveJsonModule": true,
+        "esModuleInterop": true
    },
   ...
}

2: New Game Event को Handle करने के लिए Function लिखें

एक function लिखें जो नया game शुरू होने पर execute होगा। आइए Typescript file का नाम कुछ अधिक वर्णनात्मक में बदलें:
example
mv example.ts ticTacToeActions.ts
Typescript की शक्ति का उपयोग करने के लिए, हमें पहले game state का प्रतिनिधित्व करने वाला एक type परिभाषित करने की आवश्यकता होगी। संक्षेप में, हम आपकी Web3 Action के storage में game state की एक replica store करेंगे। प्रत्येक field में उस खिलाड़ी का ID होता है जिसने वहाँ खेला। हम players object में खिलाड़ी के address को उनकी turn (पहली, दूसरी, आदि) से map करेंगे।

2.1. एक Action जोड़ें

example.ts
// ticTacToeActions.ts

export type Game = {
  players: { [address: string]: number };
  board: number[][];
};
इस बिंदु पर, हम वास्तविक Web3 Action को इस तरह परिभाषित करने के लिए तैयार हैं:
example.ts
export const createNewGame = (): Game => {
  return {
    players: {},
    board: [
      [0, 0, 0],
      [0, 0, 0],
      [0, 0, 0],
    ],
  };
};

// ticTacToeActions.ts continued
export const newGameAction: ActionFn = async (context: Context, event: Event) => {
  let txEvent = event as TransactionEvent;

  let iface = new ethers.utils.Interface(TicTacToe.abi);

  const result = iface.decodeEventLog('GameCreated', txEvent.logs[0].data, txEvent.logs[0].topics);

  const { gameId, playerNumber, player } = result;

  console.log('Game Created Event:', {
    gameId: gameId.toString(),
    playerNumber,
    player,
  });

  const game: Game = createNewGame();
  await context.storage.putJson(gameId.toString(), game);
};
यह Web3 Action smart contract में परिभाषित GameCreated event को handle करेगी। smart contract को देखकर, हम देख सकते हैं कि newGame function से केवल एक event fire हो रहा है, इसलिए हम पहली log entry में रुचि रखते हैं। हम TicTacToe.abi के आधार पर GameCreated event के txEvent.logs[0].data को decode करके ethers.js के साथ result प्राप्त कर सकते हैं। यहाँ हम इस विशेष game के साथ जुड़े ID तक पहुँच सकते हैं जब यह बनाया गया हो: result.gameId। हम इस विशेष game के लिए data को track करना चाहते हैं: खिलाड़ी और उनके द्वारा किए गए moves, एक बिल्कुल नए Game instance का उपयोग करते हुए। हम नए game का प्रतिनिधित्व करने वाले object को Storage में इस command के साथ save कर रहे हैं: context.storage.putJson(gameId, game) एक खाली board के साथ, हम भविष्य के परिवर्तनों को persist करने की नींव सेट कर रहे हैं जो अन्य actions द्वारा संभाले जाते हैं। आप Web3 Action के behaviour को verify करने के लिए automated tests भी लिख सकते हैं। कुछ tests repository में उपलब्ध हैं, लेकिन यह इस tutorial के दायरे से बाहर है।

2.2. New Game Action Invocation निर्दिष्ट करें

tenderly.yaml खोलें। specs अनुभाग में, newGame (arbitrary name) के लिए specs परिभाषित करें ताकि function newGameAction को invoke करें। आप इसे इस तरह कर सकते हैं newGameAction:newGameAction, पहले हम function वाली file का नाम परिभाषित करते हैं और फिर function name। इसके बाद, trigger निर्दिष्ट करें जिसे Tenderly action को invoke करने के लिए देखने वाला है। यह एक transaction trigger है जो block mine होने पर चलेगा। हम इसे network 3 के लिए कर रहे हैं जब निर्दिष्ट address पर contract से event NewGame emit होता है। TTT_CONTRACT_ADDRESS को अपने smart contract के वास्तविक address से बदलें। यदि आप contract को Sepolia के अलावा किसी अन्य network पर deploy करना चाहते हैं, तो network के ID को network के मान के रूप में निर्दिष्ट करें।
YOUR_USERNAME और YOUR_PROJECT_SLUG को अपने Tenderly username और अपने project के slug से बदलें। आप उन्हें Dashboard URL से copy कर सकते हैं: https://dashboard.tenderly.co/{YOUR_USERNAME}/{YOUR_PROJECT_SLUG}/transactions
example.yaml
account_id: ''
actions:
  YOUR_USERNAME/YOUR_PROJECT_SLUG:
    runtime: v1
    sources: actions
    specs:
      newGame:
        description: Respond to newGame event
        function: ticTacToeActions:newGameAction
        trigger:
          type: transaction
          transaction:
            status:
              - mined
            filters:
              - network: 3
                eventEmitted:
                  contract:
                    address: TTT_CONTRACT_ADDRESS
                  name: GameCreated
project_slug: ''

2.3. Tenderly पर अपने Tic-Tac-Toe Smart Contract को Verify करें

contract को deploy करने से पहले, आपको इसे अपने Tenderly Dashboard में verify करना होगा। यदि आप चाहें, तो आप contract को अपने browser के माध्यम से upload कर सकते हैं। एक बार upload होने के बाद, TicTacToe contract, आपने जिस network पर इसे deploy किया, और contract address का चयन करें। Add Contract पर क्लिक करें और नीचे दी गई तस्वीर में दिखाए अनुसार compiler options भरें:
Verifying and adding the contract to Tenderly
Adding the contract compiler information

2.4. Web3 Action को Tenderly पर Deploy करें

अपनी Web3 Action deploy करने के लिए, Tenderly CLI का उपयोग करके deploy command execute करें:
example
tenderly actions deploy
output इस तरह दिखना चाहिए:
The Web3 Action execution output
अपनी Web3 Action के deployment के बारे में विवरण देखने के लिए, Tenderly Dashboard देखें। यदि deployment सफल था, तो आपको कुछ इस तरह दिखना चाहिए:
Opening the Web3 Action deployment information

2.5. एक New Game में शामिल होने का प्रयास करें 🎉

यह सत्यापित करने के लिए कि सब कुछ ठीक से काम कर रहा है, Remix पर वापस जाएँ और एक नया game या यहाँ तक कि कई games बनाएँ। TicTacToe contract खोलें और newGame दबाएँ। Metamask को आपको Web3 Action के execution की पुष्टि करने के लिए prompt करना चाहिए। एक बार transaction chain पर submit हो जाने पर, Remix को इसी तरह का output produce करना चाहिए:
Creating a new game in Remix
इसके बाद, अपने Tenderly Dashboard पर वापस जाएँ और अपना transaction देखने के लिए Transactions अनुभाग खोलें। Actions अनुभाग में, आप देखेंगे कि “Latest Execution” column बदल गया है। execution history देखने के लिए, अपनी Web3 Action खोलें और Execution History tab पर क्लिक करें:
Opening the Execution history tab for your Web3 Action
execution history आपको निम्न data प्रदान करेगी:
The execution history data
ऊपरी pane में, आपको chain से आ रहे payload के बारे में जानकारी मिलेगी। निचले pane में logged game number के बारे में विवरण है। हमारे मामले में, यह 0xb है, जिसका अर्थ है कि यह इस contract के लिए शुरू किया गया 11वाँ game है।

2.6. अपनी Web 3 Actions के Storage की जाँच करें

Storage की contents देखने के लिए “Go To Storage” बटन पर क्लिक करें। शुरू किए गए प्रत्येक game को इस key-value map में अपना खुद का storage slot मिलेगा। जब आप हमारे अभी बनाए गए game को ID 11 के साथ खोलते हैं, तो आपको सभी fields में zeros दिखाई देंगे, जिसका अर्थ है कि किसी भी खिलाड़ी ने अभी तक game नहीं खेला है।
Opening the Storage of your Web3 Action

3: नए खिलाड़ी Game में शामिल होने को Handle करने के लिए Web3 Action जोड़ें

game में शामिल होने वाले खिलाड़ी को खिलाड़ी के address और उनके move (1 या 2) को register करके process किया जाना चाहिए। boilerplate code के अलावा जो हमें event data retrieve करने में मदद करता है, playerJoinAction.ts file में वह code भी होता है जो हमें अनुमति देता है:
  • game के ID (storage.getJson) का उपयोग करके storage से वर्तमान game state पढ़ें।
  • खिलाड़ी के address को retrieve करें और उनकी turn store करें: game.players[player] = playerNumber
  • storage.putJson का उपयोग करके Web3 Action के storage में updated game object save करें।
example.ts
// playerJoinAction.ts

export const playerJoinedAction: ActionFn = async (context: Context, event: Event) => {
  let txEvent = event as TransactionEvent;
  let iface = new ethers.utils.Interface(TicTacToe.abi);
  const result = iface.decodeEventLog(
    'PlayerJoinedGame',
    txEvent.logs[0].data,
    txEvent.logs[0].topics,
  );

  const gameId = result.gameId.toString();
  const playerAddress = result.player.toLowerCase() as string;
  const playerNumber = result.playerNumber;

  console.log('Player joined event:', {
    gameId,
    playerAddress,
    playerNumber,
  });

  const game: Game = (await context.storage.getJson(gameId)) as Game;
  game.players[playerAddress] = playerNumber;

  await context.storage.putJson(result.gameId.toString(), game);
};

3.1. PlayerJoinedGame Action Invocation निर्दिष्ट करें

हमें Web3 Action को invoke करने के लिए आवश्यक specifications शामिल करने के लिए tenderly.yaml file से specs का विस्तार करने की भी आवश्यकता है।
example.yaml
playerJoined:
  description: Respond to player joining game
  function: ticTacToeActions:playerJoinedAction
  trigger:
    type: transaction
    transaction:
      status:
        - mined
      filters:
        - network: 3
          eventEmitted:
            contract:
              address: TTT_CONTRACT_ADDRESS
            name: PlayerJoinedGame

3.2. action को Tenderly पर deploy करें

deploy command चलाकर अपनी Web3 Action deploy करें। यह command पहले deploy की गई Web3 Actions को भी redeploy करेगा।
example.js
tenderly action deploy

3.3. एक New Game में शामिल होने का प्रयास करें

newGame input field में logs से game number जोड़कर Remix पर जाकर एक नए game में शामिल हों। transaction submit करने के लिए, joinGame बटन पर क्लिक करें:
Joining a new game in Remix
एक बार transaction mine हो जाने पर, execution के logs में playerNumber: 1 दिखाई देगा।
The execution log
game खेलने के लिए, Metamask में अपने दूसरे account पर switch करें और फिर से joinGame पर क्लिक करें। transaction के पूरा होने पर, आप Execution History में एक समान log output देखेंगे।

3.4. अपनी Web 3 Actions के Storage की जाँच करें

Opening the execution of your Web3 Action
इन executions में से किसी को भी खोलें और “Go To Storage” बटन पर क्लिक करें। हमारे खिलाड़ियों द्वारा शामिल हुआ game ID 11 है, और यह किसी भी move के होने से पहले की state है। Tic-Tac-Toe board में केवल zeros हैं और Ethereum address से खिलाड़ी की turn तक map मौजूद है।
Opening the Storage key value

3.5. जब खिलाड़ी Move करे उसे Handle करने के लिए एक Action जोड़ें

Ethers का उपयोग करके, हम game ID प्राप्त करते हैं और storage से game instance load करते हैं। इसके बाद, row result.boardRow और column result.boardCol में field को खिलाड़ी के input game.players[player] से update करें। function processNewGameState, board को console में log करेगा, लेकिन आप move होने पर एक tweet भी भेज सकते हैं, chain पर एक नया transaction trigger कर सकते हैं, या इसे किसी अन्य तरीके से उपयोग कर सकते हैं।
example.ts
// ticTacToeActions.ts
//...
export const playerMadeMoveAction: ActionFn = async (context: Context, event: Event) => {
  let txEvent = event as TransactionEvent;
  let iface = new ethers.utils.Interface(TicTacToe.abi);
  const result = iface.decodeEventLog(
    'PlayerMadeMove',
    txEvent.logs[0].data,
    txEvent.logs[0].topics,
  );

  const gameId = result.gameId.toString();
  const game = (await context.storage.getJson(gameId)) as Game;
  const player = result.player.toLowerCase() as string;
  const { boardRow, boardCol } = result;

  console.log("Player's move event log:", {
    gameId,
    player,
    boardRow: boardRow.toString(),
    boardCol: boardCol.toString(),
  });

  console.log(
    `Move: gameId ${gameId}, game ${JSON.stringify(
      game,
    )}, boradRow ${boardRow}, boardCol ${boardCol}, player ${player}`,
  );

  game.board[boardRow][boardCol] = game.players[player];
  console.log('MV', JSON.stringify(game));
  await context.storage.putJson(gameId, game);

  processNewGameState(game);
};

const processNewGameState = (game: Game) => {
  let board = '\n';
  game.board.forEach(row => {
    row.forEach(field => {
      if (field == 1) {
        board += '❎ ';
        return;
      }

      if (field == 2) {
        board += '🅾️ ';
        return;
      }

      board += '💜 ';
    });

    board += '\n';
  });

  console.log(board);
};
इस action के लिए संबंधित spec है:
example.yaml
playerMadeMove:
  description: Respond to player making a move event
  function: ticTacToeActions:playerMadeMoveAction
  trigger:
    type: transaction
    transaction:
      status:
        - mined
      filters:
        - network: 3
          eventEmitted:
            contract:
              address: TTT_CONTRACT_ADDRESS
            name: PlayerMadeMove

3.6. Game खेलने के लिए Move करें

move करने के लिए, Metamask का उपयोग करके game में शामिल होने वाले पहले खिलाड़ी पर switch करें। इसके बाद, आपको प्राप्त game number, साथ ही board (0, 1) पर row और column दर्ज करें।
Making a move in Remix
जब action execute हो जाता है, तो output इस तरह दिखना चाहिए:
The Web3 Action execution output

चरण 4. Game समाप्त होने पर Handle करने के लिए Web3 Action जोड़ें

नीचे आपको GameOver event के लिए code मिलेगा। GameOver event तब fire होता है जब कोई खिलाड़ी ऐसा move करता है जो game जीतता है या जब board भर जाता है। यह event PlayerMadeMove event के बाद fire होता है। txEvent.logs list में, GameOver event दूसरा element है। GameOver event प्राप्त करने का एक आसान तरीका इसे txEvent.logs[1] के माध्यम से access करना है। हालाँकि, हम एक अधिक मजबूत समाधान implement करेंगे जो fire होने वाले events के order और संख्या पर निर्भर नहीं करता है। पहले, आपको iface.getEventTopics के माध्यम से ethers का उपयोग करके gameOverTopic प्राप्त करने की आवश्यकता है। यह आपको संबंधित hexadecimal value देगा। इसके बाद, आपको txEvent.logs में log entry खोजनी होगी, जिसकी topics list में gameOverTopic है। यह हमारा GameOver event log है जिसे हम Ethers का उपयोग करके decode कर सकते हैं।
example.js
export const gameOverAction: ActionFn = async (context: Context, event: Event) => {
	let txEvent = event as TransactionEvent;
	let iface = new ethers.utils.Interface(TicTacToe.abi);

	const gameOverTopic = iface.getEventTopic("GameOver");
	const gameOverLog = txEvent.logs.find(log => log.topics.find(topic => topic == gameOverTopic) !== undefined);

	if (gameOverLog == undefined) {
		// impossible
		throw Error("GameOver log not found in event's logs");
	}

	const result = iface.decodeEventLog("GameOver", gameOverLog.data, gameOverLog.topics);

	console.log(result);

	const gameId = result.gameId.toString();
	const winner = result.winner as number;

	const winnerMessage = getWinnerMessage(winner);
	if (winnerMessage !== false) {
		console.info(`🎉 Winner of the game ${gameId} is ${winnerMessage}`);
	} else {
		console.error("🤔 weird winner code");
	}
}

4.1. GameOver Action Invocation निर्दिष्ट करें

अंत में, हमें tenderly.yaml में specs का विस्तार करके GameOver action को invoke करने के लिए specification जोड़ने की आवश्यकता है:
example.yaml
gameOver:
  description: Respond to game over
  function: ticTacToeActions:gameOverAction
  trigger:
    type: transaction
    transaction:
      status:
        - mined
      filters:
        - network: 3
          eventEmitted:
            contract:
              address: TTT_CONTRACT_ADDRESS
            name: GameOver

4.2. Game खेलें

तब तक खेलते रहें जब तक कि एक खिलाड़ी game जीत नहीं जाता या board पूरी तरह से भर नहीं जाता। game के अंत में, Execution History इस तरह दिखनी चाहिए:
The execution history at the end of the game