diff --git a/404.html b/404.html index b220ffa30..9e517a9e2 100644 --- a/404.html +++ b/404.html @@ -4,7 +4,7 @@ Lisk Documentation - + diff --git a/add-token-to-lisk.html b/add-token-to-lisk.html index d3dbf70d3..7340d9d12 100644 --- a/add-token-to-lisk.html +++ b/add-token-to-lisk.html @@ -4,7 +4,7 @@ Adding an ERC-20 Token to Lisk | Lisk Documentation - + diff --git a/assets/js/6183c0e3.dbf63bd7.js b/assets/js/6183c0e3.dbf63bd7.js deleted file mode 100644 index 2ec7d0224..000000000 --- a/assets/js/6183c0e3.dbf63bd7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklisk_docs=self.webpackChunklisk_docs||[]).push([[444],{1269:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>a,default:()=>m,frontMatter:()=>r,metadata:()=>c,toc:()=>h});var s=n(5893),i=n(1151),l=n(4866),o=n(5162);const r={title:"Lisk L1->L2 migration guide",slug:"/building-on-lisk/migration-guide",description:"A migration guide, explaining how to smoothly migrate any Lisk L1 app to Lisk L2.",keywords:["Lisk","Lisk migration","Lisk L1","Lisk L2","Lisk testnet","Lisk SDK","Solidity","smart contract development","build on lisk"]},a="Lisk L1->L2 migration guide",c={id:"building-on-lisk/migration-guide",title:"Lisk L1->L2 migration guide",description:"A migration guide, explaining how to smoothly migrate any Lisk L1 app to Lisk L2.",source:"@site/docs/building-on-lisk/migration-guide.mdx",sourceDirName:"building-on-lisk",slug:"/building-on-lisk/migration-guide",permalink:"/lisk-documentation/building-on-lisk/migration-guide",draft:!1,unlisted:!1,editUrl:"https://github.com/LiskHQ/lisk-documentation/tree/main/docs/building-on-lisk/migration-guide.mdx",tags:[],version:"current",frontMatter:{title:"Lisk L1->L2 migration guide",slug:"/building-on-lisk/migration-guide",description:"A migration guide, explaining how to smoothly migrate any Lisk L1 app to Lisk L2.",keywords:["Lisk","Lisk migration","Lisk L1","Lisk L2","Lisk testnet","Lisk SDK","Solidity","smart contract development","build on lisk"]},sidebar:"documentationSidebar",previous:{title:"Deploying a smart contract with Hardhat",permalink:"/lisk-documentation/building-on-lisk/deploying-a-smart-contract"},next:{title:"Bridges",permalink:"/lisk-documentation/lisk-tools/bridges"}},d={},h=[{value:"Requirements",id:"requirements",level:2},{value:"Project setup",id:"project-setup",level:2},{value:"Module migration",id:"module-migration",level:2},{value:"Table: Lisk L1/L2 comparison",id:"table-lisk-l1l2-comparison",level:3},{value:"Storage",id:"storage",level:3},{value:"Events",id:"events",level:3},{value:"State transition logic",id:"state-transition-logic",level:3},{value:"Configuration",id:"configuration",level:4},{value:"Verification",id:"verification",level:4},{value:"Execution",id:"execution",level:4},{value:"Endpoints",id:"endpoints",level:3},{value:"Next steps",id:"next-steps",level:2},{value:"Testing the smart contract",id:"testing-the-smart-contract",level:3},{value:"Smart contract deployment",id:"smart-contract-deployment",level:3}];function u(e){const t={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",li:"li",ol:"ol",p:"p",pre:"pre",section:"section",strong:"strong",sup:"sup",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.a)(),...e.components},{Details:n}=t;return n||function(e,t){throw new Error("Expected "+(t?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.h1,{id:"lisk-l1-l2-migration-guide",children:"Lisk L1->L2 migration guide"}),"\n",(0,s.jsx)(t.p,{children:"How to smoothly migrate any Lisk L1 app to Lisk L2."}),"\n",(0,s.jsx)(t.h2,{id:"requirements",children:"Requirements"}),"\n",(0,s.jsx)(t.p,{children:"You need:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["A Lisk L1 application built on ",(0,s.jsx)(t.a,{href:"https://github.com/LiskHQ/lisk-sdk",children:"Lisk SDK"})," version 6.0.0 or later."]}),"\n",(0,s.jsxs)(t.li,{children:["A basic understanding of ",(0,s.jsx)(t.a,{href:"https://soliditylang.org/",children:"Solidity"}),"."]}),"\n",(0,s.jsxs)(t.li,{children:["The smart contract development framework of your choice.\nIn this guide, we will use the ",(0,s.jsx)(t.a,{href:"https://book.getfoundry.sh/",children:"Foundry"})," framework."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"project-setup",children:"Project setup"}),"\n",(0,s.jsxs)(t.p,{children:["To illustrate the migration process, we will use the ",(0,s.jsx)(t.a,{href:"https://github.com/LiskHQ/lisk-sdk-examples/tree/development/tutorials/hello/hello_client/src/app/modules/hello",children:"Hello module"})," from Lisk L1, and migrate it to Lisk L2."]}),"\n",(0,s.jsx)(t.p,{children:"To start with the project migration, first create a new project with Foundry like this:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"forge init hello_liskl2\n"})}),"\n",(0,s.jsxs)(t.p,{children:["This will create a new folder ",(0,s.jsx)(t.code,{children:"hello_liskl2"}),", which will contain the smart contracts we are going to implement."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"cd hello_liskl2\n"})}),"\n",(0,s.jsx)(t.h2,{id:"module-migration",children:"Module migration"}),"\n",(0,s.jsx)(t.admonition,{type:"info",children:(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Modules in Lisk L1"})," are re-implemented as ",(0,s.jsx)(t.strong,{children:"smart contracts in Lisk L2."})]})}),"\n",(0,s.jsxs)(t.p,{children:["To create a new smart contract, create a new file ",(0,s.jsx)(t.code,{children:"Hello.sol"})," under ",(0,s.jsx)(t.code,{children:"src/"})," and add the following content:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// SPDX-License-Identifier: MIT\n// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0\npragma solidity ^0.8.20;\n\ncontract Hello {\n\n}\n"})}),"\n",(0,s.jsxs)(t.p,{children:["Inside the new contract, we will put all the logic that was residing in the Lisk L1 ",(0,s.jsx)(t.code,{children:"Hello"})," module before."]}),"\n",(0,s.jsx)(t.h3,{id:"table-lisk-l1l2-comparison",children:"Table: Lisk L1/L2 comparison"}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Description"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Lisk L1"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Lisk L2"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Onchain business logic"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Module"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/first-app/",children:"Smart contract"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Onchain data storage"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Stores (onchain)"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/variables/",children:"State variables"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Logging to the blockchain"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Blockchain Events"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/events/",children:"Events"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"State-transition logic triggered by a transaction"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Commands"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/function/",children:"Functions"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"API"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Endpoints"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/view-and-pure-functions/",children:"View functions"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Internal API"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Methods"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/function-modifier/",children:"Functions (+ modifiers)"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Logic triggered per block"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Lifecycle Hooks"}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["X",(0,s.jsx)(t.sup,{children:(0,s.jsx)(t.a,{href:"#user-content-fn-1",id:"user-content-fnref-1","data-footnote-ref":!0,"aria-describedby":"footnote-label",children:"1"})})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"storage",children:"Storage"}),"\n",(0,s.jsx)(t.p,{children:"Migrate the onchain stores of a module by implementing corresponding state variables in the contract as shown below."}),"\n",(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"liskl1",label:"Lisk L1",children:(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"message",label:"Message Store",default:!0,children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/stores/message.ts"',children:"import { BaseStore } from 'lisk-sdk';\n\nexport interface MessageStoreData {\n message: string;\n}\n\nexport const messageStoreSchema = {\n $id: '/hello/message',\n type: 'object',\n required: ['message'],\n properties: {\n message: {\n dataType: 'string',\n fieldNumber: 1,\n },\n },\n};\n\nexport class MessageStore extends BaseStore {\n public schema = messageStoreSchema;\n}\n"})})}),(0,s.jsx)(o.Z,{value:"counter",label:"Counter Store",children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/stores/counter.ts"',children:"import { BaseStore } from 'lisk-sdk';\n\nexport interface CounterStoreData {\n counter: number;\n}\n\nexport const counterKey = Buffer.alloc(0);\n\nexport const counterStoreSchema = {\n $id: '/hello/counter',\n type: 'object',\n required: ['counter'],\n properties: {\n counter: {\n dataType: 'uint32',\n fieldNumber: 1,\n },\n },\n};\n\nexport class CounterStore extends BaseStore {\n public schema = counterStoreSchema;\n}\n"})})})]})}),(0,s.jsx)(o.Z,{value:"liskl2",label:"Lisk L2",default:!0,children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// SPDX-License-Identifier: MIT\n// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0\npragma solidity ^0.8.20;\n\ncontract Hello {\n /** State variables */\n // State variable for the Hello messages\n mapping(address => string) public message;\n // State variable for the message counter\n uint32 public counter = 0;\n}\n"})})})]}),"\n",(0,s.jsx)(t.h3,{id:"events",children:"Events"}),"\n",(0,s.jsx)(t.p,{children:"Migrate the blockchain events of a module by implementing corresponding events in the contract as shown below."}),"\n",(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"liskl1",label:"Lisk L1",children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/events/new_hello.ts"',children:"import { BaseEvent } from 'lisk-sdk';\n\nexport const newHelloEventSchema = {\n $id: '/hello/events/new_hello',\n type: 'object',\n required: ['senderAddress', 'message'],\n properties: {\n senderAddress: {\n dataType: 'bytes',\n fieldNumber: 1,\n },\n message: {\n dataType: 'string',\n fieldNumber: 2,\n },\n },\n};\n\nexport interface NewHelloEventData {\n senderAddress: Buffer;\n message: string;\n}\n\nexport class NewHelloEvent extends BaseEvent {\n public schema = newHelloEventSchema;\n}\n"})})}),(0,s.jsx)(o.Z,{value:"liskl2",label:"Lisk L2",default:!0,children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// SPDX-License-Identifier: MIT\n// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0\npragma solidity ^0.8.20;\n\ncontract Hello {\n /** State variables */\n // State variable for the Hello messages\n mapping(address => string) public message;\n // State variable for the message counter\n uint32 public counter = 0;\n\n /** Events */\n // Event for new Hello messages\n event NewHello(address indexed sender, string message);\n}\n"})})})]}),"\n",(0,s.jsx)(t.h3,{id:"state-transition-logic",children:"State transition logic"}),"\n",(0,s.jsxs)(n,{children:[(0,s.jsx)("summary",{children:"Configuration migration"}),(0,s.jsx)(t.h4,{id:"configuration",children:"Configuration"}),(0,s.jsxs)(t.p,{children:["The module-specific configurations, which resided in the ",(0,s.jsx)(t.code,{children:"config.json"})," on Lisk L1, are now part of the smart contract itself, and defined as state variables."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// Blacklist of words that are not allowed in the Hello message\nstring[] public blacklist = ["word1","word2"];\n// Maximum length of the Hello message\nuint32 public maxLength = 200;\n// Minimum length of the Hello message\nuint32 public minlength = 3\n'})}),(0,s.jsx)(t.p,{children:"To edit the configuration options of the Hello module, we implement the following functions in the Hello contract:"}),(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"setBlacklist()"})," to configure the blacklist of words that are not allowed in the Hello message."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"setMinMaxMessageLength()"})," to configure the minimum and maximum length of the Hello message."]}),"\n"]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// Function to configure the blacklist\nfunction setBlacklist(string[] memory _newBlackList) public onlyOwner {\n blacklist = _newBlackList;\n} \n// Function to configure min/max message length\nfunction setMinMaxMessageLength(uint32 _newMinLength,uint32 _newMaxLength) public onlyOwner {\n minlength = _newMinLength;\n maxLength = _newMaxLength;\n}\n"})}),(0,s.jsx)(t.p,{children:"As seen in the above code snippet, we add the following modifiers to the functions:"}),(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"public"})," to make the function callable from outside the contract.\nThis is a default visibility modifier for functions in Solidity."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"onlyOwner"})," to check that the caller is the owner of the contract.\nThis is a custom modifier that we need to implement in the contract manually, as shown in the example below."]}),"\n"]}),(0,s.jsxs)(t.p,{children:["To set the owner of the contract, we add a new state variable ",(0,s.jsx)(t.code,{children:"owner"})," and a constructor which sets the ",(0,s.jsx)(t.code,{children:"owner"})," variable to the account address that deploys the contract."]}),(0,s.jsx)(t.admonition,{type:"tip",children:(0,s.jsxs)(t.p,{children:["For updating the owner of the smart contract, you can imeplemnt a corresponding function ",(0,s.jsx)(t.code,{children:"setOwner()"})," and use the ",(0,s.jsx)(t.code,{children:"onlyOwner"})," modifier to ensure that only the current owner can call this function."]})}),(0,s.jsxs)(t.p,{children:["Finally, we can check for the message sender being the owner of the contract in the ",(0,s.jsx)(t.code,{children:"onlyOwner"})," modifier which is used for the ",(0,s.jsx)(t.code,{children:"setBlacklist()"})," and ",(0,s.jsx)(t.code,{children:"setMinMaxMessageLength()"})," functions."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// Address of the contract owner\naddress public immutable owner;\n\nconstructor() {\n // Set the transaction sender as the owner of the contract.\n owner = msg.sender;\n}\n\n/** Modifiers */\n// Modifier to check that the caller is the owner of the contract.\nmodifier onlyOwner() {\n require(msg.sender == owner, "Not owner");\n _;\n}\n'})})]}),"\n",(0,s.jsxs)(n,{children:[(0,s.jsx)("summary",{children:"Verification migration"}),(0,s.jsx)(t.h4,{id:"verification",children:"Verification"}),(0,s.jsx)(t.p,{children:"To verify the Hello message, we implement custom modifiers in the contract."}),(0,s.jsxs)(t.p,{children:["Inside of the modifiers, we check the length of the message and if it contains any blacklisted words, like it was done in the ",(0,s.jsx)(t.code,{children:"verify()"})," method of the Lisk L1 Hello module."]}),(0,s.jsxs)(t.p,{children:["Conveniently check the length of Hello messages in the ",(0,s.jsx)(t.code,{children:"validLength"})," modifier like this:"]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// Validate message length\nmodifier validLength(string memory _message) {\n require(bytes(_message).length >= minlength, "Message too short");\n require(bytes(_message).length <= maxLength, "Message too long");\n _;\n}\n'})}),(0,s.jsxs)(t.p,{children:["To check if the message contains any blacklisted words, we implement the ",(0,s.jsx)(t.code,{children:"validWords"})," modifier in the contract."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// Validate message content\nmodifier validWords(string memory _message) {\n bytes memory whereBytes = bytes (_message);\n\n for (uint h = 0; h < blacklist.length; h++) {\n bool found = false;\n bytes memory whatBytes = bytes (blacklist[h]);\n for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {\n bool flag = true;\n for (uint j = 0; j < whatBytes.length; j++)\n if (whereBytes [i + j] != whatBytes [j]) {\n flag = false;\n break;\n }\n if (flag) {\n found = true;\n break;\n }\n }\n require (!found, "Message contains blacklisted word");\n }\n _;\n}\n'})})]}),"\n",(0,s.jsxs)(n,{children:[(0,s.jsx)("summary",{children:"Execution migration"}),(0,s.jsx)(t.h4,{id:"execution",children:"Execution"}),(0,s.jsxs)(t.p,{children:["To migrate the createHello command execution, we implement the ",(0,s.jsx)(t.code,{children:"createHello()"})," function in the contract."]}),(0,s.jsxs)(t.p,{children:["Inside of this function, we save the message of the sender in the ",(0,s.jsx)(t.code,{children:"message"})," mapping under the sender address."]}),(0,s.jsx)(t.admonition,{type:"tip",children:(0,s.jsxs)(t.p,{children:["The sender address is a ",(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/variables/",children:"global variable"})," in Solidity and can be accessed with ",(0,s.jsx)(t.code,{children:"msg.sender"}),"."]})}),(0,s.jsxs)(t.p,{children:["Additionally, we increment the Hello message counter by +1, and emit the ",(0,s.jsx)(t.code,{children:"NewHello"})," event, like it was done in the ",(0,s.jsx)(t.code,{children:"execute()"})," method of the Lisk L1 Hello module previously."]}),(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"validMessage()"})," modifier the we defined above in the Verification section is used to check if the message is valid, before ",(0,s.jsx)(t.code,{children:"createHello()"})," function is executed."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// Function to create a new Hello message\nfunction createHello(string calldata _message) public validMessage(_message) {\n message[msg.sender] = _message;\n counter+=1;\n emit NewHello(msg.sender, _message);\n}\n"})})]}),"\n",(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"liskl1",label:"Lisk L1",children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/commands/create_hello_command.ts"',children:"/* eslint-disable class-methods-use-this */\n\nimport {\n BaseCommand,\n CommandVerifyContext,\n CommandExecuteContext,\n VerificationResult,\n VerifyStatus,\n} from 'lisk-sdk';\nimport { createHelloSchema } from '../schema';\nimport { MessageStore } from '../stores/message';\nimport { counterKey, CounterStore, CounterStoreData } from '../stores/counter';\nimport { ModuleConfig } from '../types';\nimport { NewHelloEvent } from '../events/new_hello';\n\ninterface Params {\n message: string;\n}\n\nexport class CreateHelloCommand extends BaseCommand {\n public schema = createHelloSchema;\n private _blacklist!: string[];\n\n // eslint-disable-next-line @typescript-eslint/require-await\n public async init(config: ModuleConfig): Promise {\n // Set _blacklist to the value of the blacklist defined in the module config\n this._blacklist = config.blacklist;\n // Set the max message length to the value defined in the module config\n this.schema.properties.message.maxLength = config.maxMessageLength;\n // Set the min message length to the value defined in the module config\n this.schema.properties.message.minLength = config.minMessageLength;\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await\n public async verify(context: CommandVerifyContext): Promise {\n let validation: VerificationResult;\n const wordList = context.params.message.split(\" \");\n const found = this._blacklist.filter(value => wordList.includes(value));\n if (found.length > 0) {\n context.logger.info(\"==== FOUND: Message contains a blacklisted word ====\");\n throw new Error(\n `Illegal word in hello message: ${ found.toString()}`\n );\n } else {\n context.logger.info(\"==== NOT FOUND: Message contains no blacklisted words ====\");\n validation = {\n status: VerifyStatus.OK\n };\n }\n return validation;\n }\n\n public async execute(context: CommandExecuteContext): Promise {\n // 1. Get account data of the sender of the Hello transaction.\n const { senderAddress } = context.transaction;\n // 2. Get message and counter stores.\n const messageSubstore = this.stores.get(MessageStore);\n const counterSubstore = this.stores.get(CounterStore);\n\n // 3. Save the Hello message to the message store, using the senderAddress as key, and the message as value.\n await messageSubstore.set(context, senderAddress, {\n message: context.params.message,\n });\n\n // 3. Get the Hello counter from the counter store.\n let helloCounter: CounterStoreData;\n try {\n helloCounter = await counterSubstore.get(context, counterKey);\n } catch (error) {\n helloCounter = {\n counter: 0,\n }\n }\n // 5. Increment the Hello counter +1.\n helloCounter.counter+=1;\n\n // 6. Save the Hello counter to the counter store.\n await counterSubstore.set(context, counterKey, helloCounter);\n\n // 7. Emit a \"New Hello\" event\n const newHelloEvent = this.events.get(NewHelloEvent);\n newHelloEvent.add(context, {\n senderAddress: context.transaction.senderAddress,\n message: context.params.message\n },[context.transaction.senderAddress]);\n }\n}\n"})})}),(0,s.jsx)(o.Z,{value:"liskl2",label:"Lisk L2",default:!0,children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// SPDX-License-Identifier: MIT\n// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0\npragma solidity ^0.8.20;\n\ncontract Hello {\n /** State variables */\n // State variable for the Hello messages\n mapping(address => string) public message;\n // State variable for the message counter\n uint32 public counter = 0;\n // Address of the contract owner\n address public immutable owner;\n // Blacklist of words that are not allowed in the Hello message\n string[] public blacklist = ["word1","word2"];\n // Maximum length of the Hello message\n uint32 public maxLength = 200;\n // Minimum length of the Hello message\n uint32 public minlength = 3;\n\n constructor() {\n // Set the transaction sender as the owner of the contract.\n owner = msg.sender;\n }\n\n /** Modifiers */\n // Modifier to check that the caller is the owner of the contract.\n modifier onlyOwner() {\n require(msg.sender == owner, "Not owner");\n _;\n }\n // Validate message length\n modifier validLength(string memory _message) {\n require(bytes(_message).length >= minlength, "Message too short");\n require(bytes(_message).length <= maxLength, "Message too long");\n _;\n }\n // Validate message content\n modifier validWords(string memory _message) {\n bytes memory whereBytes = bytes (_message);\n\n for (uint h = 0; h < blacklist.length; h++) {\n bool found = false;\n bytes memory whatBytes = bytes (blacklist[h]);\n for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {\n bool flag = true;\n for (uint j = 0; j < whatBytes.length; j++)\n if (whereBytes [i + j] != whatBytes [j]) {\n flag = false;\n break;\n }\n if (flag) {\n found = true;\n break;\n }\n }\n require (!found, "Message contains blacklisted word");\n }\n _;\n }\n\n /** Events */\n // Event for new Hello messages\n event NewHello(address indexed sender, string message);\n\n /** Functions */\n // Function to configure the blacklist\n function setBlacklist(string[] memory _newBlackList) public onlyOwner {\n blacklist = _newBlackList;\n } \n // Function to configure min/max message length\n function setMinMaxMessageLength(uint32 _newMinLength,uint32 _newMaxLength) public onlyOwner {\n minlength = _newMinLength;\n maxLength = _newMaxLength;\n }\n // Function to create a new Hello message\n function createHello(string calldata _message) public validLength(_message) validWords(_message) {\n message[msg.sender] = _message;\n counter+=1;\n emit NewHello(msg.sender, _message);\n }\n}\n'})})})]}),"\n",(0,s.jsx)(t.h3,{id:"endpoints",children:"Endpoints"}),"\n",(0,s.jsx)(t.p,{children:"Migrate the module endpoints by implementing corresponding view functions in the contract as shown below."}),"\n",(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"liskl1",label:"Lisk L1",children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/endpoint.ts"',children:"export class HelloEndpoint extends BaseEndpoint {\n public async getHelloCounter(ctx: ModuleEndpointContext): Promise {\n const counterSubStore = this.stores.get(CounterStore);\n\n const helloCounter = await counterSubStore.get(\n ctx,\n counterKey,\n );\n\n return helloCounter;\n }\n\n public async getHello(ctx: ModuleEndpointContext): Promise {\n const messageSubStore = this.stores.get(MessageStore);\n\n const { address } = ctx.params;\n if (typeof address !== 'string') {\n throw new Error('Parameter address must be a string.');\n }\n cryptography.address.validateLisk32Address(address);\n const helloMessage = await messageSubStore.get(\n ctx,\n cryptography.address.getAddressFromLisk32Address(address),\n );\n return helloMessage;\n }\n}\n"})})}),(0,s.jsxs)(o.Z,{value:"liskl2",label:"Lisk L2",default:!0,children:[(0,s.jsxs)(t.p,{children:["For simple getters, it is sufficient to add the ",(0,s.jsx)(t.code,{children:"public"})," visibility modifier to the state variables.\nPublic state variables can be accessed directly from external parties."]}),(0,s.jsx)(t.p,{children:"For more complex endpoints, you can implement corresponding view functions in the contract."}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// State variable for the Hello messages\nmapping(address => string) public message;\n// State variable for the message counter\nuint32 public counter = 0;\n"})})]})]}),"\n",(0,s.jsx)(t.h2,{id:"next-steps",children:"Next steps"}),"\n",(0,s.jsx)(t.p,{children:"Now that we re-implemented the Hello module from Lisk L1 as a smart contract in Lisk L2, it is possible to directly deploy the Hello contract to Lisk L2 and interact with it."}),"\n",(0,s.jsxs)(t.p,{children:["Before deploying the smart contract to Lisk, it is recommended to ",(0,s.jsx)(t.a,{href:"#testing-the-smart-contract",children:"test it locally"})," by writing corresponding tests for the newly created smart contract.\nOnce the smart contract is ",(0,s.jsx)(t.a,{href:"#smart-contract-deployment",children:"deployed"})," to Lisk, you can interact with it by calling its public functions."]}),"\n",(0,s.jsx)(t.p,{children:"Finally, you can migrate the plugins and UI of the Lisk L1 Hello app to be compatible with the new API, to complete the migration process of your Lisk application."}),"\n",(0,s.jsx)(t.h3,{id:"testing-the-smart-contract",children:"Testing the smart contract"}),"\n",(0,s.jsx)(t.p,{children:"By testing the smart contract, you can verify that the smart contract behaves as expected and that it is free of bugs, before deploying it to Lisk."}),"\n",(0,s.jsxs)(t.p,{children:["Foundry provides a testing framework to support you in writing tests for smart contracts.\nSee ",(0,s.jsx)(t.a,{href:"https://book.getfoundry.sh/forge/tests",children:"Tests - Foundry Book"})," for examples and references regarding the testing framework."]}),"\n",(0,s.jsxs)(t.p,{children:["To test the Hello smart contract, create a new file ",(0,s.jsx)(t.code,{children:"Hello.t.sol"})," under ",(0,s.jsx)(t.code,{children:"test/"})," and add the following content:"]}),"\n",(0,s.jsxs)(n,{children:[(0,s.jsx)("summary",{children:"Hello.t.sol"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/test/Hello.t.sol"',children:'// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport {Test, console} from "forge-std/Test.sol";\nimport {Hello} from "../src/Hello.sol";\n\ncontract HelloTest is Test {\n Hello public hello;\n address alice = makeAddr("alice");\n event NewHello(address indexed sender, string message);\n\n function setUp() public {\n hello = new Hello();\n }\n\n function test_CreateHello() public {\n string memory message = "Hello World";\n // Expect NewHello event\n vm.expectEmit(true,false,false,false);\n emit NewHello(address(alice), message);\n // Create a new Hello message\n hoax(alice, 100 ether);\n hello.createHello(message);\n // Check the message\n assertEq(hello.message(alice),message);\n // Check if counter = 1\n assertEq(hello.counter(),1);\n }\n\n function test_MinLength() public {\n vm.expectRevert("Message too short");\n hello.createHello("Hi");\n }\n\n function test_MaxLength() public {\n vm.expectRevert("Message too long");\n hello.createHello("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean porta neque eget elit tristique pharetra. Pellentesque tempus sollicitudin tortor, ut tempus diam. Nulla facilisi. Donec at neque sapien.");\n }\n\n function test_Blacklist() public {\n vm.expectRevert("Message contains blacklisted word");\n hello.createHello("Hello word1");\n }\n\n function test_SetBlacklist() public {\n // Create a temporary dynamic array of strings\n string[] memory bl = new string[](3);\n bl[0] = "word1";\n bl[1] = "word3";\n bl[2] = "word4";\n hello.setBlacklist(bl);\n string[] memory getBL = new string[](2);\n getBL[0] = hello.blacklist(0);\n getBL[1] = hello.blacklist(1);\n assertEq(getBL[0], bl[0]);\n assertEq(getBL[1], bl[1]);\n }\n\n function test_SetBlacklistNotOwner() public {\n string[] memory bl = new string[](3);\n bl[0] = "word1";\n bl[1] = "word3";\n bl[2] = "word4";\n vm.expectRevert("Not owner");\n hoax(alice, 100 ether);\n hello.setBlacklist(bl);\n }\n\n function test_SetMinMaxMessageLength() public {\n uint32 newMin = 1;\n uint32 newMax = 500;\n hello.setMinMaxMessageLength(newMin,newMax);\n assertEq(hello.minLength(), newMin);\n assertEq(hello.maxLength(), newMax);\n }\n\n function test_SetMinMaxMessageLengthNotOwner() public {\n uint32 newMin = 1;\n uint32 newMax = 500;\n hoax(alice, 100 ether);\n vm.expectRevert();\n hello.setMinMaxMessageLength(newMin,newMax);\n }\n}\n'})})]}),"\n",(0,s.jsx)(t.p,{children:"To run the tests, execute the following command:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"forge test\n"})}),"\n",(0,s.jsx)(t.p,{children:"The output should look like this:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-text",children:"Running 8 tests for test/Hello.t.sol:HelloTest\n[PASS] test_Blacklist() (gas: 23772)\n[PASS] test_CreateHello() (gas: 66179)\n[PASS] test_MaxLength() (gas: 14179)\n[PASS] test_MinLength() (gas: 13929)\n[PASS] test_SetBlacklist() (gas: 885276)\n[PASS] test_SetBlacklistNotOwner() (gas: 16978)\n[PASS] test_SetMinMaxMessageLength() (gas: 853243)\n[PASS] test_SetMinMaxMessageLengthNotOwner() (gas: 10889)\nTest result: ok. 8 passed; 0 failed; 0 skipped; finished in 3.35ms\n"})}),"\n",(0,s.jsx)(t.h3,{id:"smart-contract-deployment",children:"Smart contract deployment"}),"\n",(0,s.jsx)(t.p,{children:"You can now deploy the smart contract to Lisk.\nFor this example, we will use the Lisk Sepolia network to deploy the Hello contract."}),"\n",(0,s.jsxs)(t.p,{children:["Add the ",(0,s.jsx)(t.code,{children:"--verify"})," flag to the ",(0,s.jsx)(t.code,{children:"forge create"})," command to directly verify the smart contract on BlockScout."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"forge create --rpc-url https://rpc.sepolia-api.lisk.com \\\n--etherscan-api-key 123 \\\n--verify \\\n--verifier blockscout \\\n--verifier-url https://sepolia-blockscout.lisk.com/api \\\n--private-key \\\nsrc/Hello.sol:Hello\n"})}),"\n",(0,s.jsx)(t.p,{children:"If the deployment went successfully, the output should look like this:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-text",children:"[\u2822] Compiling...\nNo files changed, compilation skipped\nDeployer: 0x3C46A11471f285E36EE8d089473ce98269D1b081\nDeployed to: 0x0a5A1C81F278cAe80d340a4A97E2D7B1c3Ec511a\nTransaction hash: 0x52bb6aab8ceeecef674253ecc0ccfe35baeac7db3cc8e889a9da1f7cf1ce0593\nStarting contract verification...\nWaiting for blockscout to detect contract deployment...\nStart verifying contract `0x0a5A1C81F278cAe80d340a4A97E2D7B1c3Ec511a` deployed on 4202\n\nSubmitting verification for [src/Hello.sol:Hello] 0x0a5A1C81F278cAe80d340a4A97E2D7B1c3Ec511a.\nSubmitted contract for verification:\n\tResponse: `OK`\n\tGUID: `0a5a1c81f278cae80d340a4a97e2d7b1c3ec511a65cf6f72`\n\tURL: https://sepolia-blockscout.lisk.com/address/0x0a5a1c81f278cae80d340a4a97e2d7b1c3ec511a\nContract verification status:\nResponse: `OK`\nDetails: `Unknown UID`\n"})}),"\n",(0,s.jsx)(t.p,{children:"After the smart contract is deployed, you can interact with it by calling its public functions."}),"\n",(0,s.jsx)(t.p,{children:"From here, you can migrate the plugins and UI of the Lisk L1 app to be compatible with the new API, to complete the migration process of your Lisk application."}),"\n",(0,s.jsxs)(t.p,{children:["In case you need further assistance, feel free to reach out to the Lisk community at ",(0,s.jsx)(t.a,{href:"https://lisk.chat",children:"Lisk.chat"}),"."]}),"\n",(0,s.jsxs)(t.section,{"data-footnotes":!0,className:"footnotes",children:[(0,s.jsx)(t.h2,{className:"sr-only",id:"footnote-label",children:"Footnotes"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{id:"user-content-fn-1",children:["\n",(0,s.jsxs)(t.p,{children:["No direct equivalent in solidity.\nPlease investigate for custom solutions to migrate logic residing in the lifecycle hooks. ",(0,s.jsx)(t.a,{href:"#user-content-fnref-1","data-footnote-backref":"","aria-label":"Back to reference 1",className:"data-footnote-backref",children:"\u21a9"})]}),"\n"]}),"\n"]}),"\n"]})]})}function m(e={}){const{wrapper:t}={...(0,i.a)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},5162:(e,t,n)=>{n.d(t,{Z:()=>o});n(7294);var s=n(512);const i={tabItem:"tabItem_Ymn6"};var l=n(5893);function o(e){let{children:t,hidden:n,className:o}=e;return(0,l.jsx)("div",{role:"tabpanel",className:(0,s.Z)(i.tabItem,o),hidden:n,children:t})}},4866:(e,t,n)=>{n.d(t,{Z:()=>k});var s=n(7294),i=n(512),l=n(2466),o=n(6550),r=n(469),a=n(1980),c=n(7392),d=n(12);function h(e){return s.Children.toArray(e).filter((e=>"\n"!==e)).map((e=>{if(!e||(0,s.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}function u(e){const{values:t,children:n}=e;return(0,s.useMemo)((()=>{const e=t??function(e){return h(e).map((e=>{let{props:{value:t,label:n,attributes:s,default:i}}=e;return{value:t,label:n,attributes:s,default:i}}))}(n);return function(e){const t=(0,c.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const i=(0,o.k6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,a._X)(l),(0,s.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(i.location.search);t.set(l,e),i.replace({...i.location,search:t.toString()})}),[l,i])]}function p(e){const{defaultValue:t,queryString:n=!1,groupId:i}=e,l=u(e),[o,a]=(0,s.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const s=n.find((e=>e.default))??n[0];if(!s)throw new Error("Unexpected error: 0 tabValues");return s.value}({defaultValue:t,tabValues:l}))),[c,h]=g({queryString:n,groupId:i}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[i,l]=(0,d.Nk)(n);return[i,(0,s.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:i}),x=(()=>{const e=c??p;return m({value:e,tabValues:l})?e:null})();(0,r.Z)((()=>{x&&a(x)}),[x]);return{selectedValue:o,selectValue:(0,s.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);a(e),h(e),f(e)}),[h,f,l]),tabValues:l}}var f=n(2389);const x={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};var b=n(5893);function y(e){let{className:t,block:n,selectedValue:s,selectValue:o,tabValues:r}=e;const a=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.o5)(),d=e=>{const t=e.currentTarget,n=a.indexOf(t),i=r[n].value;i!==s&&(c(t),o(i))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=a.indexOf(e.currentTarget)+1;t=a[n]??a[0];break}case"ArrowLeft":{const n=a.indexOf(e.currentTarget)-1;t=a[n]??a[a.length-1];break}}t?.focus()};return(0,b.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.Z)("tabs",{"tabs--block":n},t),children:r.map((e=>{let{value:t,label:n,attributes:l}=e;return(0,b.jsx)("li",{role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,ref:e=>a.push(e),onKeyDown:h,onClick:d,...l,className:(0,i.Z)("tabs__item",x.tabItem,l?.className,{"tabs__item--active":s===t}),children:n??t},t)}))})}function j(e){let{lazy:t,children:n,selectedValue:i}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===i));return e?(0,s.cloneElement)(e,{className:"margin-top--md"}):null}return(0,b.jsx)("div",{className:"margin-top--md",children:l.map(((e,t)=>(0,s.cloneElement)(e,{key:t,hidden:e.props.value!==i})))})}function w(e){const t=p(e);return(0,b.jsxs)("div",{className:(0,i.Z)("tabs-container",x.tabList),children:[(0,b.jsx)(y,{...e,...t}),(0,b.jsx)(j,{...e,...t})]})}function k(e){const t=(0,f.Z)();return(0,b.jsx)(w,{...e,children:h(e.children)},String(t))}},1151:(e,t,n)=>{n.d(t,{Z:()=>r,a:()=>o});var s=n(7294);const i={},l=s.createContext(i);function o(e){const t=s.useContext(l);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(l.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/6183c0e3.dc482ac8.js b/assets/js/6183c0e3.dc482ac8.js new file mode 100644 index 000000000..35d75b139 --- /dev/null +++ b/assets/js/6183c0e3.dc482ac8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklisk_docs=self.webpackChunklisk_docs||[]).push([[444],{1269:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>a,default:()=>m,frontMatter:()=>r,metadata:()=>c,toc:()=>h});var s=n(5893),i=n(1151),l=n(4866),o=n(5162);const r={title:"Lisk L1->L2 migration guide",slug:"/building-on-lisk/migration-guide",description:"A migration guide, explaining how to smoothly migrate any Lisk L1 app to Lisk L2.",keywords:["Lisk","Lisk migration","Lisk L1","Lisk L2","Lisk testnet","Lisk SDK","Solidity","smart contract development","build on lisk"]},a="Lisk L1->L2 migration guide",c={id:"building-on-lisk/migration-guide",title:"Lisk L1->L2 migration guide",description:"A migration guide, explaining how to smoothly migrate any Lisk L1 app to Lisk L2.",source:"@site/docs/building-on-lisk/migration-guide.mdx",sourceDirName:"building-on-lisk",slug:"/building-on-lisk/migration-guide",permalink:"/lisk-documentation/building-on-lisk/migration-guide",draft:!1,unlisted:!1,editUrl:"https://github.com/LiskHQ/lisk-documentation/tree/main/docs/building-on-lisk/migration-guide.mdx",tags:[],version:"current",frontMatter:{title:"Lisk L1->L2 migration guide",slug:"/building-on-lisk/migration-guide",description:"A migration guide, explaining how to smoothly migrate any Lisk L1 app to Lisk L2.",keywords:["Lisk","Lisk migration","Lisk L1","Lisk L2","Lisk testnet","Lisk SDK","Solidity","smart contract development","build on lisk"]},sidebar:"documentationSidebar",previous:{title:"Deploying a smart contract with Hardhat",permalink:"/lisk-documentation/building-on-lisk/deploying-a-smart-contract"},next:{title:"Bridges",permalink:"/lisk-documentation/lisk-tools/bridges"}},d={},h=[{value:"Requirements",id:"requirements",level:2},{value:"Project setup",id:"project-setup",level:2},{value:"Module migration",id:"module-migration",level:2},{value:"Table: Lisk L1/L2 comparison",id:"table-lisk-l1l2-comparison",level:3},{value:"Storage",id:"storage",level:3},{value:"Events",id:"events",level:3},{value:"State transition logic",id:"state-transition-logic",level:3},{value:"Configuration",id:"configuration",level:4},{value:"Verification",id:"verification",level:4},{value:"Execution",id:"execution",level:4},{value:"Endpoints",id:"endpoints",level:3},{value:"Next steps",id:"next-steps",level:2},{value:"Testing the smart contract",id:"testing-the-smart-contract",level:3},{value:"Smart contract deployment",id:"smart-contract-deployment",level:3}];function u(e){const t={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",li:"li",ol:"ol",p:"p",pre:"pre",section:"section",strong:"strong",sup:"sup",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.a)(),...e.components},{Details:n}=t;return n||function(e,t){throw new Error("Expected "+(t?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.h1,{id:"lisk-l1-l2-migration-guide",children:"Lisk L1->L2 migration guide"}),"\n",(0,s.jsx)(t.p,{children:"How to smoothly migrate any Lisk L1 app to Lisk L2."}),"\n",(0,s.jsx)(t.h2,{id:"requirements",children:"Requirements"}),"\n",(0,s.jsx)(t.p,{children:"You need:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["A Lisk L1 application built on ",(0,s.jsx)(t.a,{href:"https://github.com/LiskHQ/lisk-sdk",children:"Lisk SDK"})," version 6.0.0 or later."]}),"\n",(0,s.jsxs)(t.li,{children:["A basic understanding of ",(0,s.jsx)(t.a,{href:"https://soliditylang.org/",children:"Solidity"}),"."]}),"\n",(0,s.jsxs)(t.li,{children:["The smart contract development framework of your choice.\nIn this guide, we will use the ",(0,s.jsx)(t.a,{href:"https://book.getfoundry.sh/",children:"Foundry"})," framework."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"project-setup",children:"Project setup"}),"\n",(0,s.jsxs)(t.p,{children:["To illustrate the migration process, we will use the ",(0,s.jsx)(t.a,{href:"https://github.com/LiskHQ/lisk-sdk-examples/tree/development/tutorials/hello/hello_client/src/app/modules/hello",children:"Hello module"})," from Lisk L1, and migrate it to Lisk L2."]}),"\n",(0,s.jsx)(t.p,{children:"To start with the project migration, first create a new project with Foundry like this:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"forge init hello_liskl2\n"})}),"\n",(0,s.jsxs)(t.p,{children:["This will create a new folder ",(0,s.jsx)(t.code,{children:"hello_liskl2"}),", which will contain the smart contracts we are going to implement."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"cd hello_liskl2\n"})}),"\n",(0,s.jsx)(t.h2,{id:"module-migration",children:"Module migration"}),"\n",(0,s.jsx)(t.admonition,{type:"info",children:(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Modules in Lisk L1"})," are re-implemented as ",(0,s.jsx)(t.strong,{children:"smart contracts in Lisk L2."})]})}),"\n",(0,s.jsxs)(t.p,{children:["To create a new smart contract, create a new file ",(0,s.jsx)(t.code,{children:"Hello.sol"})," under ",(0,s.jsx)(t.code,{children:"src/"})," and add the following content:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// SPDX-License-Identifier: MIT\n// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0\npragma solidity ^0.8.20;\n\ncontract Hello {\n\n}\n"})}),"\n",(0,s.jsxs)(t.p,{children:["Inside the new contract, we will put all the logic that was residing in the Lisk L1 ",(0,s.jsx)(t.code,{children:"Hello"})," module before."]}),"\n",(0,s.jsx)(t.h3,{id:"table-lisk-l1l2-comparison",children:"Table: Lisk L1/L2 comparison"}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Description"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Lisk L1"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Lisk L2"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Onchain business logic"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Module"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/first-app/",children:"Smart contract"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Onchain data storage"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Stores (onchain)"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/variables/",children:"State variables"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Logging to the blockchain"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Blockchain Events"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/events/",children:"Events"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"State-transition logic triggered by a transaction"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Commands"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/function/",children:"Functions"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"API"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Endpoints"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/view-and-pure-functions/",children:"View functions"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Internal API"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Methods"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/function-modifier/",children:"Functions (+ modifiers)"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Logic triggered per block"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Lifecycle Hooks"}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["X",(0,s.jsx)(t.sup,{children:(0,s.jsx)(t.a,{href:"#user-content-fn-1",id:"user-content-fnref-1","data-footnote-ref":!0,"aria-describedby":"footnote-label",children:"1"})})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"storage",children:"Storage"}),"\n",(0,s.jsx)(t.p,{children:"Migrate the onchain stores of a module by implementing corresponding state variables in the contract as shown below."}),"\n",(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"liskl1",label:"Lisk L1",children:(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"message",label:"Message Store",default:!0,children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/stores/message.ts"',children:"import { BaseStore } from 'lisk-sdk';\n\nexport interface MessageStoreData {\n message: string;\n}\n\nexport const messageStoreSchema = {\n $id: '/hello/message',\n type: 'object',\n required: ['message'],\n properties: {\n message: {\n dataType: 'string',\n fieldNumber: 1,\n },\n },\n};\n\nexport class MessageStore extends BaseStore {\n public schema = messageStoreSchema;\n}\n"})})}),(0,s.jsx)(o.Z,{value:"counter",label:"Counter Store",children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/stores/counter.ts"',children:"import { BaseStore } from 'lisk-sdk';\n\nexport interface CounterStoreData {\n counter: number;\n}\n\nexport const counterKey = Buffer.alloc(0);\n\nexport const counterStoreSchema = {\n $id: '/hello/counter',\n type: 'object',\n required: ['counter'],\n properties: {\n counter: {\n dataType: 'uint32',\n fieldNumber: 1,\n },\n },\n};\n\nexport class CounterStore extends BaseStore {\n public schema = counterStoreSchema;\n}\n"})})})]})}),(0,s.jsx)(o.Z,{value:"liskl2",label:"Lisk L2",default:!0,children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// SPDX-License-Identifier: MIT\n// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0\npragma solidity ^0.8.20;\n\ncontract Hello {\n /** State variables */\n // State variable for the Hello messages\n mapping(address => string) public message;\n // State variable for the message counter\n uint32 public counter = 0;\n}\n"})})})]}),"\n",(0,s.jsx)(t.h3,{id:"events",children:"Events"}),"\n",(0,s.jsx)(t.p,{children:"Migrate the blockchain events of a module by implementing corresponding events in the contract as shown below."}),"\n",(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"liskl1",label:"Lisk L1",children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/events/new_hello.ts"',children:"import { BaseEvent } from 'lisk-sdk';\n\nexport const newHelloEventSchema = {\n $id: '/hello/events/new_hello',\n type: 'object',\n required: ['senderAddress', 'message'],\n properties: {\n senderAddress: {\n dataType: 'bytes',\n fieldNumber: 1,\n },\n message: {\n dataType: 'string',\n fieldNumber: 2,\n },\n },\n};\n\nexport interface NewHelloEventData {\n senderAddress: Buffer;\n message: string;\n}\n\nexport class NewHelloEvent extends BaseEvent {\n public schema = newHelloEventSchema;\n}\n"})})}),(0,s.jsx)(o.Z,{value:"liskl2",label:"Lisk L2",default:!0,children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// SPDX-License-Identifier: MIT\n// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0\npragma solidity ^0.8.20;\n\ncontract Hello {\n /** State variables */\n // State variable for the Hello messages\n mapping(address => string) public message;\n // State variable for the message counter\n uint32 public counter = 0;\n\n /** Events */\n // Event for new Hello messages\n event NewHello(address indexed sender, string message);\n}\n"})})})]}),"\n",(0,s.jsx)(t.h3,{id:"state-transition-logic",children:"State transition logic"}),"\n",(0,s.jsxs)(n,{children:[(0,s.jsx)("summary",{children:"Configuration migration"}),(0,s.jsx)(t.h4,{id:"configuration",children:"Configuration"}),(0,s.jsxs)(t.p,{children:["The module-specific configurations, which resided in the ",(0,s.jsx)(t.code,{children:"config.json"})," on Lisk L1, are now part of the smart contract itself and are defined as state variables."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// Blacklist of words that are not allowed in the Hello message\nstring[] public blacklist = ["word1","word2"];\n// Maximum length of the Hello message\nuint32 public maxLength = 200;\n// Minimum length of the Hello message\nuint32 public minlength = 3;\n'})}),(0,s.jsx)(t.p,{children:"To edit the configuration options of the Hello module, we implement the following functions in the Hello contract:"}),(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"setBlacklist()"})," to configure the blacklist of words that are not allowed in the Hello message."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"setMinMaxMessageLength()"})," to configure the minimum and maximum length of the Hello message."]}),"\n"]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// Function to configure the blacklist\nfunction setBlacklist(string[] memory _newBlackList) public onlyOwner {\n blacklist = _newBlackList;\n} \n// Function to configure min/max message length\nfunction setMinMaxMessageLength(uint32 _newMinLength,uint32 _newMaxLength) public onlyOwner {\n minlength = _newMinLength;\n maxLength = _newMaxLength;\n}\n"})}),(0,s.jsx)(t.p,{children:"As seen in the above code snippet, we add the following modifiers to the functions:"}),(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"public"})," to make the function callable from outside the contract.\nThis is a default visibility modifier for functions in Solidity."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"onlyOwner"})," to check that the caller is the owner of the contract.\nThis is a custom modifier that we need to implement in the contract manually, as shown in the example below."]}),"\n"]}),(0,s.jsxs)(t.p,{children:["To set the owner of the contract, we add a new state variable ",(0,s.jsx)(t.code,{children:"owner"}),", and a constructor which sets the ",(0,s.jsx)(t.code,{children:"owner"})," variable to the account address that deploys the contract."]}),(0,s.jsx)(t.admonition,{type:"tip",children:(0,s.jsxs)(t.p,{children:["To update the smart contract owner, you can implement a corresponding function ",(0,s.jsx)(t.code,{children:"setOwner()"}),", and use the ",(0,s.jsx)(t.code,{children:"onlyOwner"})," modifier to ensure that only the current owner can call this function."]})}),(0,s.jsxs)(t.p,{children:["Finally, we can check for the message sender being the owner of the contract in the ",(0,s.jsx)(t.code,{children:"onlyOwner"})," modifier which is used for the ",(0,s.jsx)(t.code,{children:"setBlacklist()"})," and ",(0,s.jsx)(t.code,{children:"setMinMaxMessageLength()"})," functions."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// Address of the contract owner\naddress public immutable owner;\n\nconstructor() {\n // Set the transaction sender as the owner of the contract.\n owner = msg.sender;\n}\n\n/** Modifiers */\n// Modifier to check that the caller is the owner of the contract.\nmodifier onlyOwner() {\n require(msg.sender == owner, "Not owner");\n _;\n}\n'})})]}),"\n",(0,s.jsxs)(n,{children:[(0,s.jsx)("summary",{children:"Verification migration"}),(0,s.jsx)(t.h4,{id:"verification",children:"Verification"}),(0,s.jsx)(t.p,{children:"To verify the Hello message, we implement custom modifiers in the contract."}),(0,s.jsxs)(t.p,{children:["Inside the modifiers, we check the length of the message and if it contains any blacklisted words like it was done in the ",(0,s.jsx)(t.code,{children:"verify()"})," method of the Lisk L1 Hello module."]}),(0,s.jsxs)(t.p,{children:["Conveniently check the length of Hello messages in the ",(0,s.jsx)(t.code,{children:"validLength"})," modifier like this:"]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// Validate message length\nmodifier validLength(string memory _message) {\n require(bytes(_message).length >= minlength, "Message too short");\n require(bytes(_message).length <= maxLength, "Message too long");\n _;\n}\n'})}),(0,s.jsxs)(t.p,{children:["To check if the message contains any blacklisted words, we implement the ",(0,s.jsx)(t.code,{children:"validWords"})," modifier in the contract."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// Validate message content\nmodifier validWords(string memory _message) {\n bytes memory whereBytes = bytes (_message);\n\n for (uint h = 0; h < blacklist.length; h++) {\n bool found = false;\n bytes memory whatBytes = bytes (blacklist[h]);\n for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {\n bool flag = true;\n for (uint j = 0; j < whatBytes.length; j++)\n if (whereBytes [i + j] != whatBytes [j]) {\n flag = false;\n break;\n }\n if (flag) {\n found = true;\n break;\n }\n }\n require (!found, "Message contains blacklisted word");\n }\n _;\n}\n'})})]}),"\n",(0,s.jsxs)(n,{children:[(0,s.jsx)("summary",{children:"Execution migration"}),(0,s.jsx)(t.h4,{id:"execution",children:"Execution"}),(0,s.jsxs)(t.p,{children:["To migrate the createHello command execution, we implement the ",(0,s.jsx)(t.code,{children:"createHello()"})," function in the contract."]}),(0,s.jsxs)(t.p,{children:["Inside this function, we save the message of the sender in the ",(0,s.jsx)(t.code,{children:"message"})," mapping under the sender address."]}),(0,s.jsx)(t.admonition,{type:"tip",children:(0,s.jsxs)(t.p,{children:["The sender address is a ",(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/variables/",children:"global variable"})," in Solidity and can be accessed with ",(0,s.jsx)(t.code,{children:"msg.sender"}),"."]})}),(0,s.jsxs)(t.p,{children:["Additionally, we increment the Hello message counter by ",(0,s.jsx)(t.code,{children:"1"})," and emit the ",(0,s.jsx)(t.code,{children:"NewHello"})," event, like it was done in the ",(0,s.jsx)(t.code,{children:"execute()"})," method of the Lisk L1 Hello module previously."]}),(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"validMessage()"})," modifier that we defined above in the ",(0,s.jsx)(t.a,{href:"/building-on-lisk/migration-guide#verification",children:"Verification"})," section is used to check if the message is valid before the ",(0,s.jsx)(t.code,{children:"createHello()"})," function is executed."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// Function to create a new Hello message\nfunction createHello(string calldata _message) public validMessage(_message) {\n message[msg.sender] = _message;\n counter+=1;\n emit NewHello(msg.sender, _message);\n}\n"})})]}),"\n",(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"liskl1",label:"Lisk L1",children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/commands/create_hello_command.ts"',children:"/* eslint-disable class-methods-use-this */\n\nimport {\n BaseCommand,\n CommandVerifyContext,\n CommandExecuteContext,\n VerificationResult,\n VerifyStatus,\n} from 'lisk-sdk';\nimport { createHelloSchema } from '../schema';\nimport { MessageStore } from '../stores/message';\nimport { counterKey, CounterStore, CounterStoreData } from '../stores/counter';\nimport { ModuleConfig } from '../types';\nimport { NewHelloEvent } from '../events/new_hello';\n\ninterface Params {\n message: string;\n}\n\nexport class CreateHelloCommand extends BaseCommand {\n public schema = createHelloSchema;\n private _blacklist!: string[];\n\n // eslint-disable-next-line @typescript-eslint/require-await\n public async init(config: ModuleConfig): Promise {\n // Set _blacklist to the value of the blacklist defined in the module config\n this._blacklist = config.blacklist;\n // Set the max message length to the value defined in the module config\n this.schema.properties.message.maxLength = config.maxMessageLength;\n // Set the min message length to the value defined in the module config\n this.schema.properties.message.minLength = config.minMessageLength;\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await\n public async verify(context: CommandVerifyContext): Promise {\n let validation: VerificationResult;\n const wordList = context.params.message.split(\" \");\n const found = this._blacklist.filter(value => wordList.includes(value));\n if (found.length > 0) {\n context.logger.info(\"==== FOUND: Message contains a blacklisted word ====\");\n throw new Error(\n `Illegal word in hello message: ${ found.toString()}`\n );\n } else {\n context.logger.info(\"==== NOT FOUND: Message contains no blacklisted words ====\");\n validation = {\n status: VerifyStatus.OK\n };\n }\n return validation;\n }\n\n public async execute(context: CommandExecuteContext): Promise {\n // 1. Get account data of the sender of the Hello transaction.\n const { senderAddress } = context.transaction;\n // 2. Get message and counter stores.\n const messageSubstore = this.stores.get(MessageStore);\n const counterSubstore = this.stores.get(CounterStore);\n\n // 3. Save the Hello message to the message store, using the senderAddress as key, and the message as value.\n await messageSubstore.set(context, senderAddress, {\n message: context.params.message,\n });\n\n // 3. Get the Hello counter from the counter store.\n let helloCounter: CounterStoreData;\n try {\n helloCounter = await counterSubstore.get(context, counterKey);\n } catch (error) {\n helloCounter = {\n counter: 0,\n }\n }\n // 5. Increment the Hello counter +1.\n helloCounter.counter+=1;\n\n // 6. Save the Hello counter to the counter store.\n await counterSubstore.set(context, counterKey, helloCounter);\n\n // 7. Emit a \"New Hello\" event\n const newHelloEvent = this.events.get(NewHelloEvent);\n newHelloEvent.add(context, {\n senderAddress: context.transaction.senderAddress,\n message: context.params.message\n },[context.transaction.senderAddress]);\n }\n}\n"})})}),(0,s.jsx)(o.Z,{value:"liskl2",label:"Lisk L2",default:!0,children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:'// SPDX-License-Identifier: MIT\n// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0\npragma solidity ^0.8.20;\n\ncontract Hello {\n /** State variables */\n // State variable for the Hello messages\n mapping(address => string) public message;\n // State variable for the message counter\n uint32 public counter = 0;\n // Address of the contract owner\n address public immutable owner;\n // Blacklist of words that are not allowed in the Hello message\n string[] public blacklist = ["word1","word2"];\n // Maximum length of the Hello message\n uint32 public maxLength = 200;\n // Minimum length of the Hello message\n uint32 public minlength = 3;\n\n constructor() {\n // Set the transaction sender as the owner of the contract.\n owner = msg.sender;\n }\n\n /** Modifiers */\n // Modifier to check that the caller is the owner of the contract.\n modifier onlyOwner() {\n require(msg.sender == owner, "Not owner");\n _;\n }\n // Validate message length\n modifier validLength(string memory _message) {\n require(bytes(_message).length >= minlength, "Message too short");\n require(bytes(_message).length <= maxLength, "Message too long");\n _;\n }\n // Validate message content\n modifier validWords(string memory _message) {\n bytes memory whereBytes = bytes (_message);\n\n for (uint h = 0; h < blacklist.length; h++) {\n bool found = false;\n bytes memory whatBytes = bytes (blacklist[h]);\n for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {\n bool flag = true;\n for (uint j = 0; j < whatBytes.length; j++)\n if (whereBytes [i + j] != whatBytes [j]) {\n flag = false;\n break;\n }\n if (flag) {\n found = true;\n break;\n }\n }\n require (!found, "Message contains blacklisted word");\n }\n _;\n }\n\n /** Events */\n // Event for new Hello messages\n event NewHello(address indexed sender, string message);\n\n /** Functions */\n // Function to configure the blacklist\n function setBlacklist(string[] memory _newBlackList) public onlyOwner {\n blacklist = _newBlackList;\n } \n // Function to configure min/max message length\n function setMinMaxMessageLength(uint32 _newMinLength,uint32 _newMaxLength) public onlyOwner {\n minlength = _newMinLength;\n maxLength = _newMaxLength;\n }\n // Function to create a new Hello message\n function createHello(string calldata _message) public validLength(_message) validWords(_message) {\n message[msg.sender] = _message;\n counter+=1;\n emit NewHello(msg.sender, _message);\n }\n}\n'})})})]}),"\n",(0,s.jsx)(t.h3,{id:"endpoints",children:"Endpoints"}),"\n",(0,s.jsx)(t.p,{children:"Migrate the module endpoints by implementing corresponding view functions in the contract as shown below."}),"\n",(0,s.jsxs)(l.Z,{children:[(0,s.jsx)(o.Z,{value:"liskl1",label:"Lisk L1",children:(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-typescript",metastring:'title="hello_client/src/app/modules/hello/endpoint.ts"',children:"export class HelloEndpoint extends BaseEndpoint {\n public async getHelloCounter(ctx: ModuleEndpointContext): Promise {\n const counterSubStore = this.stores.get(CounterStore);\n\n const helloCounter = await counterSubStore.get(\n ctx,\n counterKey,\n );\n\n return helloCounter;\n }\n\n public async getHello(ctx: ModuleEndpointContext): Promise {\n const messageSubStore = this.stores.get(MessageStore);\n\n const { address } = ctx.params;\n if (typeof address !== 'string') {\n throw new Error('Parameter address must be a string.');\n }\n cryptography.address.validateLisk32Address(address);\n const helloMessage = await messageSubStore.get(\n ctx,\n cryptography.address.getAddressFromLisk32Address(address),\n );\n return helloMessage;\n }\n}\n"})})}),(0,s.jsxs)(o.Z,{value:"liskl2",label:"Lisk L2",default:!0,children:[(0,s.jsxs)(t.p,{children:["For simple getters, it is sufficient to add the ",(0,s.jsx)(t.code,{children:"public"})," visibility modifier to the state variables.\nPublic state variables can be accessed directly from external parties."]}),(0,s.jsxs)(t.p,{children:["For more complex endpoints, you can implement corresponding ",(0,s.jsx)(t.a,{href:"https://solidity-by-example.org/view-and-pure-functions/",children:"view"})," functions in the contract."]}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/src/Hello.sol"',children:"// State variable for the Hello messages\nmapping(address => string) public message;\n// State variable for the message counter\nuint32 public counter = 0;\n"})})]})]}),"\n",(0,s.jsx)(t.h2,{id:"next-steps",children:"Next steps"}),"\n",(0,s.jsx)(t.p,{children:"Now that we re-implemented the Hello module from Lisk L1 as a smart contract in Lisk L2, it is possible to directly deploy the Hello contract to Lisk L2 and interact with it."}),"\n",(0,s.jsxs)(t.p,{children:["Before deploying the smart contract to Lisk, it is recommended to ",(0,s.jsx)(t.a,{href:"#testing-the-smart-contract",children:"test it locally"})," by writing corresponding tests for the newly created smart contract.\nOnce the smart contract is ",(0,s.jsx)(t.a,{href:"#smart-contract-deployment",children:"deployed"})," to Lisk, you can interact with it by calling its public functions."]}),"\n",(0,s.jsx)(t.p,{children:"Finally, you can migrate the plugins and UI of the Lisk L1 Hello app to be compatible with the new API, to complete the migration process of your Lisk application."}),"\n",(0,s.jsx)(t.h3,{id:"testing-the-smart-contract",children:"Testing the smart contract"}),"\n",(0,s.jsx)(t.p,{children:"By testing the smart contract, you can verify that the smart contract behaves as expected and that it is free of bugs, before deploying it to Lisk."}),"\n",(0,s.jsxs)(t.p,{children:["Foundry provides a testing framework to support you in writing tests for smart contracts.\nSee ",(0,s.jsx)(t.a,{href:"https://book.getfoundry.sh/forge/tests",children:"Tests - Foundry Book"})," for examples and references regarding the testing framework."]}),"\n",(0,s.jsxs)(t.p,{children:["To test the Hello smart contract, create a new file ",(0,s.jsx)(t.code,{children:"Hello.t.sol"})," under ",(0,s.jsx)(t.code,{children:"test/"}),", and add the following content:"]}),"\n",(0,s.jsxs)(n,{children:[(0,s.jsx)("summary",{children:"Hello.t.sol"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-solidity",metastring:'title="hello_liskl2/test/Hello.t.sol"',children:'// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport {Test, console} from "forge-std/Test.sol";\nimport {Hello} from "../src/Hello.sol";\n\ncontract HelloTest is Test {\n Hello public hello;\n address alice = makeAddr("alice");\n event NewHello(address indexed sender, string message);\n\n function setUp() public {\n hello = new Hello();\n }\n\n function test_CreateHello() public {\n string memory message = "Hello World";\n // Expect NewHello event\n vm.expectEmit(true,false,false,false);\n emit NewHello(address(alice), message);\n // Create a new Hello message\n hoax(alice, 100 ether);\n hello.createHello(message);\n // Check the message\n assertEq(hello.message(alice),message);\n // Check if counter = 1\n assertEq(hello.counter(),1);\n }\n\n function test_MinLength() public {\n vm.expectRevert("Message too short");\n hello.createHello("Hi");\n }\n\n function test_MaxLength() public {\n vm.expectRevert("Message too long");\n hello.createHello("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean porta neque eget elit tristique pharetra. Pellentesque tempus sollicitudin tortor, ut tempus diam. Nulla facilisi. Donec at neque sapien.");\n }\n\n function test_Blacklist() public {\n vm.expectRevert("Message contains blacklisted word");\n hello.createHello("Hello word1");\n }\n\n function test_SetBlacklist() public {\n // Create a temporary dynamic array of strings\n string[] memory bl = new string[](3);\n bl[0] = "word1";\n bl[1] = "word3";\n bl[2] = "word4";\n hello.setBlacklist(bl);\n string[] memory getBL = new string[](2);\n getBL[0] = hello.blacklist(0);\n getBL[1] = hello.blacklist(1);\n assertEq(getBL[0], bl[0]);\n assertEq(getBL[1], bl[1]);\n }\n\n function test_SetBlacklistNotOwner() public {\n string[] memory bl = new string[](3);\n bl[0] = "word1";\n bl[1] = "word3";\n bl[2] = "word4";\n vm.expectRevert("Not owner");\n hoax(alice, 100 ether);\n hello.setBlacklist(bl);\n }\n\n function test_SetMinMaxMessageLength() public {\n uint32 newMin = 1;\n uint32 newMax = 500;\n hello.setMinMaxMessageLength(newMin,newMax);\n assertEq(hello.minLength(), newMin);\n assertEq(hello.maxLength(), newMax);\n }\n\n function test_SetMinMaxMessageLengthNotOwner() public {\n uint32 newMin = 1;\n uint32 newMax = 500;\n hoax(alice, 100 ether);\n vm.expectRevert();\n hello.setMinMaxMessageLength(newMin,newMax);\n }\n}\n'})})]}),"\n",(0,s.jsx)(t.p,{children:"To run the tests, execute the following command:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"forge test\n"})}),"\n",(0,s.jsx)(t.p,{children:"The output should look like this:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-text",children:"Running 8 tests for test/Hello.t.sol:HelloTest\n[PASS] test_Blacklist() (gas: 23772)\n[PASS] test_CreateHello() (gas: 66179)\n[PASS] test_MaxLength() (gas: 14179)\n[PASS] test_MinLength() (gas: 13929)\n[PASS] test_SetBlacklist() (gas: 885276)\n[PASS] test_SetBlacklistNotOwner() (gas: 16978)\n[PASS] test_SetMinMaxMessageLength() (gas: 853243)\n[PASS] test_SetMinMaxMessageLengthNotOwner() (gas: 10889)\nTest result: ok. 8 passed; 0 failed; 0 skipped; finished in 3.35ms\n"})}),"\n",(0,s.jsx)(t.h3,{id:"smart-contract-deployment",children:"Smart contract deployment"}),"\n",(0,s.jsx)(t.p,{children:"You can now deploy the smart contract to Lisk.\nFor this example, we will use the Lisk Sepolia network to deploy the Hello contract."}),"\n",(0,s.jsxs)(t.p,{children:["Add the ",(0,s.jsx)(t.code,{children:"--verify"})," flag to the ",(0,s.jsx)(t.code,{children:"forge create"})," command to directly verify the smart contract on BlockScout."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"forge create --rpc-url https://rpc.sepolia-api.lisk.com \\\n--etherscan-api-key 123 \\\n--verify \\\n--verifier blockscout \\\n--verifier-url https://sepolia-blockscout.lisk.com/api \\\n--private-key \\\nsrc/Hello.sol:Hello\n"})}),"\n",(0,s.jsx)(t.p,{children:"If the deployment went successfully, the output should look like this:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-text",children:"[\u2822] Compiling...\nNo files changed, compilation skipped\nDeployer: 0x3C46A11471f285E36EE8d089473ce98269D1b081\nDeployed to: 0x0a5A1C81F278cAe80d340a4A97E2D7B1c3Ec511a\nTransaction hash: 0x52bb6aab8ceeecef674253ecc0ccfe35baeac7db3cc8e889a9da1f7cf1ce0593\nStarting contract verification...\nWaiting for blockscout to detect contract deployment...\nStart verifying contract `0x0a5A1C81F278cAe80d340a4A97E2D7B1c3Ec511a` deployed on 4202\n\nSubmitting verification for [src/Hello.sol:Hello] 0x0a5A1C81F278cAe80d340a4A97E2D7B1c3Ec511a.\nSubmitted contract for verification:\n\tResponse: `OK`\n\tGUID: `0a5a1c81f278cae80d340a4a97e2d7b1c3ec511a65cf6f72`\n\tURL: https://sepolia-blockscout.lisk.com/address/0x0a5a1c81f278cae80d340a4a97e2d7b1c3ec511a\nContract verification status:\nResponse: `OK`\nDetails: `Unknown UID`\n"})}),"\n",(0,s.jsx)(t.p,{children:"After the smart contract is deployed, you can interact with it by calling its public functions."}),"\n",(0,s.jsx)(t.p,{children:"From here, you can migrate the plugins and UI of the Lisk L1 app to be compatible with the new API, to complete the migration process of your Lisk application."}),"\n",(0,s.jsxs)(t.p,{children:["In case you need further assistance, feel free to reach out to the Lisk community at ",(0,s.jsx)(t.a,{href:"https://lisk.chat",children:"Lisk.chat"}),"."]}),"\n",(0,s.jsxs)(t.section,{"data-footnotes":!0,className:"footnotes",children:[(0,s.jsx)(t.h2,{className:"sr-only",id:"footnote-label",children:"Footnotes"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{id:"user-content-fn-1",children:["\n",(0,s.jsxs)(t.p,{children:["No direct equivalent in solidity.\nPlease investigate for custom solutions to migrate logic residing in the lifecycle hooks. ",(0,s.jsx)(t.a,{href:"#user-content-fnref-1","data-footnote-backref":"","aria-label":"Back to reference 1",className:"data-footnote-backref",children:"\u21a9"})]}),"\n"]}),"\n"]}),"\n"]})]})}function m(e={}){const{wrapper:t}={...(0,i.a)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},5162:(e,t,n)=>{n.d(t,{Z:()=>o});n(7294);var s=n(512);const i={tabItem:"tabItem_Ymn6"};var l=n(5893);function o(e){let{children:t,hidden:n,className:o}=e;return(0,l.jsx)("div",{role:"tabpanel",className:(0,s.Z)(i.tabItem,o),hidden:n,children:t})}},4866:(e,t,n)=>{n.d(t,{Z:()=>k});var s=n(7294),i=n(512),l=n(2466),o=n(6550),r=n(469),a=n(1980),c=n(7392),d=n(12);function h(e){return s.Children.toArray(e).filter((e=>"\n"!==e)).map((e=>{if(!e||(0,s.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}function u(e){const{values:t,children:n}=e;return(0,s.useMemo)((()=>{const e=t??function(e){return h(e).map((e=>{let{props:{value:t,label:n,attributes:s,default:i}}=e;return{value:t,label:n,attributes:s,default:i}}))}(n);return function(e){const t=(0,c.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const i=(0,o.k6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,a._X)(l),(0,s.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(i.location.search);t.set(l,e),i.replace({...i.location,search:t.toString()})}),[l,i])]}function p(e){const{defaultValue:t,queryString:n=!1,groupId:i}=e,l=u(e),[o,a]=(0,s.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const s=n.find((e=>e.default))??n[0];if(!s)throw new Error("Unexpected error: 0 tabValues");return s.value}({defaultValue:t,tabValues:l}))),[c,h]=g({queryString:n,groupId:i}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[i,l]=(0,d.Nk)(n);return[i,(0,s.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:i}),x=(()=>{const e=c??p;return m({value:e,tabValues:l})?e:null})();(0,r.Z)((()=>{x&&a(x)}),[x]);return{selectedValue:o,selectValue:(0,s.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);a(e),h(e),f(e)}),[h,f,l]),tabValues:l}}var f=n(2389);const x={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};var b=n(5893);function y(e){let{className:t,block:n,selectedValue:s,selectValue:o,tabValues:r}=e;const a=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.o5)(),d=e=>{const t=e.currentTarget,n=a.indexOf(t),i=r[n].value;i!==s&&(c(t),o(i))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=a.indexOf(e.currentTarget)+1;t=a[n]??a[0];break}case"ArrowLeft":{const n=a.indexOf(e.currentTarget)-1;t=a[n]??a[a.length-1];break}}t?.focus()};return(0,b.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.Z)("tabs",{"tabs--block":n},t),children:r.map((e=>{let{value:t,label:n,attributes:l}=e;return(0,b.jsx)("li",{role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,ref:e=>a.push(e),onKeyDown:h,onClick:d,...l,className:(0,i.Z)("tabs__item",x.tabItem,l?.className,{"tabs__item--active":s===t}),children:n??t},t)}))})}function j(e){let{lazy:t,children:n,selectedValue:i}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===i));return e?(0,s.cloneElement)(e,{className:"margin-top--md"}):null}return(0,b.jsx)("div",{className:"margin-top--md",children:l.map(((e,t)=>(0,s.cloneElement)(e,{key:t,hidden:e.props.value!==i})))})}function w(e){const t=p(e);return(0,b.jsxs)("div",{className:(0,i.Z)("tabs-container",x.tabList),children:[(0,b.jsx)(y,{...e,...t}),(0,b.jsx)(j,{...e,...t})]})}function k(e){const t=(0,f.Z)();return(0,b.jsx)(w,{...e,children:h(e.children)},String(t))}},1151:(e,t,n)=>{n.d(t,{Z:()=>r,a:()=>o});var s=n(7294);const i={},l=s.createContext(i);function o(e){const t=s.useContext(l);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(l.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/6ce766bb.889824c0.js b/assets/js/6ce766bb.8d9037d1.js similarity index 61% rename from assets/js/6ce766bb.889824c0.js rename to assets/js/6ce766bb.8d9037d1.js index ef0823c76..e93226877 100644 --- a/assets/js/6ce766bb.889824c0.js +++ b/assets/js/6ce766bb.8d9037d1.js @@ -1 +1 @@ -"use strict";(self.webpackChunklisk_docs=self.webpackChunklisk_docs||[]).push([[184],{9030:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>d,toc:()=>c});var i=s(5893),r=s(1151);const n={title:"API Providers",slug:"/lisk-tools/api-providers",description:"Documentation for Node Providers for the Lisk network. Including details on their services, supported networks, and pricing plans.",keywords:["Node Providers","Lisk","Lisk network","Lisk node","hosted nodes","archival nodes","RPC","RPC node","RPC URL","RPC endpoints","blockchain services","blockchain infrastructure","developer tools","API","Web3 infrastructure","dRPC","Sepolia"]},o="API Providers",d={id:"lisk-tools/api-providers",title:"API Providers",description:"Documentation for Node Providers for the Lisk network. Including details on their services, supported networks, and pricing plans.",source:"@site/docs/lisk-tools/api-providers.md",sourceDirName:"lisk-tools",slug:"/lisk-tools/api-providers",permalink:"/lisk-documentation/lisk-tools/api-providers",draft:!1,unlisted:!1,editUrl:"https://github.com/LiskHQ/lisk-documentation/tree/main/docs/lisk-tools/api-providers.md",tags:[],version:"current",frontMatter:{title:"API Providers",slug:"/lisk-tools/api-providers",description:"Documentation for Node Providers for the Lisk network. Including details on their services, supported networks, and pricing plans.",keywords:["Node Providers","Lisk","Lisk network","Lisk node","hosted nodes","archival nodes","RPC","RPC node","RPC URL","RPC endpoints","blockchain services","blockchain infrastructure","developer tools","API","Web3 infrastructure","dRPC","Sepolia"]},sidebar:"documentationSidebar",previous:{title:"Bridges",permalink:"/lisk-documentation/lisk-tools/bridges"}},l={},c=[{value:"API reference",id:"api-reference",level:2},{value:"Lisk RPC",id:"lisk-rpc",level:2},{value:"dRPC",id:"drpc",level:2}];function a(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,r.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.h1,{id:"api-providers",children:"API Providers"}),"\n",(0,i.jsx)(t.p,{children:"Lisk nodes expose an RPC API that allows other parties to interact with the blockchain by invoking requests."}),"\n",(0,i.jsxs)(t.p,{children:["If you're just getting started and need an RPC URL, you can use our ",(0,i.jsx)(t.a,{href:"#lisk-rpc",children:"free endpoints"}),".\nIf you're looking to strengthen your app and avoid rate-limiting for your users, please check out our available RPC node providers like ",(0,i.jsx)(t.a,{href:"#drpc",children:"dRPC"}),"."]}),"\n",(0,i.jsx)(t.h2,{id:"api-reference",children:"API reference"}),"\n",(0,i.jsxs)(t.p,{children:["The available endpoints for Lisk nodes include all ",(0,i.jsx)(t.a,{href:"https://geth.ethereum.org/docs/interacting-with-geth/rpc",children:"Geth RPC endpoints"}),", which also include all standard ",(0,i.jsx)(t.a,{href:"https://ethereum.github.io/execution-apis/api-documentation/",children:"JSON-RPC API endpoints"})," of Ethereum."]}),"\n",(0,i.jsx)(t.h2,{id:"lisk-rpc",children:"Lisk RPC"}),"\n",(0,i.jsx)(t.p,{children:"Free, rate limited RPC endpoints for the Lisk networks."}),"\n",(0,i.jsxs)(t.table,{children:[(0,i.jsx)(t.thead,{children:(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.th,{style:{textAlign:"left"}}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Lisk Sepolia Testnet"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Lisk Mainnet"})]})}),(0,i.jsxs)(t.tbody,{children:[(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.strong,{children:"HTTP RPC"})}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.a,{href:"https://rpc.sepolia-api.lisk.com",children:"https://rpc.sepolia-api.lisk.com"})}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.a,{href:"https://rpc.api.lisk.com",children:"https://rpc.api.lisk.com"})})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.strong,{children:"WS RPC"})}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.code,{children:"wss://ws.sepolia-api.lisk.com"})}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.code,{children:"wss://ws.api.lisk.com"})})]})]})]}),"\n",(0,i.jsx)(t.h2,{id:"drpc",children:"dRPC"}),"\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.a,{href:"https://drpc.org/",children:"dRPC"})," is a decentralized Web3 infrastructure provider with a focus on resilience and latency.\ndRPC offers access to a distributed network of public nodes for Lisk.\nThey provide a free tier that allows for an unlimited amount of requests over public nodes, or a paid tier that provides access to all providers, as well as other additional features."]}),"\n",(0,i.jsxs)(t.p,{children:["Check the available endpoints for Lisk directly under ",(0,i.jsx)(t.a,{href:"https://drpc.org/public-endpoints/lisk",children:"https://drpc.org/public-endpoints/lisk"}),"."]})]})}function p(e={}){const{wrapper:t}={...(0,r.a)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(a,{...e})}):a(e)}},1151:(e,t,s)=>{s.d(t,{Z:()=>d,a:()=>o});var i=s(7294);const r={},n=i.createContext(r);function o(e){const t=i.useContext(n);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function d(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),i.createElement(n.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunklisk_docs=self.webpackChunklisk_docs||[]).push([[184],{9030:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>d,toc:()=>c});var i=s(5893),r=s(1151);const n={title:"API Providers",slug:"/lisk-tools/api-providers",description:"Documentation for Node Providers for the Lisk network. Including details on their services, supported networks, and pricing plans.",keywords:["Node Providers","Lisk","Lisk network","Lisk node","hosted nodes","archival nodes","RPC","RPC node","RPC URL","RPC endpoints","blockchain services","blockchain infrastructure","developer tools","API","Web3 infrastructure","dRPC","Sepolia"]},o="API Providers",d={id:"lisk-tools/api-providers",title:"API Providers",description:"Documentation for Node Providers for the Lisk network. Including details on their services, supported networks, and pricing plans.",source:"@site/docs/lisk-tools/api-providers.md",sourceDirName:"lisk-tools",slug:"/lisk-tools/api-providers",permalink:"/lisk-documentation/lisk-tools/api-providers",draft:!1,unlisted:!1,editUrl:"https://github.com/LiskHQ/lisk-documentation/tree/main/docs/lisk-tools/api-providers.md",tags:[],version:"current",frontMatter:{title:"API Providers",slug:"/lisk-tools/api-providers",description:"Documentation for Node Providers for the Lisk network. Including details on their services, supported networks, and pricing plans.",keywords:["Node Providers","Lisk","Lisk network","Lisk node","hosted nodes","archival nodes","RPC","RPC node","RPC URL","RPC endpoints","blockchain services","blockchain infrastructure","developer tools","API","Web3 infrastructure","dRPC","Sepolia"]},sidebar:"documentationSidebar",previous:{title:"Bridges",permalink:"/lisk-documentation/lisk-tools/bridges"}},l={},c=[{value:"API reference",id:"api-reference",level:2},{value:"Lisk RPC",id:"lisk-rpc",level:2},{value:"dRPC",id:"drpc",level:2}];function a(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,r.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.h1,{id:"api-providers",children:"API Providers"}),"\n",(0,i.jsx)(t.p,{children:"Lisk nodes expose an RPC API that allows other parties to interact with the blockchain by invoking requests."}),"\n",(0,i.jsxs)(t.p,{children:["If you're just getting started and need an RPC URL, you can use our ",(0,i.jsx)(t.a,{href:"#lisk-rpc",children:"free endpoints"}),".\nIf you're looking to strengthen your app and avoid rate-limiting for your users, please check out our available RPC node providers like ",(0,i.jsx)(t.a,{href:"#drpc",children:"dRPC"}),"."]}),"\n",(0,i.jsx)(t.h2,{id:"api-reference",children:"API reference"}),"\n",(0,i.jsxs)(t.p,{children:["The available endpoints for Lisk nodes include all ",(0,i.jsx)(t.a,{href:"https://geth.ethereum.org/docs/interacting-with-geth/rpc",children:"Geth RPC endpoints"}),", which also include all standard ",(0,i.jsx)(t.a,{href:"https://ethereum.github.io/execution-apis/api-documentation/",children:"JSON-RPC API endpoints"})," of Ethereum."]}),"\n",(0,i.jsx)(t.h2,{id:"lisk-rpc",children:"Lisk RPC"}),"\n",(0,i.jsx)(t.p,{children:"Free, rate limited RPC endpoints for the Lisk networks."}),"\n",(0,i.jsxs)(t.table,{children:[(0,i.jsx)(t.thead,{children:(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.th,{style:{textAlign:"left"}}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Lisk Sepolia Testnet"})]})}),(0,i.jsxs)(t.tbody,{children:[(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.strong,{children:"HTTP RPC"})}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.a,{href:"https://rpc.sepolia-api.lisk.com",children:"https://rpc.sepolia-api.lisk.com"})})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.strong,{children:"WS RPC"})}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:(0,i.jsx)(t.code,{children:"wss://ws.sepolia-api.lisk.com"})})]})]})]}),"\n",(0,i.jsx)(t.h2,{id:"drpc",children:"dRPC"}),"\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.a,{href:"https://drpc.org/",children:"dRPC"})," is a decentralized Web3 infrastructure provider with a focus on resilience and latency.\ndRPC offers access to a distributed network of public nodes for Lisk.\nThey provide a free tier that allows for an unlimited amount of requests over public nodes, or a paid tier that provides access to all providers, as well as other additional features."]}),"\n",(0,i.jsxs)(t.p,{children:["Check the available endpoints for Lisk directly under ",(0,i.jsx)(t.a,{href:"https://drpc.org/public-endpoints/lisk",children:"https://drpc.org/public-endpoints/lisk"}),"."]})]})}function p(e={}){const{wrapper:t}={...(0,r.a)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(a,{...e})}):a(e)}},1151:(e,t,s)=>{s.d(t,{Z:()=>d,a:()=>o});var i=s(7294);const r={},n=i.createContext(r);function o(e){const t=i.useContext(n);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function d(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),i.createElement(n.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.2f29eef1.js b/assets/js/runtime~main.4b3a4dcb.js similarity index 97% rename from assets/js/runtime~main.2f29eef1.js rename to assets/js/runtime~main.4b3a4dcb.js index 7c54dd07d..404dcba15 100644 --- a/assets/js/runtime~main.2f29eef1.js +++ b/assets/js/runtime~main.4b3a4dcb.js @@ -1 +1 @@ -(()=>{"use strict";var e,t,r,a,o,n={},f={};function c(e){var t=f[e];if(void 0!==t)return t.exports;var r=f[e]={exports:{}};return n[e].call(r.exports,r,r.exports,c),r.exports}c.m=n,e=[],c.O=(t,r,a,o)=>{if(!r){var n=1/0;for(d=0;d=o)&&Object.keys(c.O).every((e=>c.O[e](r[i])))?r.splice(i--,1):(f=!1,o0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[r,a,o]},c.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return c.d(t,{a:t}),t},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,c.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var n={};t=t||[null,r({}),r([]),r(r)];for(var f=2&a&&e;"object"==typeof f&&!~t.indexOf(f);f=r(f))Object.getOwnPropertyNames(f).forEach((t=>n[t]=()=>e[t]));return n.default=()=>e,c.d(o,n),o},c.d=(e,t)=>{for(var r in t)c.o(t,r)&&!c.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},c.f={},c.e=e=>Promise.all(Object.keys(c.f).reduce(((t,r)=>(c.f[r](e,t),t)),[])),c.u=e=>"assets/js/"+({6:"2ef85f89",53:"935f2afb",68:"0eafa1eb",85:"1f391b9e",184:"6ce766bb",240:"90210d68",318:"b2059c63",368:"a94703ab",383:"0480cc7e",388:"1224f3e7",414:"393be207",421:"bcc5765f",444:"6183c0e3",445:"951748bc",462:"f58537f1",518:"a7bd4aaa",568:"dad52d3b",661:"5e95c892",671:"0e384e19",817:"14eb3368",918:"17896441"}[e]||e)+"."+{6:"cbf2faff",53:"27d90acd",68:"06991558",85:"34f79b08",184:"889824c0",240:"dc0f9a3f",318:"31821c9d",368:"5c7c46ba",383:"07513c48",388:"f648b06f",414:"6280be6b",421:"1591268c",444:"dbf63bd7",445:"b1639ec1",462:"81742591",518:"c2569488",568:"77f099aa",661:"15d8fdf1",671:"d4de47e6",674:"5283d329",772:"7b0f19ea",817:"c8cc9442",918:"9169ebd0"}[e]+".js",c.miniCssF=e=>{},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),a={},o="lisk-docs:",c.l=(e,t,r,n)=>{if(a[e])a[e].push(t);else{var f,i;if(void 0!==r)for(var b=document.getElementsByTagName("script"),d=0;d{f.onerror=f.onload=null,clearTimeout(s);var o=a[e];if(delete a[e],f.parentNode&&f.parentNode.removeChild(f),o&&o.forEach((e=>e(r))),t)return t(r)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:f}),12e4);f.onerror=l.bind(null,f.onerror),f.onload=l.bind(null,f.onload),i&&document.head.appendChild(f)}},c.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.p="/lisk-documentation/",c.gca=function(e){return e={17896441:"918","2ef85f89":"6","935f2afb":"53","0eafa1eb":"68","1f391b9e":"85","6ce766bb":"184","90210d68":"240",b2059c63:"318",a94703ab:"368","0480cc7e":"383","1224f3e7":"388","393be207":"414",bcc5765f:"421","6183c0e3":"444","951748bc":"445",f58537f1:"462",a7bd4aaa:"518",dad52d3b:"568","5e95c892":"661","0e384e19":"671","14eb3368":"817"}[e]||e,c.p+c.u(e)},(()=>{var e={303:0,532:0};c.f.j=(t,r)=>{var a=c.o(e,t)?e[t]:void 0;if(0!==a)if(a)r.push(a[2]);else if(/^(303|532)$/.test(t))e[t]=0;else{var o=new Promise(((r,o)=>a=e[t]=[r,o]));r.push(a[2]=o);var n=c.p+c.u(t),f=new Error;c.l(n,(r=>{if(c.o(e,t)&&(0!==(a=e[t])&&(e[t]=void 0),a)){var o=r&&("load"===r.type?"missing":r.type),n=r&&r.target&&r.target.src;f.message="Loading chunk "+t+" failed.\n("+o+": "+n+")",f.name="ChunkLoadError",f.type=o,f.request=n,a[1](f)}}),"chunk-"+t,t)}},c.O.j=t=>0===e[t];var t=(t,r)=>{var a,o,n=r[0],f=r[1],i=r[2],b=0;if(n.some((t=>0!==e[t]))){for(a in f)c.o(f,a)&&(c.m[a]=f[a]);if(i)var d=i(c)}for(t&&t(r);b{"use strict";var e,t,r,a,o,n={},f={};function c(e){var t=f[e];if(void 0!==t)return t.exports;var r=f[e]={exports:{}};return n[e].call(r.exports,r,r.exports,c),r.exports}c.m=n,e=[],c.O=(t,r,a,o)=>{if(!r){var n=1/0;for(d=0;d=o)&&Object.keys(c.O).every((e=>c.O[e](r[i])))?r.splice(i--,1):(f=!1,o0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[r,a,o]},c.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return c.d(t,{a:t}),t},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,c.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var n={};t=t||[null,r({}),r([]),r(r)];for(var f=2&a&&e;"object"==typeof f&&!~t.indexOf(f);f=r(f))Object.getOwnPropertyNames(f).forEach((t=>n[t]=()=>e[t]));return n.default=()=>e,c.d(o,n),o},c.d=(e,t)=>{for(var r in t)c.o(t,r)&&!c.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},c.f={},c.e=e=>Promise.all(Object.keys(c.f).reduce(((t,r)=>(c.f[r](e,t),t)),[])),c.u=e=>"assets/js/"+({6:"2ef85f89",53:"935f2afb",68:"0eafa1eb",85:"1f391b9e",184:"6ce766bb",240:"90210d68",318:"b2059c63",368:"a94703ab",383:"0480cc7e",388:"1224f3e7",414:"393be207",421:"bcc5765f",444:"6183c0e3",445:"951748bc",462:"f58537f1",518:"a7bd4aaa",568:"dad52d3b",661:"5e95c892",671:"0e384e19",817:"14eb3368",918:"17896441"}[e]||e)+"."+{6:"cbf2faff",53:"27d90acd",68:"06991558",85:"34f79b08",184:"8d9037d1",240:"dc0f9a3f",318:"31821c9d",368:"5c7c46ba",383:"07513c48",388:"f648b06f",414:"6280be6b",421:"1591268c",444:"dc482ac8",445:"b1639ec1",462:"81742591",518:"c2569488",568:"77f099aa",661:"15d8fdf1",671:"d4de47e6",674:"5283d329",772:"7b0f19ea",817:"c8cc9442",918:"9169ebd0"}[e]+".js",c.miniCssF=e=>{},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),a={},o="lisk-docs:",c.l=(e,t,r,n)=>{if(a[e])a[e].push(t);else{var f,i;if(void 0!==r)for(var b=document.getElementsByTagName("script"),d=0;d{f.onerror=f.onload=null,clearTimeout(s);var o=a[e];if(delete a[e],f.parentNode&&f.parentNode.removeChild(f),o&&o.forEach((e=>e(r))),t)return t(r)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:f}),12e4);f.onerror=l.bind(null,f.onerror),f.onload=l.bind(null,f.onload),i&&document.head.appendChild(f)}},c.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.p="/lisk-documentation/",c.gca=function(e){return e={17896441:"918","2ef85f89":"6","935f2afb":"53","0eafa1eb":"68","1f391b9e":"85","6ce766bb":"184","90210d68":"240",b2059c63:"318",a94703ab:"368","0480cc7e":"383","1224f3e7":"388","393be207":"414",bcc5765f:"421","6183c0e3":"444","951748bc":"445",f58537f1:"462",a7bd4aaa:"518",dad52d3b:"568","5e95c892":"661","0e384e19":"671","14eb3368":"817"}[e]||e,c.p+c.u(e)},(()=>{var e={303:0,532:0};c.f.j=(t,r)=>{var a=c.o(e,t)?e[t]:void 0;if(0!==a)if(a)r.push(a[2]);else if(/^(303|532)$/.test(t))e[t]=0;else{var o=new Promise(((r,o)=>a=e[t]=[r,o]));r.push(a[2]=o);var n=c.p+c.u(t),f=new Error;c.l(n,(r=>{if(c.o(e,t)&&(0!==(a=e[t])&&(e[t]=void 0),a)){var o=r&&("load"===r.type?"missing":r.type),n=r&&r.target&&r.target.src;f.message="Loading chunk "+t+" failed.\n("+o+": "+n+")",f.name="ChunkLoadError",f.type=o,f.request=n,a[1](f)}}),"chunk-"+t,t)}},c.O.j=t=>0===e[t];var t=(t,r)=>{var a,o,n=r[0],f=r[1],i=r[2],b=0;if(n.some((t=>0!==e[t]))){for(a in f)c.o(f,a)&&(c.m[a]=f[a]);if(i)var d=i(c)}for(t&&t(r);b Deploying a smart contract with Hardhat | Lisk Documentation - + diff --git a/building-on-lisk/migration-guide.html b/building-on-lisk/migration-guide.html index 4e93879b3..4e55acd08 100644 --- a/building-on-lisk/migration-guide.html +++ b/building-on-lisk/migration-guide.html @@ -4,7 +4,7 @@ Lisk L1->L2 migration guide | Lisk Documentation - + @@ -41,7 +41,7 @@

Events
hello_liskl2/src/Hello.sol
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.20;

contract Hello {
/** State variables */
// State variable for the Hello messages
mapping(address => string) public message;
// State variable for the message counter
uint32 public counter = 0;

/** Events */
// Event for new Hello messages
event NewHello(address indexed sender, string message);
}

State transition logic

-
Configuration migration

Configuration

The module-specific configurations, which resided in the config.json on Lisk L1, are now part of the smart contract itself, and defined as state variables.

hello_liskl2/src/Hello.sol
// Blacklist of words that are not allowed in the Hello message
string[] public blacklist = ["word1","word2"];
// Maximum length of the Hello message
uint32 public maxLength = 200;
// Minimum length of the Hello message
uint32 public minlength = 3

To edit the configuration options of the Hello module, we implement the following functions in the Hello contract:

    +
    Configuration migration

    Configuration

    The module-specific configurations, which resided in the config.json on Lisk L1, are now part of the smart contract itself and are defined as state variables.

    hello_liskl2/src/Hello.sol
    // Blacklist of words that are not allowed in the Hello message
    string[] public blacklist = ["word1","word2"];
    // Maximum length of the Hello message
    uint32 public maxLength = 200;
    // Minimum length of the Hello message
    uint32 public minlength = 3;

    To edit the configuration options of the Hello module, we implement the following functions in the Hello contract:

    • setBlacklist() to configure the blacklist of words that are not allowed in the Hello message.
    • setMinMaxMessageLength() to configure the minimum and maximum length of the Hello message.
    hello_liskl2/src/Hello.sol
    // Function to configure the blacklist
    function setBlacklist(string[] memory _newBlackList) public onlyOwner {
    blacklist = _newBlackList;
    }
    // Function to configure min/max message length
    function setMinMaxMessageLength(uint32 _newMinLength,uint32 _newMaxLength) public onlyOwner {
    minlength = _newMinLength;
    maxLength = _newMaxLength;
    }

    As seen in the above code snippet, we add the following modifiers to the functions:

      @@ -49,14 +49,14 @@

      State This is a default visibility modifier for functions in Solidity.
    • onlyOwner to check that the caller is the owner of the contract. This is a custom modifier that we need to implement in the contract manually, as shown in the example below.
    • -

    To set the owner of the contract, we add a new state variable owner and a constructor which sets the owner variable to the account address that deploys the contract.

    tip

    For updating the owner of the smart contract, you can imeplemnt a corresponding function setOwner() and use the onlyOwner modifier to ensure that only the current owner can call this function.

    Finally, we can check for the message sender being the owner of the contract in the onlyOwner modifier which is used for the setBlacklist() and setMinMaxMessageLength() functions.

    hello_liskl2/src/Hello.sol
    // Address of the contract owner
    address public immutable owner;

    constructor() {
    // Set the transaction sender as the owner of the contract.
    owner = msg.sender;
    }

    /** Modifiers */
    // Modifier to check that the caller is the owner of the contract.
    modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _;
    }
    -
    Verification migration

    Verification

    To verify the Hello message, we implement custom modifiers in the contract.

    Inside of the modifiers, we check the length of the message and if it contains any blacklisted words, like it was done in the verify() method of the Lisk L1 Hello module.

    Conveniently check the length of Hello messages in the validLength modifier like this:

    hello_liskl2/src/Hello.sol
    // Validate message length
    modifier validLength(string memory _message) {
    require(bytes(_message).length >= minlength, "Message too short");
    require(bytes(_message).length <= maxLength, "Message too long");
    _;
    }

    To check if the message contains any blacklisted words, we implement the validWords modifier in the contract.

    hello_liskl2/src/Hello.sol
    // Validate message content
    modifier validWords(string memory _message) {
    bytes memory whereBytes = bytes (_message);

    for (uint h = 0; h < blacklist.length; h++) {
    bool found = false;
    bytes memory whatBytes = bytes (blacklist[h]);
    for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {
    bool flag = true;
    for (uint j = 0; j < whatBytes.length; j++)
    if (whereBytes [i + j] != whatBytes [j]) {
    flag = false;
    break;
    }
    if (flag) {
    found = true;
    break;
    }
    }
    require (!found, "Message contains blacklisted word");
    }
    _;
    }
    -
    Execution migration

    Execution

    To migrate the createHello command execution, we implement the createHello() function in the contract.

    Inside of this function, we save the message of the sender in the message mapping under the sender address.

    tip

    The sender address is a global variable in Solidity and can be accessed with msg.sender.

    Additionally, we increment the Hello message counter by +1, and emit the NewHello event, like it was done in the execute() method of the Lisk L1 Hello module previously.

    The validMessage() modifier the we defined above in the Verification section is used to check if the message is valid, before createHello() function is executed.

    hello_liskl2/src/Hello.sol
    // Function to create a new Hello message
    function createHello(string calldata _message) public validMessage(_message) {
    message[msg.sender] = _message;
    counter+=1;
    emit NewHello(msg.sender, _message);
    }
    +

