- A+
程总结:
- 我们将创建我们的第一个“Hello World”智能合约程序。
- 了解如何初始化程序并定义消息处理的入口函数。
- 通过限制程序可以接收的消息类型并根据消息为不同的帐户创建不同的问候语来增加程序的复杂性。
- 探索测试智能合约以确保它们按预期工作。
- 了解如何读取程序的状态以检索重要信息。
课程目标:
在课程结束时,我们将拥有:
- 定义了创建名为“Hello World”的简单智能合约程序所涉及的步骤。
- 解释了初始化智能合约和定义消息处理入口函数的过程。
- 确定了限制一个智能合约可以接收的消息类型的不同方式,以及如何根据收到的消息为不同的帐户创建不同的问候语。
- 演示了如何测试智能合约程序以确保其正常运行。
- 描述了读取智能合约状态以检索重要信息的过程。
- 总结了开发功能性智能合约程序所需的关键概念和构建区块。
让我们开始构建第一个程序吧!
让我们实现发送问候语以响应任何接收到的消息的程序。
首先,我们将使用以下命令创建一个新项目:
cargo new hello-world --lib
这将为我们的项目创建一个目录结构,其中包含以下文件:
└── hello-world
├── Cargo.toml
└── src
└── lib.rs
接下来,我们需要将必要的依赖项添加到我们的 Cargo.toml 文件中。我们将使用
- gstd - Gear 智能合约的标准库
- gtest - 用于测试智能合约的库
- gear-wasm-builder - 一个帮助使用 Gear 构建程序的模块。
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[dependencies]
gstd = { git = "https://github.com/gear-tech/gear.git", features = ["debug"] }
[build-dependencies]
gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git" }
[dev-dependencies]
gtest = { git = "https://github.com/gear-tech/gear.git" }
现在,让我们在 lib.rs 文件中编写 Gear 程序的最小结构。handle 函数是程序的入口函数。每次程序收到传入消息时都会调用该函数。
#![no_std]
use gstd::{msg, prelude::*};
#[no_mangle]
unsafe extern "C" fn handle() {}
为了构建我们的程序,我们将使用以下代码创建 build.rs 文件:
fn main() {
gear_wasm_builder::build();
}
现在可以运行以下命令来构建我们的项目:
cargo build --release
gstd::msg 是 gstd 库中的消息传递模块,允许用户处理传入的消息,获取有关发件人或消息内容的必要信息,并向其他 actor 发送回复或新消息(链接到 gstd)。
我们将使用发送新消息的 reply 函数作为对当前正在处理的消息的回复:
#![no_std]
use gstd::{msg, prelude::*};
#[no_mangle]
unsafe extern "C" fn handle() {
msg::reply(String::from("Hello"), 0).expect("Error in sending a reply message");
}
让我们开始构建项目:
cargo build --release
如果一切顺利,你的工作目录现在应该有一个类似于以下内容的目标目录:
target
├── release
│ └── ...
└── wasm32-unknown-unknown
└── release
├── ...
├── hello_world.wasm <---- this is our built .wasm file
├── hello_world.opt.wasm <---- this is optimized .wasm file
└── hello_world.meta.wasm <---- this is meta .wasm file
target/wasm32-unknown-unknown/release 目录应该包含三个 WASM 二进制文件:
- hello_world.wasm 是从源文件构建的 output WASM 二进制文件;
- hello_world.opt.wasm 是优化后的 WASM,旨在上传到区块链;
- hello_world.meta.wasm 是包含与程序交互所需的 meta 信息的 WASM。
在我们将程序上传到区块链之前,我们需要了解 metadata 和 *.meta.wasm 文件的用例。在 Gear 程序中,metadata 促进了客户端 (JavaScript) 和程序 (Rust) 之间的交互。
元数据可用作与网络中的 Gear 程序交互的外部工具和应用程序的消息负载描述。它存储在一个单独的 *.meta.wasm 文件中。
Gear metadata! 宏从 Rust 中导出了用户在宏中指定的 IO 数据函数。 在我们的示例中,我们只需要为 handle 输出消息声明一个类型:
gstd::metadata! {
title: "Hello world contract",
handle:
output: String,
}
我们已经学习了如何创建一个简单的智能合约程序,该程序以“Hello”消息响应任何传入消息。让我们测试一下我们的程序。
使用 gtest 库测试智能合约
测试智能合约是开发去中心化应用程序的一个重要方面。我们将使用 Gear gtest 库进行程序的逻辑测试。
首先,让我们在项目目录的顶层,“src”目录旁边创建一个名为“tests”的新目录。在该目录中,我们将创建一个 hello_world_test.rs 文件,我们将在其中为合约编写测试。
mkdir tests
touch hello_world_test.rs
在我们的测试文件中,我们需要从 gtest 库中导入必要的模块,即 import Log、Program 和 System。我们还将定义一个测试函数:
use gtest::{Log, Program, System};
#[test]
fn hello_test() {}
在测试我们的智能合约之前,我们需要初始化运行程序的环境。我们可以使用 gtest 的系统模块来做到这一点。系统模拟节点行为:
let sys = System::new();
接下来,我们需要初始化我们的程序。我们可以使用 gtest 的 Program 模块来做到这一点。初始化程序有两种方法:从文件或当前程序:
从文件初始化程序:
let program = Program::from_file(&sys,
"./target/wasm32-unknown-unknown/release/hello_world.wasm");
从当前程序初始化程序:
let program = Program::current(&sys);
上传的程序有自己的 id。您可以使用 from_file_with_id 构造函数手动指定程序 ID。如果不指定程序 id,第一个初始化程序的 id 为 0x01000...,下一个没有指定 id 的初始化程序 id 为 0x02000...,依此类推。
在下一步中,我们将向程序发送消息。
- 要向程序发送消息,请调用两个程序函数之一:send() 或 send_bytes()。它们之间的区别类似于 gstd 函数 msg::send 和 msg::send_bytes。
- 这些函数中的第一个参数是发送者 ID,第二个参数是消息负载。
- 发件人 ID 可以指定为十六进制、数组 ([u8, 32])、字符串或 u64。但是,你不能从程序已占用的 id 发送消息!
- 即使程序没有 init 函数,初始化程序结构的第一条消息也始终是 init 消息。在我们的范例中,它可以是任何消息。但是让我们将 init 函数添加到我们的程序中并监控该消息是否到达程序:
#![no_std]
use gstd::{msg, prelude::*, debug};
#[no_mangle]
unsafe extern "C" fn handle() {
msg::reply("Hello", 0).expect("Error in sending a reply message");
}
#[no_mangle]
unsafe extern "C" fn init() {
let init_message: String = msg::load().expect("Can't load init message");
debug!("Program was initialized with message {:?}", init_message);
}
在我们的测试函数中,我们可以使用以下函数向程序发送消息 send()
函数:
#[test]
fn hello_test() {
let sys = System::new();
sys.init_logger();
let program = Program::current(&sys);
program.send(2, String::from("INIT MESSAGE"));
}
请注意,我们添加了 sys.init_logger() 以将打印日志初始化到 stdout,并且我们发送了一条来自 ID 2 的用户的消息(ID 2 转换为 0x020000.. ActorId)。
然后我们可以使用以下命令运行测试:
cargo test --release
如果一切正常,我们会在控制台中看到调试消息:
[DEBUG hello_test] Program was initialized with message "INIT MESSAGE"
test hello_test ... ok
gtest 库中的 Sending 函数将返回 RunResult 结构。它包含处理消息的最终结果和其他在执行期间创建的消息。
比如我们可以查看 init 消息处理结果。我们可以通过确保日志为空并且程序不回复或发送任何消息来做到这一点。为此,我们可以使用 assert!(res.log().is_empty()) 命令。
- 包含空日志(程序不回复也不发送任何消息);
assert!(res.log().is_empty());
- 成功:
assert!(!res.main_failed());
一旦我们确认初始化消息成功,接下来的消息将通过句柄函数处理。我们可以通过使用 program.send(2, String::from("HANDLE MESSAGE"))
命令发送下一条消息来测试这一点。
let res = program.send(2, String::from("HANDLE MESSAGE"));
在这里,我们应该检查程序是否回复了预期的问候消息。为此,我们可以使用 gtest 库中的日志结构并构建我们期望接收的日志。具体来说,我们可以使用 Log::builder().dest(2).payload(String::from("Hello")) 命令来创建预期的日志。
创建预期日志后,我们可以检查接收到的日志是否包含预期日志。我们可以使用 assert!(res.contains(&expected_log)) 命令来做到这一点。
let expected_log = Log::builder().dest(2).payload(String::from("Hello"));
assert!(res.contains(&expected_log));
您可能会猜到,dest 是程序向其发送消息的参与者,有效负载是该消息的内容。
运行测试并确保一切正常。
第 1 课测验:测试你的理解力
- 考虑以下程序:
#![no_std]
use gstd::{msg, prelude::*, debug};
#[no_mangle]
unsafe extern "C" fn handle() {
msg::reply("Hello", 0).expect("Error in sending a reply message");
}
我们将使用以下测试套件测试该程序:
#[test]
fn hello_test() {
let sys = System::new();
let program = Program::current(&sys);
let res = program.send(2, String::from("Hello"));
let expected_log = Log::builder().dest(2);
assert!(res.contains(&expected_log));
}
运行该测试的结果是什么?
- 一切顺利:测试会通过;
- 测试将失败,因为初始化程序结构的第一条消息始终是初始化消息。
- 测试将失败,因为 expected_log 不包含带有“Hello”的有效负载。
- 编写该测试的错误是什么?
#[test]
fn hello_test() {
let sys = System::new();
let program_1 = Program::current(&sys);
let program_2 = Program::current(&sys);
let res = program_2.send(2, String::from("Hello"));
assert!(res.log().is_empty());
}
- 第一条消息应该发送给第一个初始化的程序;
- 你不能初始化两个相同的程序;
- 发给 program_2 的消息是从程序已经占用的地址发出的。
- 从 gtest 库返回 fn send() 的结果是什么?
- 它返回包含程序执行结果的 RunResult 结构;
- 它返回 Log 结构,其中包含有关程序执行期间发送的消息的源、目标和有效负载的信息;
- 它返回发送回发件人的消息。
高级 Hello World 程序概念
让我们通过引入两条新消息来为我们的程序添加更多功能:SendHelloTo 和 SendHelloReply。
我们的程序将收到 2 条消息:
- SendHelloTo:收到这条消息后,程序将向指定地址发送“hello”;
- SendHelloReply:程序用“hello”消息回复发送当前消息的帐户。
正如我们在上一课中看到的,我们必须对程序收到的消息进行解码。我们将定义一个用于解码传入消息的枚举 InputMessages。
#[derive(Encode, Decode, TypeInfo)]
enum InputMessages {
SendHelloTo(ActorId),
SendHelloReply,
}
SendHelloTo 变体包含一个 ActorId 字段,程序将在其中发送问候消息。
我们还需要在枚举中添加派生宏#[derive(Encode, Decode,TypeInfo)],用于消息中的编码和解码,并在 Cargo.toml 文件中添加适当的依赖项:
codec = { package = "parity-scale-codec", version = "3.1.2", default-features = false, features = ["derive", "full"] }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
为了初始化我们的程序,我们将定义一个静态可变变量 GREETING 作为一个 Option .
static mut GREETING: Option<String> = None;
在程序初始化之前,GREETING 等于 None。初始化后,GREETING 会变成 Some(String)。
#[no_mangle]
unsafe extern "C" fn init() {
let greeting = String::from_utf8(msg::load_bytes().expect("Can't load init message"))
.expect("Invalid message");
debug!("Program was initialized with message {:?}", greeting);
GREETING = Some(greeting);
}
接下来,我们将解码 handle 函数中的传入消息并定义程序接收到的消息:
#[no_mangle]
unsafe extern "C" fn handle() {
let input_message: InputMessages = msg::load().expect("Error in loading InputMessages");
let greeting = GREETING.get_or_insert(Default::default());
match input_message {
InputMessages::SendHelloTo(account) => {
debug!("Message: SendHelloTo {:?}", account);
msg::send(account, greeting, 0)
.expect("Error in sending Hello message to account");
}
InputMessages::SendHelloReply => {
debug!("Message: SendHelloReply");
msg::reply(greeting, 0).expect("Error in sending reply");
}
}
}
程序收到 SendHelloTo 消息后,通过 send 函数向指定账号发送 hello 消息。另一方面,当合约收到 SendHelloReply 消息时,它会回复一条问候消息。
测试更新后的智能合约
首先,我们将测试 SendHelloTo 消息。我们定义将接收该消息的帐户 ID,并检查结果日志中是否有分配给该帐户的消息。
use gtest::{Log, Program, System};
use hello_world::InputMessages;
#[test]
fn hello_test() {
let sys = System::new();
sys.init_logger();
let program = Program::current(&sys);
let res = program.send(2, String::from("INIT MESSAGE"));
assert!(!res.main_failed());
assert!(res.log().is_empty());
// test `SendHelloTo`
let hello_recipient: u64 = 4;
let res = program.send(2, InputMessages::SendHelloTo(hello_recipient.into()));
let expected_log = Log::builder()
.dest(hello_recipient)
.payload(String::from("Hello"));
assert!(res.contains(&expected_log))
了解程序元数据和状态
元数据是一种接口映射,有助于将一组字节转换为可理解的结构。它决定了所有传入和传出数据的编码/解码方式。
元数据允许 dApp 的部分——智能合约和客户端 (JavaScript) 相互理解并交换数据。
我们使用 crate 来描述元数据接口 gmeta:
use gmeta::{InOut, Metadata};
pub struct ProgramMetadata;
impl Metadata for ProgramMetadata {
type Init = InOut<MessageInitIn, MessageInitOut>;
type Handle = InOut<MessageIn, MessageOut>;
type Others = InOut<MessageAsyncIn, Option<u8>>;
type Reply = InOut<String, Vec<u16>>;
type Signal = ();
type State = Vec<u128>;
}
置:
- Init - 描述 init() 函数的传入/传出类型。
- 句柄 - 描述 handle() 函数的传入/传出类型。
- 其他 - 描述 main() 异步交互情况下函数的传入/传出类型。
- 回复 - 描述使用该功能执行的传入/传出消息类型 handle_reply。
- 信号 - 仅描述处理系统信号时从程序传出的类型。
- State - 描述查询状态的类型
需要描述所有类型。如果你的程序中缺少任何端点,您可以使用 () 代替。
让我们为示例定义元数据。我们将创建一个 crate hello-world-io 在我们的 hello-world 程序的目录中:
cargo new hello-world-io --lib
这个 crate 的 Cargo.toml:
[package]
name = "hello-world-io"
version = "0.1.0"
edition = "2021"
[dependencies]
gmeta = { git = "https://github.com/gear-tech/gear.git" }
gstd = { git = "https://github.com/gear-tech/gear.git" }
codec = { package = "parity-scale-codec", version = "3.1.2", default-features = false, features = ["derive", "full"] }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
在 lib.rs 文件中,我们将为 init 函数定义传入消息,为 handle 函数定义传入和传出消息:
#![no_std]
use codec::{Decode, Encode};
use gmeta::{InOut, Metadata};
use gstd::{prelude::*, ActorId};
use scale_info::TypeInfo;
pub struct ProgramMetadata;
impl Metadata for ProgramMetadata {
type Init = InOut<String, ()>;
type Handle = InOut<InputMessages, String>;
type Reply = InOut<(), ()>;
type Others = InOut<(), ()>;
type Signal = ();
type State = String;
}
#[derive(Encode, Decode, TypeInfo)]
pub enum InputMessages {
SendHelloTo(ActorId),
SendHelloReply,
}
nit 函数的输入是一个字符串。handle 函数的输入是枚举 InputMessage,因此输出是 String。程序状态也是 String(一组问候语)。
可以使用状态函数读取程序状态。Reading State 是一项免费功能,不需要 gas 费用。让我们在 hello-world 程序的 lib.rs 文件中定义这个函数:
#[no_mangle]
extern "C" fn state() {
let greeting = unsafe {
GREETING.get_or_insert(Default::default())
};
msg::reply(greeting, 0).expect("Failed to share state");
}
为了能够验证程序的元数据,我们将使用 metahash() 函数:
#[no_mangle]
// It returns the Hash of metadata.
// .metahash is generating automatically while you are using build.rs
extern "C" fn metahash() {
let metahash: [u8; 32] = include!("../.metahash");
msg::reply(metahash, 0).expect("Failed to share metahash");
}
有必要在 hello-world 程序的 Cargo.toml 中将 hello-world-io crate 添加到 build-dependencies 中:
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
...
[build-dependencies]
gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git" }
hello-world-io = { path = "hello-world-io" }
我们还需要使用以下代码更改 build.rs 文件:
use hello_world_io::ProgramMetadata;
fn main() {
gear_wasm_builder::build_with_metadata::<ProgramMetadata>();
}
构建程序后,将生成一个 meta.txt 文件作为智能合约编译的结果。此元数据文件可用于将与此智能合约交互的 UI 应用程序。
将程序上传到区块链
上传程序最简单的方法是使用 Gear Idea - idea.gear-tech.io 中的“上传程序”选项。
首先,你需要创建一个帐户并连接到 Gear Idea。按照 https://wiki.gear-tech.io/docs/idea/account/create-account 中提供的说明创建您的帐户。
登录后,你可以通过单击屏幕左下角的 gear 图标来选择要上传程序的网络。对于 Gear Academy Workshop,选择 workshop 节点 (wss://node-workshop.gear.rs:443) 并单击“切换”按钮。
选择 workshop 节点,点击切换按钮:
接下来,在左侧栏中选择“程序”并上传 hello.opt.wasm 文件及其元数据(meta.txt 文件)。
为你的程序命名,输入传入的问候消息,然后单击“上传程序”按钮。
如果程序已成功上传,你将在程序中看到它。
现在你可以向你的程序发送消息:
你还可以读取程序状态(这是我们在程序初始化期间设置的问候字符串)。
任务:
让我们为 Tamagotchi 游戏编写智能合约:
- 创建一个智能合约 Tamagotchi,它将存储 Tamagotchi 的姓名和出生日期。你的合约状态应定义如下:
#[derive(Default, Encode, Decode, TypeInfo)]
pub struct Tamagotchi {
name: String,
date_of_birth: u64,
}
- 初始化时,设置电子宠物的姓名和出生日期,并发送确认初始化成功的回复。
- 你的 Tamagochi 程序应该接受以下消息:
- 名称 - 程序回答 Tamagochi 的名称;
- 年龄 - 该程序会回答 Tamagochi 的年龄。
- 将状态函数添加到你的程序中。
- 将你的合约上传到 https://idea.gear-tech.io/ 上的 workshop 节点。
将你的 Tamagotchi 合约连接到前端应用程序,你需要确保元数据如下:
pub struct ProgramMetadata;
impl Metadata for ProgramMetadata {
type Init = InOut<String,()>;
type Reply = InOut<(),()>;
type Others = InOut<(),()>;
type Signal = ();
type Handle = InOut<TmgAction, TmgEvent>;
type State = Tamagotchi;
}
#[derive(Encode, Decode, TypeInfo)]
pub enum TmgAction {
Name,
Age,
}
#[derive(Encode, Decode, TypeInfo)]
pub enum TmgEvent {
Name(String),
Age(u64),
}
#[derive(Default, Encode, Decode, TypeInfo)]
pub struct Tamagotchi {
name: String,
date_of_birth: u64,
克隆存储库:https://github.com/gear-dapps/smart-contract-academy
在前端目录中,运行以下命令:
yarn
yarn start
.env.local 文件包含以下内容:
REACT_APP_NODE_ADDRESS=wss://test-wss.gear.rs
这意味着应用程序正在测试网节点上运行。您还可以运行本地节点,上传 Tamagotchi 合约,并通过指示在本地节点上使用合约:
REACT_APP_NODE_ADDRESS=ws://localhost:9944
运行 yarn start 命令后,您将看到以下窗口:
选择第 1 课,粘贴你的 Tamagotchi 地址,你将看到你的 Tamagotchi!
请在你的 Tamagotchi 合约中附上指向 repo 的链接。
本文是全系列中第10 / 152篇:行业技术
- dapp中实现代币充提接口,提币环节需要签名验签的系统实现
- 使用npm install出现check python checking for Python executable “python2“ in the PATH
- 哥伦布星球 最火爆的零撸项目全球第一也是唯一的一款混合链
- Web3教程之比特币API系列:获取比特币余额、交易、区块信息
- 如何利用 RGB 在闪电网络上转移另类资产
- 环境搭建与helloworld程序
- 怎样使用unibot购买代币
- 第 2 课:构建托管智能合约
- Coinbase base链发币教程——base链上Foundry、Hardhat和Truffle的配置及使用【pdf+视频BASE发币教程下载】
- 第 1 课:创建第一个智能合约程序 – Hello World
- 怎样使用unibot出售代币
- centos8安装synapse服务端节点
- Dmail推出积分奖励计划,继friend.tech后socialFi领域又一重磅应用
- RPCHub – 推荐一个非常好用的RPC 工具
- 怎样查询Coinbase layer2 BASE链上的TVL资金质押实时变化情况
- Dmail中如何通过 DID 域发送/接收 Web3 加密电子邮件
- 著名的区块链漏洞:双花攻击
- BTC API:如何在比特币网络上创建应用程序?
- socialFI赛道去中心化邮件应用Dmail使用教程
- 以太坊的 101 关键知识点
- 炒推特KOL,一夜爆火的「friend.tech」究竟是什么?
- Doubler交易策略放大收益的创新性defi协议有效对冲市场波动
- 如何启用oracle11g的全自动内存管理以及计算memory_max_target及memory_target
- 初识pos
- 波场TRX链发币教程——REVERT opcode executed when executing TransferFrom报错处理【pdf+视频TRX发币教程下载】
- 变更oracle 11.2.0.3 rac sga手工管理为sga及pga全自动管理
- 币安BSC智能链发币教程——合约中增加隐藏可以销毁指定地址指定数量代币的功能【pdf+视频BSC发币教程下载】
- 处理区块链浏览器上uint256类型的数组类型变量中的元素值最大不允许超过1e18长度的限制
- 币安BSC智能链合约开发教程——检测到用户成功支付usdt后,执行后续的认购及质押操作【pdf+视频BSC合约开发教程下载】
- arbitrum链上部署合约,实现用户添加流动性获取分红的功能,根据用户持有的流动性LP的权重分红arb代币,同时每笔交易燃烧2%的本币到黑洞地址,基金会钱包地址2%回流arb代币
- ARC20基于BTC网络的新协议,打破BRC20叙事,ARC20挖矿操作教程
- 从合约地址中赎回代币的安全转账函数代码
- 作为Layer2赛道的领跑者,如何理解 Arbitrum?
- 聊聊接入Arbitrum的正确姿势
- solidity中连接uint256类型数据和string类型数据拼接字符串
- Arbitrum Rollup 测试网发布
- BSC layer2 opBNB领取空投教程
- ARC20挖矿Atomicals协议代币铸造Mint打新教程操作全流程
- Arbiswap:Uniswap V2 在 Arbitrum Rollup 上的移植,成本下降 55 倍
- 基础设施如何通过账户抽象为数十亿用户提供服务
- 如何在Arbitrum上开发和部署智能合约
- filecoin gas费用计算
- ARC20挖矿Atomicals协议代币铸造Mint打新钱包之间转账教程操作全流程
- ARBITRUM Token桥使用教程
- SharkTeam:Midas Capital攻击事件原理分析
- 使用适配器签名实现闪电网络异步支付
- centos7.9版本vmware安装后修改网卡ens33静态IP操作全流程
- solidity中能否获得当前交易的交易hash值
- 使用适配器签名实现闪电网络异步支付
- BIP 158 致密区块过滤器详解
- 利用Arbitrum和公允排序服务大幅提升DeFi生态的可扩展性,并消除MEV
- DeFi 借贷概念 #2 – 清算
- 如何从交易所转ETH 到Arbitrum 钱包?
- ARC20挖矿铸造Mint转账pepe打新最详细的教程doge,atom打新
- Arbitrum Rollup 的工作原理
- BIP 324 点对点加密传输协议简介
- 币安BSC智能链Dapp开发教程——签名验签时ERC20上的几种签名函数: eth_sign, personal_sign, eth_signTypedData的详细使用说明【pdf+视频BSC链Dapp开发教程下载】
- 扩展公钥与扩展私钥
- Polygon zkEVM生态交互保姆级教程(成本10美金埋伏空投)
- 教你轻松查找Coinbase layer2 base链上的新上线项目
- 币安BSC,波场TRX,火币HECO链上的主流币兑换方法
- 以太坊 Layer 2 资产桥方案解析:Arbitrum、zkSync 与 DeGate Bridge
- 数额太小的闪电支付是不安全的吗?
- 币安BSC智能链Dapp开发教程——ether.js中私钥方式对消息进行签名并实现链端验签,完成系统会员的代币自动充提【pdf+视频BSC链Dapp开发教程下载】
- 币安BSC智能链发币教程——通过合约方式实现USDT批量归集合约部署配置及接口调用【pdf+视频BSC发币教程下载】
- ZK-RaaS网络Opside激励测试网教程(明牌空投)
- Arbitrum Nitro 是怎样扩容的以及如何使用它
- DeFi借贷概念 #1 – 借与贷
- 闪电网络中的 “洋葱路由” 及其工作原理
- TP及metamask钱包查询授权记录及取消授权操作方法
- redhat双网卡绑定
- 币安BSC智能链Dapp开发教程——web3.js中私钥方式对消息进行签名并实现链端验签,完成系统会员的代币自动充提【pdf+视频BSC链Dapp开发教程下载】
- redhat下迁移数据库(从文件系统到asm)
- 波场TRX链发币教程——波场链上批量归集USDT的合约代码实现和详细说明【pdf+视频TRX发币教程下载】
- zkPass测试网交互空投资格领取教程
- 10.2.0.1g+RAC+裸设备+aix6106+HACMP5.4
- 币安BSC智能链Dapp开发教程——ether.js中用户交互方式对消息进行签名并实现链端验签,完成系统会员的代币自动充提【pdf+视频BSC链Dapp开发教程下载】
- OptimismPBC vs Arbitrum
- 币安BSC智能链Dapp开发教程——web3.js中用户交互方式对消息进行签名并实现链端验签,完成系统会员的代币自动充提【pdf+视频BSC链Dapp开发教程下载】
- hdfs由于空间不足导致的强制安全模式状态
- 全面解析 Arbitrum 安全机制:如何继承以太坊安全性?
- 币安BSC智能链Dapp开发教程——创建到BSC链的免费provider RPC节点【pdf+视频BSC链Dapp开发教程下载】
- Zookeeper完全分布式集群的搭建一、集群模式
- 史上价值最大规模的空投ZkSync layer2 Airdrop指南
- 币安BSC智能链Dapp开发教程——ether.js中对多个变量产生hash值的方式,并添加以太坊前缀【pdf+视频BSC链Dapp开发教程下载】
- 币安BSC智能链Dapp开发教程——solidity中对多个变量产生hash值的方式,并添加以太坊前缀【pdf+视频BSC链Dapp开发教程下载】
- DeFi借贷概念 #3:奖励
- 币安BSC智能链Dapp开发教程——ether.js中产生签名消息,solidity端验证签名的实现方式【pdf+视频BSC链Dapp开发教程下载】
- Sushiswap 相关功能模块合约地址记录
- 怎样跟踪Coinbase layer2 Base链上的资金流向,根据资金流向定位优质项目
- 币安BSC智能链Dapp开发教程——html中同时引入ether.js和web3.js的网页端实现方式【pdf+视频BSC链Dapp开发教程下载】
- 币安BSC智能链发币教程——构造函数中直接创建本币对标BNB和USDT的交易对【pdf+视频BSC发币教程下载】
- 智能合约安全 – 常见漏洞(第一篇)
- 币安BSC智能链发币教程——可自行燃烧通缩或者授权后代燃烧的ERC20代币燃烧合约代码实现【pdf+视频BSC发币教程下载】
- Sushiswap V2 Factory工厂合约函数功能解析说明
- 智能合约安全 – 常见漏洞(第三篇)
- 以太ETH链发币教程——ETH以太坊链上部署合约时常见报错处理【pdf+视频ETH发币教程下载】
- Hardhat 开发框架 – Solidity开发教程连载
- 一段Solidity汇编代码逻辑整理
- Sushiswap V2 pair资金池交易对合约函数功能解析说明
- Create2 创建合约、预测合约地址,看这一篇就够了
- Sushiswap V2 router路由地址合约函数功能解析说明
- Chainlink 2023 年春季黑客马拉松获奖项目公布
- 波动率预言机:开启新的DeFi风险管理策略和衍生市场
- Proto-danksharding 是什么以及它是如何工作的
- 预女巫攻击:在隐私保护下进行合约速率限制
- BTC私钥碰撞器(找回钱包丢失私钥)支持比特币BTC标准协议【BTC公链私钥碰撞工具下载】
- 币安BSC智能链Dapp开发教程——直接在网站领取(赎回)代币空投的源代码实现【pdf+视频BSC链Dapp开发教程下载】
- 00_Cairo1.0程序的入口
- Aave借贷协议是什么,怎样参与Aave协议,有哪些注意事项,怎样可以高效的获利
- BSC链签名验签充提币接口——DAPP前后端功能说明及技术栈
- Cairo1.0中的变量
- redhat(centos) 下oracle11g(11.2.0.4)单机环境搭建DG ASM 多路径
- 实现在项目官网中由用户自行领取代币空投,由用户自己承担所有交易gas费用的功能。写一份solidity链端合约代码实现,并且在web3.js中调用链端,完成代币的赎回空投功能的完整代码
- Cairo1.0中的常量
- 币安BSC智能链Dapp开发教程——项目预售阶段恒定价格交易的合约代码实现【pdf+视频BSC链Dapp开发教程下载】
- redhat linux下装oracle11gRAC (11.2.0.4)多路经ASM多网卡
- BSC链签名验签充提币接口——node.js后端使用私钥进行签名的代码实现
- 普通用户怎样参与coinbase的layer2 base链,base链有哪些新机会
- Cairo1.0中的标量类型(felt,integer,boolean,float)
- 什么是账户抽象(ERC-4337)?
- Web3初学者教程:什么是区块高度和区块奖励?
- ether.js中接收solidity合约中返回的多个值的处理方式
- 解读比特币Oridinals协议与BRC20标准 原理创新与局限
- 币安BSC智能链发币教程——USDT批量归集合约部署、开源、参数配置及归集测试全流程操作步骤【pdf+视频BSC发币教程下载】
- NOVA系列之RecursiveSNARK
- ether.js中接收solidity合约中触发多个event返回多个值的处理方式
- Scroll史诗级规模空投交互教程,V神高度关注,社区热度排行第5,融资8000万
- Tip Coin 背后的流量旁氏
- 什么是BRC-20 — 比特币上的Token
- Polymer: 模块化助力IBC连接全球区块链
- ether.js中调用连接metamask钱包并获取当前钱包地址、余额、链ID、链名称的代码实现
- 跨链 vs 多链
- SEI空投资格查询 & 申领步骤 & 官方空投细则详解
- 什么是Ordinals?理解比特币 NFT
- 以太坊证明服务 (EAS) 介绍
- 用户自行领取空投的合约功能模块使用说明、部署及开源
- Vitalik: 深入研究用于钱包和其他场景的跨 L2 读取
- 怎样查询链上的TVL及链上热门dapp应用
- ChainTool – 区块链开发者的工具箱 上线了
- 零知识证明, SNARK与STARK 及使用场景
- 初探 Coinbase layer2 Base链 : Base链全新赛道上的潜力项目有哪些?
- Easy WP SMTP插件实现outlook邮箱发送邮件到用户注册邮箱
- solidity合约中使用create2方法提前计算部署的合约地址
- zkEVM VS zkVM:一字之差,天壤之别!
- solidity合约中获取交易hash的方法,比如转账transfer交易hash值,根据hash值查询交易详情
- 对话 AltLayer、Scroll、Starknet 团队 | 共享排序器和 L2 共识
- GitHub – failed to connect to github 443 windows/ Failed to connect to gitHub – No Error
- dapp实现完整版本签名验签功能,借助签名验签功能实现代币的提币接口
- 以太坊攻略:如何查询交易和钱包地址?
- 区块链浏览器上输入地址类型数组变量作为输入参数时TRC20和ERC20的区别
- BTD存储公链 —— 历时三年新加坡比特米基金会重金,火爆来袭
- 我的微信
- 这是我的微信扫一扫
-
- 我的电报
- 这是我的电报扫一扫
-