{"id":497,"date":"2023-04-05T10:42:00","date_gmt":"2023-04-05T08:42:00","guid":{"rendered":"https:\/\/ackeeblockchain.com\/blog\/?p=497"},"modified":"2025-02-17T18:00:49","modified_gmt":"2025-02-17T16:00:49","slug":"how-to-develop-secure-cross-chain-apps-on-axelar","status":"publish","type":"post","link":"https:\/\/ackee.xyz\/blog\/how-to-develop-secure-cross-chain-apps-on-axelar\/","title":{"rendered":"How to develop secure cross-chain apps on Axelar"},"content":{"rendered":"<p class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">Communication between multiple chains has become more and more important. Although the major security responsibility and trust are on the selected bridge protocol, bad cross-chain app implementation on top of these bridges can lead to catastrophic consequences. This guide covers the main threats, mistakes, and best practices for developing secure cross-chain apps on the <a class=\"kp kq eb\" href=\"https:\/\/medium.com\/u\/faa4dcd1226d?source=post_page-----5bdd20f574fd--------------------------------\" target=\"_blank\" rel=\"noopener\">Axelar protocol.<\/a><\/p>\n<h2>Axelar Architecture<\/h2>\n<p>We will focus mainly on the application layer and gateway, but it is good to have basic knowledge of the whole architecture.<\/p>\n<p>Axelar has deployed\u00a0<code class=\"codehl\">AxelarGateway<\/code>\u00a0contracts on every chain, which acts as a middle layer between applications and Axelar network and provides communication between these layers. On the source chain, the gateway sends messages (as events) to validators and eventually burns\/locks tokens. On the destination chain, the gateway is responsible for received message validations and eventually minting\/unlocking tokens. Axelar network consensus is achieved by a set of\u00a0<a class=\"ae ly\" href=\"https:\/\/docs.axelar.dev\/validator\/setup\/overview\" target=\"_blank\" rel=\"noopener\">validators<\/a>\u00a0using delegated proof-of-stake.<\/p>\n<figure class=\"ma mb mc md gg jk fu fv paragraph-image\">\n<div class=\"jl jm di jn bf jo\" tabindex=\"0\" role=\"button\">\n<div class=\"fu fv lz\"><picture><source srcset=\"https:\/\/miro.medium.com\/v2\/resize:fit:640\/format:webp\/1*uXs7FuAMdvfAhcS30CgzIA.png 640w, https:\/\/miro.medium.com\/v2\/resize:fit:720\/format:webp\/1*uXs7FuAMdvfAhcS30CgzIA.png 720w, https:\/\/miro.medium.com\/v2\/resize:fit:750\/format:webp\/1*uXs7FuAMdvfAhcS30CgzIA.png 750w, https:\/\/miro.medium.com\/v2\/resize:fit:786\/format:webp\/1*uXs7FuAMdvfAhcS30CgzIA.png 786w, https:\/\/miro.medium.com\/v2\/resize:fit:828\/format:webp\/1*uXs7FuAMdvfAhcS30CgzIA.png 828w, https:\/\/miro.medium.com\/v2\/resize:fit:1100\/format:webp\/1*uXs7FuAMdvfAhcS30CgzIA.png 1100w, https:\/\/miro.medium.com\/v2\/resize:fit:1400\/format:webp\/1*uXs7FuAMdvfAhcS30CgzIA.png 1400w\" type=\"image\/webp\" sizes=\"(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px\" \/><source srcset=\"https:\/\/miro.medium.com\/v2\/resize:fit:640\/1*uXs7FuAMdvfAhcS30CgzIA.png 640w, https:\/\/miro.medium.com\/v2\/resize:fit:720\/1*uXs7FuAMdvfAhcS30CgzIA.png 720w, https:\/\/miro.medium.com\/v2\/resize:fit:750\/1*uXs7FuAMdvfAhcS30CgzIA.png 750w, https:\/\/miro.medium.com\/v2\/resize:fit:786\/1*uXs7FuAMdvfAhcS30CgzIA.png 786w, https:\/\/miro.medium.com\/v2\/resize:fit:828\/1*uXs7FuAMdvfAhcS30CgzIA.png 828w, https:\/\/miro.medium.com\/v2\/resize:fit:1100\/1*uXs7FuAMdvfAhcS30CgzIA.png 1100w, https:\/\/miro.medium.com\/v2\/resize:fit:1400\/1*uXs7FuAMdvfAhcS30CgzIA.png 1400w\" sizes=\"(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px\" data-testid=\"og\" \/><img loading=\"lazy\" decoding=\"async\" class=\"bf jp jq c aligncenter\" role=\"presentation\" src=\"https:\/\/miro.medium.com\/v2\/resize:fit:1400\/1*uXs7FuAMdvfAhcS30CgzIA.png\" alt=\"\" width=\"673\" height=\"504\" \/><\/picture><\/div>\n<\/div>\n<\/figure>\n<h2 id=\"b828\" class=\"kr ks ij bd kt ku kv kw kx ky kz la lb lc ld le lf lg lh li lj lk ll lm ln lo bi\">Axelar General Message Passing protocol<\/h2>\n<p id=\"ee4d\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Axelar GMP is the fundamental feature of Axelar network, which provides developers an easy way to send tokens and call functions across supported chains. What does the message flow look like?<\/p>\n<ol class=\"\">\n<li id=\"ab54\" class=\"me mf ij jt b ju jv jy jz kc mg kg mh kk mi ko mj mk ml mm bi\" data-selectable-paragraph=\"\">Application on the source chain calls one of these gateway functions:<br \/>\n&#8211;\u00a0<code class=\"codehl\">sendToken<\/code> to send only tokens,<br \/>\n&#8211;\u00a0<code class=\"codehl\">callContract<\/code> to execute a <code class=\"codehl\">payload<\/code>\u00a0on the destination chain,<br \/>\n&#8211;\u00a0<code class=\"codehl\">callContractWithToken<\/code>\u00a0to send tokens and execute a call.<\/li>\n<li id=\"5309\" class=\"me mf ij jt b ju mn jy mo kc mp kg mq kk mr ko mj mk ml mm bi\" data-selectable-paragraph=\"\">Gateway emits an event with all parameters.<\/li>\n<li id=\"7ecd\" class=\"me mf ij jt b ju mn jy mo kc mp kg mq kk mr ko mj mk ml mm bi\" data-selectable-paragraph=\"\">Validators validate the message and notify the destination gateway.<\/li>\n<li id=\"1da5\" class=\"me mf ij jt b ju mn jy mo kc mp kg mq kk mr ko mj mk ml mm bi\" data-selectable-paragraph=\"\">The execute function is called in the app on the destination chain.<\/li>\n<\/ol>\n<figure class=\"ma mb mc md gg jk fu fv paragraph-image\">\n<div class=\"jl jm di jn bf jo\" tabindex=\"0\" role=\"button\">\n<div class=\"fu fv ms\"><picture><source srcset=\"https:\/\/miro.medium.com\/v2\/resize:fit:640\/format:webp\/1*VhLBH0Tv5x9253KzfPtIYg.png 640w, https:\/\/miro.medium.com\/v2\/resize:fit:720\/format:webp\/1*VhLBH0Tv5x9253KzfPtIYg.png 720w, https:\/\/miro.medium.com\/v2\/resize:fit:750\/format:webp\/1*VhLBH0Tv5x9253KzfPtIYg.png 750w, https:\/\/miro.medium.com\/v2\/resize:fit:786\/format:webp\/1*VhLBH0Tv5x9253KzfPtIYg.png 786w, https:\/\/miro.medium.com\/v2\/resize:fit:828\/format:webp\/1*VhLBH0Tv5x9253KzfPtIYg.png 828w, https:\/\/miro.medium.com\/v2\/resize:fit:1100\/format:webp\/1*VhLBH0Tv5x9253KzfPtIYg.png 1100w, https:\/\/miro.medium.com\/v2\/resize:fit:1400\/format:webp\/1*VhLBH0Tv5x9253KzfPtIYg.png 1400w\" type=\"image\/webp\" sizes=\"(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px\" \/><source srcset=\"https:\/\/miro.medium.com\/v2\/resize:fit:640\/1*VhLBH0Tv5x9253KzfPtIYg.png 640w, https:\/\/miro.medium.com\/v2\/resize:fit:720\/1*VhLBH0Tv5x9253KzfPtIYg.png 720w, https:\/\/miro.medium.com\/v2\/resize:fit:750\/1*VhLBH0Tv5x9253KzfPtIYg.png 750w, https:\/\/miro.medium.com\/v2\/resize:fit:786\/1*VhLBH0Tv5x9253KzfPtIYg.png 786w, https:\/\/miro.medium.com\/v2\/resize:fit:828\/1*VhLBH0Tv5x9253KzfPtIYg.png 828w, https:\/\/miro.medium.com\/v2\/resize:fit:1100\/1*VhLBH0Tv5x9253KzfPtIYg.png 1100w, https:\/\/miro.medium.com\/v2\/resize:fit:1400\/1*VhLBH0Tv5x9253KzfPtIYg.png 1400w\" sizes=\"(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px\" data-testid=\"og\" \/><img loading=\"lazy\" decoding=\"async\" class=\"bf jp jq c aligncenter\" role=\"presentation\" src=\"https:\/\/miro.medium.com\/v2\/resize:fit:1400\/1*VhLBH0Tv5x9253KzfPtIYg.png\" alt=\"\" width=\"662\" height=\"543\" \/><\/picture><\/div>\n<\/div>\n<\/figure>\n<h2 id=\"cebe\" class=\"kr ks ij bd kt ku kv kw kx ky kz la lb lc ld le lf lg lh li lj lk ll lm ln lo bi\">GMP Express<\/h2>\n<p id=\"c8d8\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Privileged clients (protocols) can be allowed by Axelar to use the Express version of the messaging protocol. A standard message can take several minutes for the Axelar network to fully approve the message and pass it to the final destination. The express message will still go through the Axelar network, but GMP Express Service will lend any sent tokens to the destination address while approval is happening. Once the complete approval is done, tokens are paid back to the GMP Express service. This process can speed up the cross-chain transfer more than ten times. The crucial part of GMP Express is complete trust between the privileged protocol and Axelar. The privileged protocol MUST implement the logic for token returns when a standard message is received. Otherwise, the privileged protocol will receive 2x more tokens.<\/p>\n<h2 id=\"2058\" class=\"kr ks ij bd kt ku kv kw kx ky kz la lb lc ld le lf lg lh li lj lk ll lm ln lo bi\">How to start<\/h2>\n<p id=\"0988\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Developing apps on Axelar is simple and straightforward, using Axelar GMP SDK. Just add\u00a0<a class=\"ae ly\" href=\"https:\/\/github.com\/axelarnetwork\/axelar-gmp-sdk-solidity\" target=\"_blank\" rel=\"noopener\">@axelar-network\/axelar-gmp-sdk-solidity<\/a>\u00a0dependency to your Solidity project, and you are good to go.<\/p>\n<h2 id=\"6195\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">Application is AxelarExecutable<\/h2>\n<pre class=\"ma mb mc md gg nh lx ni bn nj nk bi\"><span id=\"3e52\" class=\"nl ks ij lx b be nm nn l no np\" data-selectable-paragraph=\"\">@axelar-network\/axelar-gmp-sdk-solidity\/contracts\/executables\/<span class=\"hljs-title.class\">AxelarExecutable<\/span>.<span class=\"hljs-property\">sol<\/span><\/span><\/pre>\n<p id=\"b3cd\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">Every application built on Axelar Network has to inherit from\u00a0<a href=\"https:\/\/github.com\/axelarnetwork\/axelar-gmp-sdk-solidity\/blob\/main\/contracts\/executable\/AxelarExecutable.sol\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">AxelarExecutable<\/code><\/a> contract, which implements <a href=\"https:\/\/github.com\/axelarnetwork\/axelar-gmp-sdk-solidity\/blob\/main\/contracts\/interfaces\/IAxelarExecutable.sol\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">IAxelarExecutable<\/code><\/a> interface and handles received messages.<\/p>\n<pre><code class=\"language-solidity\">contract YourApp is AxelarExecutable {\n\n\nIAxelarGasService public immutable gasService;\n\n\nconstructor(\naddress gatewayAddress_,\naddress gasServiceAddress_,\n) AxelarExecutable(gatewayAddress_) {\nif (gatewayAddress_ == address(0)\n|| gasServiceAddress_ == address(0)\n) revert ZeroAddress();\ngasService = IAxelarGasService(gasServiceAddress_);\n}\n}<\/code><\/pre>\n<h3 class=\"ma mb mc md gg nh lx ni bn nj nk bi\"><span id=\"b55e\" class=\"nl ks ij lx b be nm nn l no np\" data-selectable-paragraph=\"\"><\/span>Receiving messages<\/h3>\n<p id=\"8deb\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">The following internal functions are intended to be overridden in the application to handle received messages or\/and tokens. This is also a critical part of application security. Pay maximum attention to avoid any reentrancies, logic bugs, or miscalculations.<\/p>\n<pre><code class=\"language-solidity\">function _execute(\n       string calldata sourceChain,\n       string calldata sourceAddress,\n       bytes calldata payload\n   ) internal virtual<\/code><\/pre>\n<p id=\"5aac\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">If the source chain calls\u00a0<code class=\"codehl\">sendToken<\/code> or <code class=\"codehl\">callContractWithToken<\/code> function on the gateway, then in the app on the destination chain\u00a0<code class=\"codehl\">_executeWithToken<\/code> function gets called. Minting\/unlocking tokens is handled automatically by\u00a0<code class=\"codehl\">gateway.validateContractCallAndMint<\/code>\u00a0function.<\/p>\n<pre><code class=\"language-solidity\">function _executeWithToken(\n       string calldata sourceChain,\n       string calldata sourceAddress,\n       bytes calldata payload,\n       string calldata tokenSymbol,\n       uint256 amount\n   ) internal virtual<\/code><\/pre>\n<h3>Sending messages<\/h3>\n<p id=\"3f5c\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">For sending messages, there is\u00a0<a href=\"https:\/\/github.com\/axelarnetwork\/axelar-cgp-solidity\/blob\/main\/contracts\/interfaces\/IAxelarGateway.sol\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">IAxelarGateway<\/code><\/a> the interface of Axelar gateway, which is used to send tokens and messages.<\/p>\n<pre class=\"ma mb mc md gg nh lx ni bn nj nk bi\"><span id=\"ed9d\" class=\"nl ks ij lx b be nm nn l no np\" data-selectable-paragraph=\"\">@axelar-network\/axelar-gmp-sdk-solidity\/contracts\/interfaces\/<span class=\"hljs-title.class\">IAxelarGateway<\/span>.<span class=\"hljs-property\">sol<\/span><\/span><\/pre>\n<p class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">Function\u00a0<code class=\"codehl\">sendToken<\/code> provides multichain token transfer. The destination chain is identified by string id, the destination address is also a string. Depending on the type of token (internal\/external), it gets burned\/locked on the source chain and minted\/unlocked on the destination chain.<\/p>\n<pre><code class=\"language-solidity\">function sendToken(\n       string calldata destinationChain,\n       string calldata destinationAddress,\n       string calldata symbol,\n       uint256 amount\n   )<\/code><\/pre>\n<p class=\"ma mb mc md gg nh lx ni bn nj nk bi\"><span id=\"e8b6\" class=\"nl ks ij lx b be nm nn l no np\" data-selectable-paragraph=\"\"><\/span>For sending the cross-chain message (payload), use <code class=\"codehl\">callContract<\/code> function.<\/p>\n<pre><code class=\"language-solidity\">function callContract(\n       string calldata destinationChain,\n       string calldata destinationContractAddress,\n       bytes calldata payload\n   )<\/code><\/pre>\n<p class=\"ma mb mc md gg nh lx ni bn nj nk bi\"><span id=\"2605\" class=\"nl ks ij lx b be nm nn l no np\" data-selectable-paragraph=\"\"><\/span>And if you need to send both (message and tokens), there is a <code class=\"codehl\">callContract&lt;code class=&quot;er lu lv lw lx b&quot;&gt;WithToken<\/code><\/code> function.<\/p>\n<pre><code class=\"language-solidity\">function callContractWithToken(\n       string calldata destinationChain,\n       string calldata destinationContractAddress,\n       bytes calldata payload,\n       string calldata symbol,\n       uint256 amount\n   )<\/code><\/pre>\n<h3><span id=\"5881\" class=\"nl ks ij lx b be nm nn l no np\" data-selectable-paragraph=\"\"><\/span>How to pay gas on Axelar?<\/h3>\n<p id=\"f009\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">There are two ways to pay gas for cross-chain calls. The preferred one is <a href=\"https:\/\/github.com\/axelarnetwork\/axelar-cgp-solidity\/blob\/main\/contracts\/gas-service\/AxelarGasService.sol\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">AxelarGasService<\/code><\/a>\u00a0 contract, which works as a prepay on the source chain for transactions on the destination chain.<\/p>\n<ol class=\"\">\n<li id=\"4ecd\" class=\"me mf ij jt b ju jv jy jz kc mg kg mh kk mi ko mj mk ml mm bi\" data-selectable-paragraph=\"\">Using\u00a0<a class=\"ae ly\" href=\"https:\/\/docs.axelar.dev\/dev\/axelarjs-sdk\/intro\" target=\"_blank\" rel=\"noopener\">AxelarJS SDK<\/a> call <a href=\"https:\/\/docs.axelar.dev\/dev\/axelarjs-sdk\/axelar-query-api#estimategasfee\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">estimateGasFee<\/code><\/a> function on the destination chain to calculate the gas fee.<\/li>\n<li id=\"ac0f\" class=\"me mf ij jt b ju mn jy mo kc mp kg mq kk mr ko mj mk ml mm bi\" data-selectable-paragraph=\"\">Pass the calculated gas fee as\u00a0<code class=\"codehl\">msg.value<\/code> from the smart contract to <a href=\"https:\/\/github.com\/axelarnetwork\/axelar-cgp-solidity\/blob\/main\/contracts\/gas-service\/AxelarGasService.sol\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">AxelarGasService<\/code><\/a> contract on the source chain using one of these functions:<br \/>\n&#8211; <code class=\"codehl\">payGasForContractCall<\/code><br \/>\n&#8211; <code class=\"codehl\">payGasForContractCallWithToke<\/code><br \/>\n&#8211; <code class=\"codehl\">payNativeGasForContractCall<\/code><br \/>\n&#8211; <code class=\"codehl\">payNativeGasForContractCallWithToken<\/code>The user\/app on the destination chain can also pay gas fees manually. For detailed information about paying gas fees, see this article in Axelar <a class=\"ae ly\" href=\"https:\/\/docs.axelar.dev\/dev\/general-message-passing\/gas-services\/pay-gas\" target=\"_blank\" rel=\"noopener\">documentation<\/a>.<\/li>\n<\/ol>\n<h2>Example of an Axelar cross-chain app<\/h2>\n<p id=\"5005\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">We\u2019ve prepared a sample cross-chain app for sending messages and tokens through the Axelar network\u00a0<a class=\"ae ly\" href=\"https:\/\/github.com\/Ackee-Blockchain\/axelar-project-example\" target=\"_blank\" rel=\"noopener\">here on GitHub<\/a>. The project also includes\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/woke\/docs\/latest\/testing-framework\/overview\/\" target=\"_blank\" rel=\"noopener\">Wake<\/a>\u00a0tests.<\/p>\n<h3 id=\"9d88\" class=\"kr ks ij bd kt ku kv kw kx ky kz la lb lc ld le lf lg lh li lj lk ll lm ln lo bi\">Upgradability<\/h3>\n<p id=\"46c7\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">For upgradable contracts, it\u2019s recommended to inherit from Axelar\u2019s <code class=\"codehl\">Upgradable<\/code> contract, which implements <code class=\"codehl\">IUpgradable<\/code> interface.<\/p>\n<pre class=\"ma mb mc md gg nh lx ni bn nj nk bi\"><span id=\"9e01\" class=\"nl ks ij lx b be nm nn l no np\" data-selectable-paragraph=\"\">@axelar-network\/axelar-gmp-sdk-solidity\/contracts\/upgradables\/<span class=\"hljs-title.class\">Upgradable<\/span>.<span class=\"hljs-property\">sol<\/span><\/span><\/pre>\n<p id=\"9fad\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">Then define a unique <code class=\"codehl\">contractId<\/code>\u00a0constant, which needs to stay the same across the contract versions. The<code class=\"codehl\">contractId<\/code>\u00a0is a hash of app\/component name e.g. <code class=\"codehl\">keccak256(&quot;your-app&quot;)<\/code>. During every upgrade, the<code class=\"codehl\">Upgradable<\/code> implementation logic checks if the <code class=\"codehl\">contractId<\/code> of the new implementation matches th<code class=\"codehl\">contractId<\/code> of the Proxy. If not, the upgrade fails.<\/p>\n<p id=\"97a7\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">We implemented a\u00a0<a class=\"ae ly\" href=\"https:\/\/github.com\/Ackee-Blockchain\/woke\/blob\/main\/woke\/analysis\/detectors\/axelar\/proxy_contract_id.py\" target=\"_blank\" rel=\"noopener\">detector<\/a>\u00a0for\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/woke\/docs\/latest\/detectors\/\" target=\"_blank\" rel=\"noopener\">Wake static analyzer<\/a>, which checks<code class=\"codehl\">contractId<\/code>\u00a0constant. It warns if a proxy for implementation is missing or if more than one proxy contract with the same<code class=\"codehl\">contractId<\/code>\u00a0exists.<\/p>\n<h3 id=\"d042\" class=\"kr ks ij bd kt ku kv kw kx ky kz la lb lc ld le lf lg lh li lj lk ll lm ln lo bi\">Things to be aware of<\/h3>\n<p id=\"4410\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Besides the awareness of common vulnerabilities of Solidity\/EVM, cross-chain app development brings more topics to be cautious about. Let\u2019s dive deeper into the six most important ones.<\/p>\n<h3 id=\"2a8a\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">Inherit from AxelarExecutable<\/h3>\n<p id=\"2feb\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\"><code class=\"codehl\">AxelarExecutable<\/code> contract contains important checks against the Axelar Gateway to validate contract calls. Bypassing these checks (e.g. implementing <code class=\"codehl\">IAxelarExecutable<\/code> interface directly) could lead to critical vulnerabilities. Since the execute function is not protected by any modifier, everyone would be able to call it with any payload and execute any function on the destination chain.<\/p>\n<p id=\"4cbe\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">So, ensure that your execute function validates the calls using this gateway function <code class=\"codehl\">&lt;code class=&quot;er lu lv lw lx b&quot;&gt;validateContractCall<\/code><\/code>\u00a0, which checks that Axelar validators confirmed the message\u2019s source and payload are valid.<\/p>\n<pre><code class=\"language-solidity\">if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash))\n            revert NotApprovedByGateway();<\/code><\/pre>\n<p class=\"ma mb mc md gg nh lx ni bn nj nk bi\"><span id=\"b5c0\" class=\"nl ks ij lx b be nm nn l no np\" data-selectable-paragraph=\"\"><\/span>A similar approach applies to <code class=\"codehl\">executeWithToken<\/code> function, but using <code class=\"codehl\">validateContractCallAndMint<\/code>.<\/p>\n<pre><code class=\"language-solidity\"> if (!gateway.validateContractCallAndMint(\n                commandId,\n                sourceChain,\n                sourceAddress,\n                payloadHash,\n                tokenSymbol,\n                amount\n            )\n        ) revert NotApprovedByGateway();<\/code><\/pre>\n<h3 id=\"a079\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">Axelar components\u2019 addresses<\/h3>\n<p id=\"d57b\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Axelar gateway is a fundamental part of the protocol security, so it\u2019s very important to double-check the gateway address passed to the app during the deployment. A malicious gateway would be able to manipulate message checks. This is also important from the user\u2019s perspective. Always check that the application uses the\u00a0<a class=\"ae ly\" href=\"https:\/\/docs.axelar.dev\/dev\/reference\/mainnet-contract-addresses\" target=\"_blank\" rel=\"noopener\">official Axelar gateway and Gas Service addresses<\/a>.<\/p>\n<p id=\"e49b\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">Also, the application should not contain a gateway address setter. This would decrease the trust in the application. If the owner can control the gateway address, he also would be able to manipulate cross-chain messages in many ways.<\/p>\n<h3 id=\"f385\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">Destination chain and address validations<\/h3>\n<p id=\"bf96\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Before sending the message to another chain, do data validations of\u00a0<code class=\"codehl\">destinationChain<\/code>\u00a0and <code class=\"codehl\">destinationAddress<\/code>. Sending tokens to a non-existent chain or address means a loss of funds.<\/p>\n<h3>Validate the source address on the destination chain<\/h3>\n<p id=\"e56e\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">It is important to avoid receiving messages from any source address. For this purpose, deploying the app to the same address on multiple chains is a good practice. Axelar provides for this purpose utils\u00a0<a href=\"https:\/\/github.com\/axelarnetwork\/axelar-gmp-sdk-solidity\/blob\/main\/contracts\/deploy\/ConstAddressDeployer.sol\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">ConstAddressDeployer<\/code><\/a> and <code class=\"codehl\">Create3Deployer<\/code>. Then, you can simply validate the source address in your <code class=\"codehl\">_execute<\/code> and <code class=\"codehl\">_executeWithToken<\/code> functions using this condition.<\/p>\n<pre><code class=\"language-solidity\">if (sourceAddress.toAddress() != address(this)) revert InvalidSourceAddress()\n<\/code><\/pre>\n<h3 id=\"59f1\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">Express GMP data validations<\/h3>\n<p id=\"982e\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">If you decide to use <a href=\"https:\/\/github.com\/axelarnetwork\/axelar-gmp-sdk-solidity\/blob\/main\/contracts\/express\/ExpressExecutable.sol\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">ExpressExecutable<\/code><\/a> contract, be extra careful of the receiving messages data validations. Functions <code class=\"codehl\">execute<\/code> and <code class=\"codehl\">executeWithToken<\/code> are external and aren\u2019t protected by any modifier, so anyone can call them and potentially exploit the contract in many ways using malicious payload. Message validations using the Axelar gateway are also missing in the <a href=\"https:\/\/github.com\/axelarnetwork\/axelar-gmp-sdk-solidity\/blob\/main\/contracts\/express\/ExpressExecutable.sol\" target=\"_blank\" rel=\"noopener\"><code class=\"codehl\">ExpressExecutable<\/code><\/a>. Keep this in mind and implement robust data validations here.<\/p>\n<h3 id=\"bc02\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">NFT doubling<\/h3>\n<p id=\"7553\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">When implementing NFT cross-chain transfers, ensure the user\u2019s NFT is properly locked in the contract and the user cannot own it on both chains simultaneously.<\/p>\n<h3 id=\"1fd1\" class=\"kr ks ij bd kt ku kv kw kx ky kz la lb lc ld le lf lg lh li lj lk ll lm ln lo bi\">Static analysis, unit testing, and fuzzing<\/h3>\n<p id=\"9af1\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Contract development doesn\u2019t end with programming itself. Quality assurance has to be your top priority when protocols operate with users\u2019 funds or other valuables. There steps that development teams can and should perform internally before assigning a\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/blog\/how-to-prepare-for-a-smart-contract-audit-2\/\" target=\"_blank\" rel=\"noopener\">security audit<\/a>.<\/p>\n<h3 id=\"9631\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">Static analysis<\/h3>\n<p id=\"693e\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Detecting possible vulnerabilities during the development using classic static analysis tools like Slither, MythX, Mythrill\u2026 can be helpful and also painful because of many false positive findings. Our\u00a0<a href=\"https:\/\/getwake.io\/\" target=\"_blank\" rel=\"noopener\">Wake static analyzer<\/a>\u00a0goes deeper into this rabbit hole. Our target is to minimize false positive detections and achieve high precision while we accept a lower recall. Check out\u00a0<a class=\"ae ly\" href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=AckeeBlockchain.tools-for-solidity\" target=\"_blank\" rel=\"noopener\">Tools For Solidity<\/a>\u00a0extension that shows the static analysis results directly in VSCode.<\/p>\n<h2 id=\"89c1\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">Unit testing Axelar<\/h2>\n<p id=\"60c2\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">It is very important to test the application properly. Unit tests are useful for testing all use cases, and high test coverage is essential for basic security.<\/p>\n<p id=\"f452\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">Since apps on Axelar operate on multiple chains, it brings additional complexity to the system, and cross-chain testing definitely should be part of a development pipeline. We recommend our easy-to-use testing framework Woke, for this purpose. It provides a complete set of tools for\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/woke\/docs\/latest\/testing-framework\/cross-chain-testing\/\" target=\"_blank\" rel=\"noopener\">cross-chain testing<\/a>\u00a0and even\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/woke\/docs\/latest\/testing-framework\/fuzzing\/\" target=\"_blank\" rel=\"noopener\">fuzzing<\/a>. See the\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/woke\/docs\/latest\/\" target=\"_blank\" rel=\"noopener\">documentation<\/a>\u00a0for more information. Also, don\u2019t miss the article\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/blog\/testing-axelar-contracts-using-open-source-tools\/\" target=\"_blank\" rel=\"noopener\">Testing Axelar contracts using open-source tools<\/a>.<\/p>\n<p id=\"29bb\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">Unit and fuzz\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/woke\/docs\/latest\/testing-framework\/coverage-analysis\/\" target=\"_blank\" rel=\"noopener\">test coverage<\/a>\u00a0is also calculated by\u00a0<a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/woke\/docs\/2.0.0\/testing-framework\/overview\/\" target=\"_blank\" rel=\"noopener\">Wake<\/a>\u00a0and is displayed in the\u00a0<a class=\"ae ly\" href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=AckeeBlockchain.tools-for-solidity\" target=\"_blank\" rel=\"noopener\">Tools For Solidity<\/a>\u00a0extension as well. In the case of fuzzing, the coverage is calculated realtime.<\/p>\n<figure class=\"ma mb mc md gg jk fu fv paragraph-image\">\n<div class=\"jl jm di jn bf jo\" tabindex=\"0\" role=\"button\">\n<div class=\"fu fv nq\"><picture><source srcset=\"https:\/\/miro.medium.com\/v2\/resize:fit:640\/0*fO1smDcj6cn9XFOc 640w, https:\/\/miro.medium.com\/v2\/resize:fit:720\/0*fO1smDcj6cn9XFOc 720w, https:\/\/miro.medium.com\/v2\/resize:fit:750\/0*fO1smDcj6cn9XFOc 750w, https:\/\/miro.medium.com\/v2\/resize:fit:786\/0*fO1smDcj6cn9XFOc 786w, https:\/\/miro.medium.com\/v2\/resize:fit:828\/0*fO1smDcj6cn9XFOc 828w, https:\/\/miro.medium.com\/v2\/resize:fit:1100\/0*fO1smDcj6cn9XFOc 1100w, https:\/\/miro.medium.com\/v2\/resize:fit:1400\/0*fO1smDcj6cn9XFOc 1400w\" type=\"image\/webp\" sizes=\"(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px\" \/><source srcset=\"https:\/\/miro.medium.com\/v2\/resize:fit:640\/0*fO1smDcj6cn9XFOc 640w, https:\/\/miro.medium.com\/v2\/resize:fit:720\/0*fO1smDcj6cn9XFOc 720w, https:\/\/miro.medium.com\/v2\/resize:fit:750\/0*fO1smDcj6cn9XFOc 750w, https:\/\/miro.medium.com\/v2\/resize:fit:786\/0*fO1smDcj6cn9XFOc 786w, https:\/\/miro.medium.com\/v2\/resize:fit:828\/0*fO1smDcj6cn9XFOc 828w, https:\/\/miro.medium.com\/v2\/resize:fit:1100\/0*fO1smDcj6cn9XFOc 1100w, https:\/\/miro.medium.com\/v2\/resize:fit:1400\/0*fO1smDcj6cn9XFOc 1400w\" sizes=\"(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px\" data-testid=\"og\" \/><img loading=\"lazy\" decoding=\"async\" class=\"bf jp jq c aligncenter\" role=\"presentation\" src=\"https:\/\/miro.medium.com\/v2\/resize:fit:1400\/0*fO1smDcj6cn9XFOc\" alt=\"\" width=\"700\" height=\"465\" \/><\/picture><\/div>\n<\/div>\n<\/figure>\n<h2 id=\"5a0d\" class=\"mt ks ij bd kt mu mv mw kx mx my mz lb kc na nb lf kg nc nd lj kk ne nf ln ng bi\">Fuzzing Axelar<\/h2>\n<p id=\"fb4d\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Fuzzing is a technique for testing software that involves providing invalid, unexpected, or random data as inputs to a computer program. Learn more about <a href=\"https:\/\/ackee.xyz\">Ackee Blockchain<\/a>&#8216;s fuzzing tools and methods <a href=\"https:\/\/ackee.xyz\/blog\/introducing-manually-guided-fuzzing-a-new-approach-in-smart-contract-testing\/\">here<\/a>.<\/p>\n<p id=\"f7bb\" class=\"pw-post-body-paragraph jr js ij jt b ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko ic bi\" data-selectable-paragraph=\"\">Unit tests mostly cover the system\u2019s intended behavior and strictly defined use cases. On the other hand, fuzz tests can test a wide range of unpredictable, random scenarios and discover even hidden vulnerabilities like inconsistent calculations.<\/p>\n<h2 id=\"544d\" class=\"kr ks ij bd kt ku kv kw kx ky kz la lb lc ld le lf lg lh li lj lk ll lm ln lo bi\">Summary<\/h2>\n<p id=\"a2c3\" class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Axelar provides developers with a powerful platform and tools to develop cross-chain applications. Axelar cross-chain applications inherit the bridge\u2019s security, but it doesn\u2019t mean the applications are safe. There are still potential pitfalls during the implementation of the app logic.<\/p>\n<p class=\"pw-post-body-paragraph jr js ij jt b ju lp jw jx jy lq ka kb kc lr ke kf kg ls ki kj kk lt km kn ko ic bi\" data-selectable-paragraph=\"\">Even if the bridge is bulletproof, it doesn\u2019t mean that the application is too, and that the user funds are safe. Always do intense internal testing and independent external <a class=\"ae ly\" href=\"https:\/\/ackeeblockchain.com\/\">security audits<\/a>\u00a0to achieve high-security standards and one of the most important things in Web3 \u2014 community trust.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Communication between multiple chains has become more and more important. Although the major security responsibility and trust are on the selected bridge protocol, bad cross-chain app implementation on top of these bridges can lead to catastrophic consequences. This guide covers the main threats, mistakes, and best practices for developing secure cross-chain apps on the Axelar protocol. Axelar Architecture We will focus mainly&hellip;<\/p>\n","protected":false},"author":17,"featured_media":499,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61,10,80,63,103],"tags":[72,90,71,24,88,68,102,104],"class_list":["post-497","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-education","category-ethereum","category-solidity","category-tutorial","category-wake","tag-axelar","tag-cross-chain","tag-dapps","tag-ethereum","tag-how-to","tag-solidity","tag-tutorial","tag-wake"],"aioseo_notices":[],"featured_image_src":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/04\/1_-9a6-SAyaWE_sVaSx1LJng-600x400.webp","featured_image_src_square":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/04\/1_-9a6-SAyaWE_sVaSx1LJng-600x600.webp","author_info":{"display_name":"Stepan Sonsky","author_link":"https:\/\/ackee.xyz\/blog\/author\/stepan\/"},"_links":{"self":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/497","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/users\/17"}],"replies":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/comments?post=497"}],"version-history":[{"count":0,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/497\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media\/499"}],"wp:attachment":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media?parent=497"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/categories?post=497"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/tags?post=497"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}