To set the owner of the contract, we add a new state variable owner, and a constructor which sets the owner variable to the account address that deploys the contract.

tip

To update the smart contract owner, you can implement a corresponding function setOwner(), and use the onlyOwner modifier to ensure that only the current owner can call this function.

Finally, we can check for the message sender being the owner of the contract in the onlyOwner modifier which is used for the setBlacklist() and setMinMaxMessageLength() functions.

hello_liskl2/src/Hello.sol
// Address of the contract owner
address public immutable owner;

constructor() {
// Set the transaction sender as the owner of the contract.
owner = msg.sender;
}

/** Modifiers */
// Modifier to check that the caller is the owner of the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
+
Verification migration

Verification

To verify the Hello message, we implement custom modifiers in the contract.

Inside the modifiers, we check the length of the message and if it contains any blacklisted words like it was done in the verify() method of the Lisk L1 Hello module.

Conveniently check the length of Hello messages in the validLength modifier like this:

hello_liskl2/src/Hello.sol
// Validate message length
modifier validLength(string memory _message) {
require(bytes(_message).length >= minlength, "Message too short");
require(bytes(_message).length <= maxLength, "Message too long");
_;
}

To check if the message contains any blacklisted words, we implement the validWords modifier in the contract.

hello_liskl2/src/Hello.sol
// Validate message content
modifier validWords(string memory _message) {
bytes memory whereBytes = bytes (_message);

for (uint h = 0; h < blacklist.length; h++) {
bool found = false;
bytes memory whatBytes = bytes (blacklist[h]);
for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {
bool flag = true;
for (uint j = 0; j < whatBytes.length; j++)
if (whereBytes [i + j] != whatBytes [j]) {
flag = false;
break;
}
if (flag) {
found = true;
break;
}
}
require (!found, "Message contains blacklisted word");
}
_;
}
+
Execution migration

Execution

To migrate the createHello command execution, we implement the createHello() function in the contract.

Inside this function, we save the message of the sender in the message mapping under the sender address.

tip

The sender address is a global variable in Solidity and can be accessed with msg.sender.

Additionally, we increment the Hello message counter by 1 and emit the NewHello event, like it was done in the execute() method of the Lisk L1 Hello module previously.

The validMessage() modifier that we defined above in the Verification section is used to check if the message is valid before the createHello() function is executed.

hello_liskl2/src/Hello.sol
// Function to create a new Hello message
function createHello(string calldata _message) public validMessage(_message) {
message[msg.sender] = _message;
counter+=1;
emit NewHello(msg.sender, _message);
}
hello_liskl2/src/Hello.sol
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.20;

contract Hello {
/** State variables */
// State variable for the Hello messages
mapping(address => string) public message;
// State variable for the message counter
uint32 public counter = 0;
// Address of the contract owner
address public immutable owner;
// Blacklist of words that are not allowed in the Hello message
string[] public blacklist = ["word1","word2"];
// Maximum length of the Hello message
uint32 public maxLength = 200;
// Minimum length of the Hello message
uint32 public minlength = 3;

constructor() {
// Set the transaction sender as the owner of the contract.
owner = msg.sender;
}

/** Modifiers */
// Modifier to check that the caller is the owner of the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// Validate message length
modifier validLength(string memory _message) {
require(bytes(_message).length >= minlength, "Message too short");
require(bytes(_message).length <= maxLength, "Message too long");
_;
}
// Validate message content
modifier validWords(string memory _message) {
bytes memory whereBytes = bytes (_message);

for (uint h = 0; h < blacklist.length; h++) {
bool found = false;
bytes memory whatBytes = bytes (blacklist[h]);
for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {
bool flag = true;
for (uint j = 0; j < whatBytes.length; j++)
if (whereBytes [i + j] != whatBytes [j]) {
flag = false;
break;
}
if (flag) {
found = true;
break;
}
}
require (!found, "Message contains blacklisted word");
}
_;
}

/** Events */
// Event for new Hello messages
event NewHello(address indexed sender, string message);

/** Functions */
// Function to configure the blacklist
function setBlacklist(string[] memory _newBlackList) public onlyOwner {
blacklist = _newBlackList;
}
// Function to configure min/max message length
function setMinMaxMessageLength(uint32 _newMinLength,uint32 _newMaxLength) public onlyOwner {
minlength = _newMinLength;
maxLength = _newMaxLength;
}
// Function to create a new Hello message
function createHello(string calldata _message) public validLength(_message) validWords(_message) {
message[msg.sender] = _message;
counter+=1;
emit NewHello(msg.sender, _message);
}
}

Endpoints

Migrate the module endpoints by implementing corresponding view functions in the contract as shown below.

For simple getters, it is sufficient to add the public visibility modifier to the state variables. -Public state variables can be accessed directly from external parties.

For more complex endpoints, you can implement corresponding view functions in the contract.

hello_liskl2/src/Hello.sol
// State variable for the Hello messages
mapping(address => string) public message;
// State variable for the message counter
uint32 public counter = 0;
+Public state variables can be accessed directly from external parties.

For more complex endpoints, you can implement corresponding view functions in the contract.

hello_liskl2/src/Hello.sol
// State variable for the Hello messages
mapping(address => string) public message;
// State variable for the message counter
uint32 public counter = 0;

Next steps

Now that we re-implemented the Hello module from Lisk L1 as a smart contract in Lisk L2, it is possible to directly deploy the Hello contract to Lisk L2 and interact with it.

Before deploying the smart contract to Lisk, it is recommended to test it locally by writing corresponding tests for the newly created smart contract. @@ -66,7 +66,7 @@

T

By testing the smart contract, you can verify that the smart contract behaves as expected and that it is free of bugs, before deploying it to Lisk.

Foundry provides a testing framework to support you in writing tests for smart contracts. See Tests - Foundry Book for examples and references regarding the testing framework.

-

To test the Hello smart contract, create a new file Hello.t.sol under test/ and add the following content:

+

To test the Hello smart contract, create a new file Hello.t.sol under test/, and add the following content:

Hello.t.sol
hello_liskl2/test/Hello.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Test, console} from "forge-std/Test.sol";
import {Hello} from "../src/Hello.sol";

contract HelloTest is Test {
Hello public hello;
address alice = makeAddr("alice");
event NewHello(address indexed sender, string message);

function setUp() public {
hello = new Hello();
}

function test_CreateHello() public {
string memory message = "Hello World";
// Expect NewHello event
vm.expectEmit(true,false,false,false);
emit NewHello(address(alice), message);
// Create a new Hello message
hoax(alice, 100 ether);
hello.createHello(message);
// Check the message
assertEq(hello.message(alice),message);
// Check if counter = 1
assertEq(hello.counter(),1);
}

function test_MinLength() public {
vm.expectRevert("Message too short");
hello.createHello("Hi");
}

function test_MaxLength() public {
vm.expectRevert("Message too long");
hello.createHello("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean porta neque eget elit tristique pharetra. Pellentesque tempus sollicitudin tortor, ut tempus diam. Nulla facilisi. Donec at neque sapien.");
}

function test_Blacklist() public {
vm.expectRevert("Message contains blacklisted word");
hello.createHello("Hello word1");
}

function test_SetBlacklist() public {
// Create a temporary dynamic array of strings
string[] memory bl = new string[](3);
bl[0] = "word1";
bl[1] = "word3";
bl[2] = "word4";
hello.setBlacklist(bl);
string[] memory getBL = new string[](2);
getBL[0] = hello.blacklist(0);
getBL[1] = hello.blacklist(1);
assertEq(getBL[0], bl[0]);
assertEq(getBL[1], bl[1]);
}

function test_SetBlacklistNotOwner() public {
string[] memory bl = new string[](3);
bl[0] = "word1";
bl[1] = "word3";
bl[2] = "word4";
vm.expectRevert("Not owner");
hoax(alice, 100 ether);
hello.setBlacklist(bl);
}

function test_SetMinMaxMessageLength() public {
uint32 newMin = 1;
uint32 newMax = 500;
hello.setMinMaxMessageLength(newMin,newMax);
assertEq(hello.minLength(), newMin);
assertEq(hello.maxLength(), newMax);
}

function test_SetMinMaxMessageLengthNotOwner() public {
uint32 newMin = 1;
uint32 newMax = 500;
hoax(alice, 100 ether);
vm.expectRevert();
hello.setMinMaxMessageLength(newMin,newMax);
}
}

To run the tests, execute the following command:

forge test
diff --git a/category/building-on-lisk.html b/category/building-on-lisk.html index 72602f36c..66f6e2c1e 100644 --- a/category/building-on-lisk.html +++ b/category/building-on-lisk.html @@ -4,7 +4,7 @@ Building on Lisk | Lisk Documentation - + diff --git a/connecting-to-a-wallet.html b/connecting-to-a-wallet.html index f6f16ba09..c6d1ac1a1 100644 --- a/connecting-to-a-wallet.html +++ b/connecting-to-a-wallet.html @@ -4,7 +4,7 @@ Connecting to a wallet | Lisk Documentation - + diff --git a/contracts.html b/contracts.html index ca5cac97b..860c4c9fd 100644 --- a/contracts.html +++ b/contracts.html @@ -4,7 +4,7 @@ Contracts | Lisk Documentation - + diff --git a/fees.html b/fees.html index 3e212f768..8782f672d 100644 --- a/fees.html +++ b/fees.html @@ -4,7 +4,7 @@ Fees | Lisk Documentation - + diff --git a/index.html b/index.html index 88d2c5820..0c52d680f 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ Introduction to Lisk | Lisk Documentation - + diff --git a/lisk-tools/api-providers.html b/lisk-tools/api-providers.html index 54c9c0244..1b3df8058 100644 --- a/lisk-tools/api-providers.html +++ b/lisk-tools/api-providers.html @@ -4,7 +4,7 @@ API Providers | Lisk Documentation - + @@ -19,7 +19,7 @@

API reference<

The available endpoints for Lisk nodes include all Geth RPC endpoints, which also include all standard JSON-RPC API endpoints of Ethereum.

Lisk RPC

Free, rate limited RPC endpoints for the Lisk networks.

-
Lisk Sepolia TestnetLisk Mainnet
HTTP RPChttps://rpc.sepolia-api.lisk.comhttps://rpc.api.lisk.com
WS RPCwss://ws.sepolia-api.lisk.comwss://ws.api.lisk.com
+
Lisk Sepolia Testnet
HTTP RPChttps://rpc.sepolia-api.lisk.com
WS RPCwss://ws.sepolia-api.lisk.com

dRPC

dRPC is a decentralized Web3 infrastructure provider with a focus on resilience and latency. dRPC offers access to a distributed network of public nodes for Lisk. diff --git a/lisk-tools/bridges.html b/lisk-tools/bridges.html index b8609b3cd..f90a5de2f 100644 --- a/lisk-tools/bridges.html +++ b/lisk-tools/bridges.html @@ -4,7 +4,7 @@ Bridges | Lisk Documentation - + diff --git a/markdown-page.html b/markdown-page.html index ce8825b4d..894a23536 100644 --- a/markdown-page.html +++ b/markdown-page.html @@ -4,7 +4,7 @@ Markdown page example | Lisk Documentation - + diff --git a/network-info.html b/network-info.html index 949d11d9b..0b1cfb133 100644 --- a/network-info.html +++ b/network-info.html @@ -4,7 +4,7 @@ Network information, official explorers & faucets | Lisk Documentation - +