- A+
所属分类:以太坊ETH
全面指南:构建与部署以太坊多签钱包(MultiSigWallet)智能合约的最佳实践
MultiSigWallet介绍
这是一个基于以太坊智能合约的简单多签钱包实现。多签钱包允许多个签名者共同控制钱包资金,以增加安全性和透明度。
功能
实现⼀个简单的多签合约钱包,合约包含的功能:
- 创建多签钱包时,确定所有的多签持有⼈和签名门槛
- 多签持有⼈可提交提案
- 其他多签⼈确认提案(使⽤交易的⽅式确认即可)
- 达到多签⻔槛、任何⼈都可以执⾏交易
这是一个基于以太坊智能合约的多签钱包实现。多签钱包是一种允许多个签名者共同控制钱包资金的合约。在这个实现中,合约的所有者可以提交提案,然后其他所有者可以确认提案。当提案被确认的次数达到阈值时,提案将被执行。
实操
实现原理:
- 使用数组和结构体来存储提案信息,包括目标地址、转账金额和调用数据。
- 使用 mapping 来存储所有者和提案 ID 的映射关系,以及提案 ID 和提案的映射关系。
- 使用 modifier 来限制函数的访问权限,确保只有所有者可以提交和确认提案。
- 使用事件来记录提案的创建、确认和执行。
用途:
- 用于多签持有人共同控制钱包资金。
- 用于实现去中心化交易所、借贷平台等应用。
注意事项
- 地址管理:确保所有者地址的正确性和唯一性。
- 提案验证:提交提案时,验证金额和数据符合预期。
- 确认检查:确认提案时,防止重复确认。
- 执行确认:执行提案前,确认提案已正确确认并达到门槛。
什么是MultiSigWallet
MultiSigWallet 是一种多签钱包,它允许多个账户共同控制一个钱包的资产。在MultiSigWallet中,每个账户都有一个权重,这个权重决定了该账户在交易中的投票权。只有当足够的账户(即权重之和大于等于总权重)投票同意后,交易才能被执行。
MultiSigWallet 的应用场景
MultiSigWallet 可以用于各种需要多个账户共同决策的场景,例如:
- 共同控制公司资产
- 共同管理基金
- 共同控制数字货币资产
- 共同管理智能合约
MultiSigWallet 的优点
MultiSigWallet 的优点包括:
- 安全性高:由于需要多个账户共同决策,因此即使某个账户被攻击,也不会影响整个钱包的安全。
- 灵活性高:可以根据需要设置不同的权重,以适应不同的场景。
- 可扩展性高:可以添加或删除账户,以适应团队的变化。
实操
forge init MultiSigWallet
cd MultiSigWallet/
code .
touch .env
touch StudyNotes.md
目录结构
MultiSigWallet on master [!+?] via base
➜ tree . -L 6 -I 'lib|out|broadcast|cache'
.
├── README.md
├── StudyNotes.md
├── foundry.toml
├── remappings.txt
├── script
│ └── MultiSigWallet.s.sol
├── src
│ ├── MultiSigWallet.sol
│ └── MyToken.sol
└── test
└── MultiSigWalletTest.sol
4 directories, 12 files
代码
MultiSigWallet.sol
文件
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract MultiSigWallet {
address[] public owners; // 多签持有人地址列表
uint256 public threshold; // 签名门槛
struct Proposal {
address target; // 目标地址
uint256 value; // 转账金额
bytes data; // 调用数据
bool executed; // 提案是否已执行
uint256 confirmations; // 确认数
mapping(address => bool) confirmedBy; // 确认者地址映射
}
Proposal[] public proposals;
mapping(address => bool) public isOwner;
// 修饰符:仅限所有者
modifier onlyOwner() {
require(isOwner[msg.sender], "Not an owner"); // 检查发送者是否为所有者
_;
}
// 修饰符:检查提案是否存在
modifier proposalExists(uint256 proposalId) {
require(proposalId < proposals.length, "Proposal does not exist"); // 检查提案ID是否有效
_;
}
// 修饰符:检查提案是否未执行
modifier notExecuted(uint256 proposalId) {
require(!proposals[proposalId].executed, "Proposal already executed");
_;
}
/**
* @dev 构造函数:初始化合约,设置所有者和签名门槛。创建多签钱包时,确定所有的多签持有⼈和签名门槛
* @param _owners 多签持有人
* @param _threshold 多签门槛
*/
constructor(address[] memory _owners, uint256 _threshold) {
require(_owners.length > 0, "At least one owner required");
require(_threshold > 0 && _threshold <= _owners.length, "Invalid threshold");
for (uint256 i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "Invalid owner address");
require(!isOwner[owner], "Duplicate owner");
isOwner[owner] = true;
owners.push(owner);
}
threshold = _threshold;
}
/**
* @dev submitProposal:允许多签持有人提交提案。提交提案:所有者可以提交提案,提案包括目标地址、转账金额和调用数据。
* @param target 目标地址
* @param value 转账金额
* @param data 调用数据
*/
function submitProposal(address target, uint256 value, bytes calldata data) external onlyOwner {
uint256 proposalId = proposals.length; // 获取提案ID
Proposal storage proposal = proposals.push(); // 创建新提案
proposal.target = target; // 设置目标地址
proposal.value = value; // 设置转账金额
proposal.data = data; // 设置调用数据
proposal.executed = false; // 初始化为未执行
proposal.confirmations = 0; // 初始化确认数为0
emit ProposalCreated(proposalId, target, value, data); // 触发提案创建事件
}
/**
* @dev confirmProposal:允许多签持有人确认提案。确认提案:所有者可以确认提案,提案确认后,确认数加1。
* @param proposalId 提案ID
*/
function confirmProposal(uint256 proposalId)
external
onlyOwner
proposalExists(proposalId)
notExecuted(proposalId)
{
Proposal storage proposal = proposals[proposalId];
require(!proposal.confirmedBy[msg.sender], "Proposal already confirmed by sender");
proposal.confirmedBy[msg.sender] = true;
proposal.confirmations++;
emit ProposalConfirmed(proposalId, msg.sender);
if (proposal.confirmations >= threshold) {
executeProposal(proposalId);
}
}
/**
* @dev executeProposal:执行提案。执行提案:提案确认数达到阈值后,执行提案。
* @param proposalId 提案ID
* 在确认数达到门槛时执行提案。该函数被 confirmProposal 调用。
*/
function executeProposal(uint256 proposalId) internal proposalExists(proposalId) notExecuted(proposalId) {
Proposal storage proposal = proposals[proposalId]; // 获取提案
require(proposal.confirmations >= threshold, "Insufficient confirmations"); // 检查确认数是否足够
proposal.executed = true; // 标记为已执行
// 调用目标地址的函数
(bool success,) = proposal.target.call{value: proposal.value}(proposal.data);
emit ProposalExecutionLog(proposalId, proposal.target, proposal.value, proposal.data, success);
require(success, "Transaction failed");
emit ProposalExecuted(proposalId);
}
function cancelProposal(uint256 proposalId) external onlyOwner proposalExists(proposalId) notExecuted(proposalId) {
Proposal storage proposal = proposals[proposalId];
require(proposal.confirmations == 0, "Cannot cancel a confirmed proposal");
delete proposals[proposalId];
emit ProposalCancelled(proposalId);
}
function getProposalsLength() public view returns (uint256) {
return proposals.length;
}
function isConfirmed(uint256 proposalId, address owner) external view returns (bool) {
Proposal storage proposal = proposals[proposalId];
return proposal.confirmedBy[owner];
}
function getProposal(uint256 proposalId) external view returns (address, uint256, bytes memory, bool, uint256) {
Proposal storage proposal = proposals[proposalId];
return (proposal.target, proposal.value, proposal.data, proposal.executed, proposal.confirmations);
}
// Fallback function to accept ether
receive() external payable {
emit Received(msg.sender, msg.value);
}
fallback() external payable {
emit FallbackEvent(msg.sender, msg.value);
}
event ProposalCreated(uint256 proposalId, address target, uint256 value, bytes data);
event ProposalConfirmed(uint256 proposalId, address confirmer);
event ProposalExecuted(uint256 proposalId);
event ProposalCancelled(uint256 proposalId);
event ProposalExecutionLog(uint256 proposalId, address target, uint256 value, bytes data, bool success);
event Received(address sender, uint256 amount);
event FallbackEvent(address sender, uint256 amount);
}
测试
测试代码
MultiSigWalletTest.sol 文件
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {MultiSigWallet} from "../src/MultiSigWallet.sol";
import {MyToken} from "../src/MyToken.sol";
contract MultiSigWalletTest is Test {
MultiSigWallet public msw;
MyToken public mytoken;
Account owner = makeAccount("owner");
Account bob = makeAccount("bob");
Account alice = makeAccount("alice");
Account charlie = makeAccount("charlie");
address[] public owners = [owner.addr, bob.addr, alice.addr];
uint256 public threshold = 2;
event TestReceived(address sender, uint256 amount);
function setUp() public {
mytoken = new MyToken(owner.addr);
msw = new MultiSigWallet(owners, threshold);
vm.deal(owner.addr, 1 ether);
vm.deal(bob.addr, 1 ether);
vm.deal(alice.addr, 1 ether);
vm.deal(charlie.addr, 1 ether);
vm.deal(address(msw), 1 ether);
vm.startPrank(owner.addr);
mytoken.mint(owner.addr, 100 ether);
mytoken.mint(address(msw), 100 ether);
vm.stopPrank();
}
function testSubmitProposal() public {
vm.startPrank(owner.addr);
msw.submitProposal(charlie.addr, 1 ether, "transfer");
assertEq(msw.getProposalsLength(), 1);
(address target, uint256 value, bytes memory data, bool executed, uint256 confirmations) = msw.getProposal(0);
assertEq(target, charlie.addr);
assertEq(value, 1 ether);
assertEq(data, "transfer");
assertEq(executed, false);
assertEq(confirmations, 0);
vm.stopPrank();
}
function testConfirmProposal() public {
vm.startPrank(owner.addr);
msw.submitProposal(address(mytoken), 1 ether, "");
msw.confirmProposal(0);
vm.stopPrank();
vm.prank(bob.addr);
msw.confirmProposal(0);
assertEq(msw.getProposalsLength(), 1);
(address target, uint256 value, bytes memory data, bool executed, uint256 confirmations) = msw.getProposal(0);
assertEq(target, address(mytoken));
assertEq(value, 1 ether);
assertEq(data, "");
assertEq(executed, true);
assertEq(confirmations, 2);
require(confirmations >= threshold, "Confirmations should be greater than or equal to the threshold");
assertGe(confirmations, msw.threshold(), "Confirmations should be greater than or equal to threshold");
require(msw.isConfirmed(0, owner.addr), "Proposal not confirmed");
}
function testConfirmProposalSuccessful() public {
vm.startPrank(owner.addr);
msw.submitProposal(charlie.addr, 1 ether, "");
msw.confirmProposal(0);
vm.stopPrank();
vm.prank(bob.addr);
msw.confirmProposal(0);
assertEq(msw.getProposalsLength(), 1);
(address target, uint256 value, bytes memory data, bool executed, uint256 confirmations) = msw.getProposal(0);
assertEq(target, charlie.addr);
assertEq(value, 1 ether);
assertEq(data, "");
assertEq(executed, true);
assertEq(confirmations, 2);
require(confirmations >= threshold, "Confirmations should be greater than or equal to the threshold");
assertGe(confirmations, msw.threshold(), "Confirmations should be greater than or equal to threshold");
require(msw.isConfirmed(0, owner.addr), "Proposal not confirmed");
}
function testConfirmProposalBelowThreshold() public {
vm.startPrank(owner.addr);
msw.submitProposal(charlie.addr, 1 ether, "");
msw.confirmProposal(0);
vm.stopPrank();
assertEq(msw.getProposalsLength(), 1);
(address target, uint256 value, bytes memory data, bool executed, uint256 confirmations) = msw.getProposal(0);
assertEq(target, charlie.addr);
assertEq(value, 1 ether);
assertEq(data, "");
assertEq(executed, false);
assertEq(confirmations, 1); // Only one confirmation
require(confirmations < threshold, "Confirmations should be less than the threshold");
}
function testRepeatedConfirmation() public {
vm.startPrank(owner.addr);
msw.submitProposal(charlie.addr, 1 ether, "");
msw.confirmProposal(0);
vm.stopPrank();
vm.prank(bob.addr);
msw.confirmProposal(0);
// Attempt to confirm again
vm.prank(bob.addr);
try msw.confirmProposal(0) {
revert("Bob should not be able to confirm the proposal again");
} catch {}
// Confirm proposal status
(address target, uint256 value, bytes memory data, bool executed, uint256 confirmations) = msw.getProposal(0);
assertEq(target, charlie.addr);
assertEq(value, 1 ether);
assertEq(data, "");
assertEq(executed, true);
assertEq(confirmations, 2); // Ensure confirmations remain 2
}
function testConfirmProposalByDifferentOwners() public {
vm.startPrank(owner.addr);
msw.submitProposal(address(mytoken), 1 ether, "");
msw.confirmProposal(0);
vm.stopPrank();
vm.prank(bob.addr);
msw.confirmProposal(0);
vm.prank(alice.addr);
vm.expectRevert("Proposal already executed");
msw.confirmProposal(0);
assertEq(msw.getProposalsLength(), 1);
(address target, uint256 value, bytes memory data, bool executed, uint256 confirmations) = msw.getProposal(0);
assertEq(target, address(mytoken));
assertEq(value, 1 ether);
assertEq(data, "");
assertEq(executed, true);
assertEq(confirmations, 2);
}
function testInvalidProposal() public {
vm.startPrank(owner.addr);
vm.expectRevert("Proposal with invalid address or value should fail");
try msw.submitProposal(address(0), 0, "invalid") {
revert("Proposal with invalid address or value should fail");
} catch {}
vm.stopPrank();
}
function testCancelProposal() public {
// 提交一个新的提案
vm.startPrank(owner.addr);
msw.submitProposal(charlie.addr, 1 ether, ""); // 提交提案
uint256 proposalId = msw.getProposalsLength() - 1; // 获取提案ID
// 确保提案已提交
assertEq(msw.getProposalsLength(), 1);
// 取消提案
msw.cancelProposal(proposalId);
// 确保提案已取消
// 使用 delete 操作符: 数组的长度不会改变。删除的元素会被重置为默认值
assertEq(msw.getProposalsLength(), 1, "Proposal should be cancelled");
vm.stopPrank();
// 验证提案是否已从映射中删除
(address target, uint256 value, bytes memory data, bool executed, uint256 confirmations) =
msw.getProposal(proposalId);
assertEq(target, address(0));
assertEq(value, 0);
assertEq(data, "");
assertEq(executed, false);
assertEq(confirmations, 0);
}
function testSubmitProposalToken() public {
assertEq(mytoken.balanceOf(owner.addr), 100e18, "Owner balance should be 100 tokens");
assertEq(mytoken.balanceOf(address(msw)), 100e18, "MultiSigWallet balance should be 100 tokens");
// 准备参数
address target = address(mytoken); // 目标地址是 MyToken 合约地址
uint256 value = 0; // 转账金额为0,因为我们只调用函数
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", owner.addr, 50e18);
// 模拟 owner1 提交提案
vm.startPrank(owner.addr); // 模拟 owner1 的操作
msw.submitProposal(target, value, data);
// 验证提案已创建
uint256 proposalId = msw.getProposalsLength() - 1;
(address _target, uint256 _value, bytes memory _data, bool executed, uint256 confirmations) =
msw.getProposal(proposalId);
assertEq(_target, target);
assertEq(_value, value);
assertEq(_data, data);
assertFalse(executed);
assertEq(confirmations, 0);
msw.confirmProposal(0);
vm.stopPrank();
vm.prank(bob.addr);
msw.confirmProposal(0);
// Verify the proposal has been confirmed
(
address confirmedTarget,
uint256 confirmedValue,
bytes memory confirmedData,
bool confirmedExecuted,
uint256 confirmedConfirmations
) = msw.getProposal(proposalId);
assertEq(confirmedTarget, target);
assertEq(confirmedValue, value);
assertEq(confirmedData, data);
assertTrue(confirmedExecuted);
assertEq(confirmedConfirmations, 2);
assertGe(confirmedConfirmations, msw.threshold(), "Confirmations should be greater than or equal to threshold");
require(msw.isConfirmed(0, owner.addr), "Proposal not confirmed");
assertEq(mytoken.balanceOf(owner.addr), 150e18, "Owner balance should be 150 tokens");
}
}
实操测试
MultiSigWallet on main via base took 5.0s
➜ forge fmt && forge test --match-path ./test/MultiSigWalletTest.sol --show-progress -vv
[⠊] Compiling...
No files changed, compilation skipped
test/MultiSigWalletTest.sol:MultiSigWalletTest
↪ Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 11.71ms (16.59ms CPU time)
Ran 9 tests for test/MultiSigWalletTest.sol:MultiSigWalletTest
[PASS] testCancelProposal() (gas: 79837)
[PASS] testConfirmProposal() (gas: 218863)
[PASS] testConfirmProposalBelowThreshold() (gas: 148399)
[PASS] testConfirmProposalByDifferentOwners() (gas: 219851)
[PASS] testConfirmProposalSuccessful() (gas: 217422)
[PASS] testInvalidProposal() (gas: 70004)
[PASS] testRepeatedConfirmation() (gas: 212759)
[PASS] testSubmitProposal() (gas: 119132)
[PASS] testSubmitProposalToken() (gas: 305882)
Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 11.71ms (16.59ms CPU time)
Ran 1 test suite in 346.02ms (11.71ms CPU time): 9 tests passed, 0 failed, 0 skipped (9 total tests)
部署
部署脚本
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {MultiSigWallet} from "../src/MultiSigWallet.sol";
contract MultiSigWalletScript is Script {
MultiSigWallet public msw;
address[] public owners;
uint256 public threshold = 1;
function setUp() public {}
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployerAccountAddress = vm.envAddress("ACCOUNT_ADDRESS");
address deployerAddress = vm.addr(deployerPrivateKey);
owners = [deployerAddress, deployerAccountAddress];
vm.startBroadcast(deployerPrivateKey);
msw = new MultiSigWallet(owners, threshold);
console.log("MultiSigWallet deployed to:", address(msw));
vm.stopBroadcast();
}
}
实操部署
MultiSigWallet on master [!+?] via base
➜ source .env
MultiSigWallet on master [!+?] via base took 17.6s
➜ forge script --chain sepolia MultiSigWalletScript --rpc-url $SEPOLIA_RPC_URL --account MetaMask --broadcast --verify -vvvv
[⠊] Compiling...
No files changed, compilation skipped
Traces:
[1144342] MultiSigWalletScript::run()
├─ [0] VM::envUint("PRIVATE_KEY") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::envAddress("ACCOUNT_ADDRESS") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] 0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5
├─ [0] VM::startBroadcast(<pk>)
│ └─ ← [Return]
├─ [1028302] → new MultiSigWallet@0xDd2fE19ff6F33d1A57FE6e845Ae49A071224c55B
│ └─ ← [Return] 4462 bytes of code
├─ [0] console::log("MultiSigWallet deployed to:", MultiSigWallet: [0xDd2fE19ff6F33d1A57FE6e845Ae49A071224c55B]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::stopBroadcast()
│ └─ ← [Return]
└─ ← [Stop]
Script ran successfully.
== Logs ==
MultiSigWallet deployed to: 0xDd2fE19ff6F33d1A57FE6e845Ae49A071224c55B
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
[1028302] → new MultiSigWallet@0xDd2fE19ff6F33d1A57FE6e845Ae49A071224c55B
└─ ← [Return] 4462 bytes of code
==========================
Chain 11155111
Estimated gas price: 39.158879778 gwei
Estimated total gas used for script: 1516104
Estimated amount required: 0.059368934266944912 ETH
==========================
Enter keystore password:
##### sepolia
✅ [Success]Hash: 0x19928b01dbf03e0d40d756f36b95f85dc9f8e8629cf0890c57e9369ce7e5748d
Contract Address: 0xDd2fE19ff6F33d1A57FE6e845Ae49A071224c55B
Block: 6447785
Paid: 0.022294817420990006 ETH (1166582 gas * 19.111230433 gwei)
✅ Sequence #1 on sepolia | Total Paid: 0.022294817420990006 ETH (1166582 gas * avg 19.111230433 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
##
Start verification for (1) contracts
Start verifying contract `0xDd2fE19ff6F33d1A57FE6e845Ae49A071224c55B` deployed on sepolia
Submitting verification for [src/MultiSigWallet.sol:MultiSigWallet] 0xDd2fE19ff6F33d1A57FE6e845Ae49A071224c55B.
Submitting verification for [src/MultiSigWallet.sol:MultiSigWallet] 0xDd2fE19ff6F33d1A57FE6e845Ae49A071224c55B.
Submitted contract for verification:
Response: `OK`
GUID: `rd4kf3ehcf7lewpv8jb19tdgxtrve5deckrmmfhbjesbasq5hp`
URL: https://sepolia.etherscan.io/address/0xdd2fe19ff6f33d1a57fe6e845ae49a071224c55b
Contract verification status:
Response: `NOTOK`
Details: `Pending in queue`
Contract verification status:
Response: `OK`
Details: `Pass - Verified`
Contract successfully verified
All (1) contracts were verified!
Transactions saved to: /Users/qiaopengjun/Code/solidity-code/MultiSigWallet/broadcast/MultiSigWallet.s.sol/11155111/run-latest.json
Sensitive values saved to: /Users/qiaopengjun/Code/solidity-code/MultiSigWallet/cache/MultiSigWallet.s.sol/11155111/run-latest.json
MultiSigWallet on master [!+?
部署成功,浏览器查看
https://sepolia.etherscan.io/address/0xdd2fe19ff6f33d1a57fe6e845ae49a071224c55b#code
知识
- EOA 和合约账户在 EVM 上是一样的,有同样的属性 :balance、nonce、code、 state
- 如果一个合约可以持有资金且可以调用任意合约方法,那么这个合约就是一个智能合约钱包账户
- 智能合约钱包:支持多签、multicall、密钥替换、找回 ...
- ERC4337:账户抽象(Account Abstraction),抽象了 EOA 与 智能合约钱包的区别
本文是全系列中第120 / 268篇:行业技术
- 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服务端节点
- BRC20、BSC20、ERC20、EVM网络铭文操作教程——BSC链上铸造mint BSC-20协议标准的铭文【pdf+视频EVM铭文操作教程下载】
- 币安BSC智能链发币教程——ERC314/BSC314协议实时燃烧资金池同步计算买卖价格的核心代码实现【pdf+视频BSC发币教程下载】
- Dmail推出积分奖励计划,继friend.tech后socialFi领域又一重磅应用
- RPCHub – 推荐一个非常好用的RPC 工具
- 币安BSC智能链发币教程——合约自动创建的bnb资金池对被恶意打入WBNB导致添加流动性失败【pdf+视频BSC发币教程下载】
- 怎样查询Coinbase layer2 BASE链上的TVL资金质押实时变化情况
- BRC20、BSC20、ERC20、EVM网络铭文操作教程——铭文赛道各个链marketing链接地址【pdf+视频EVM铭文操作教程下载】
- Dmail中如何通过 DID 域发送/接收 Web3 加密电子邮件
- BTC layer2 B2 Network交互获取积分point领取空投教程
- 著名的区块链漏洞:双花攻击
- BSC链上首个支持BSC-20协议标准的的龙头铭文代币BNBS
- BTC API:如何在比特币网络上创建应用程序?
- socialFI赛道去中心化邮件应用Dmail使用教程
- 以太坊的 101 关键知识点
- BRC20、BSC20、ERC20、EVM网络铭文操作教程——BSC链上通过solidity合约直接部署和批量铸造铭文代币【pdf+视频EVM铭文操作教程下载】
- Solana Actions and Blinks
- 炒推特KOL,一夜爆火的「friend.tech」究竟是什么?
- Doubler交易策略放大收益的创新性defi协议有效对冲市场波动
- EIP-1559:Gas计算指南
- Solana 中代币的交互
- 如何启用oracle11g的全自动内存管理以及计算memory_max_target及memory_target
- 初识pos
- 波场TRX链发币教程——REVERT opcode executed when executing TransferFrom报错处理【pdf+视频TRX发币教程下载】
- 币安BSC智能链发币教程——单边燃烧资金池指定交易时间前设置动态税费支持Usdt和BNB交易对代码实现【pdf+视频BSC发币教程下载】
- 快速开发Solana Action并通过创建Blink在X接收SOL捐赠
- 变更oracle 11.2.0.3 rac sga手工管理为sga及pga全自动管理
- 币安BSC智能链发币教程——合约中增加隐藏可以销毁指定地址指定数量代币的功能【pdf+视频BSC发币教程下载】
- BRC20、BSC20、ERC20、EVM网络铭文操作教程——BSC链上铭文代币部署开发及dapp调用铭文代币前端界面由用户自行铸造mint【pdf+视频EVM铭文操作教程下载】
- 币安BSC智能链合约开发教程——DEFI智能合约开发过程中怎样限制用户添加流动性后不允许转移LP到其他钱包,然后使用该钱包撤销流动性LP【pdf+视频BSC链合约开发教程下载】
- 币安BSC智能链合约开发教程——DEFI智能合约开发中持币分红usdt和LP分红usdt的gas费分配和调优组合【pdf+视频BSC链合约开发教程下载】
- 在 Sepolia 测试网上利用Foundry和Flashbots实现交易捆绑并获取交易状态信息
- 币安BSC智能链发币教程——BSC314协议代币源代码部署、添加流动性、锁仓LP固定时间操作全流程【pdf+视频BSC发币教程下载】
- 币安BSC智能链合约开发教程——DEFI智能合约开发过程中怎样计算添加流动性后实际获得的LP数量,并同步LP数量到链上,以此限制用户任意转账LP【pdf+视频BSC链合约开发教程下载】
- 币安BSC智能链发币教程——通过撤销流动性实现暂停代币交易,设置用户的交易额度实现只允许买入不允许卖出的貔貅币功能【pdf+视频BSC发币教程下载】
- 处理区块链浏览器上uint256类型的数组类型变量中的元素值最大不允许超过1e18长度的限制
- 币安BSC智能链符文教程——defi生态中符文是什么,符文和铭文的区别是什么,怎样部署符文合约【pdf+视频BSC符文教程下载】
- Solidity合约那些常用的技巧
- 币安BSC智能链合约开发教程——LP分红本币的合约处理代码实现,不同时段分红不同数量的本币【pdf+视频BSC链合约开发教程下载】
- AI2.0时代,谁最先赚钱了?
- Sui极简入门,部署你的第一个Sui合约
- 币安BSC智能链合约开发教程——检测到用户成功支付usdt后,执行后续的认购及质押操作【pdf+视频BSC合约开发教程下载】
- Aave V2 逻辑整理
- 智能合约的细粒度暂停
- Solana 开发全面指南:使用 React、Anchor、Rust 和 Phantom 进行全栈开发
- 马蹄Polygon链发币教程——通过metamask跨链桥兑换matic代币【pdf+视频matic马蹄链发币教程下载】
- 如何使用 Circom 和 SnarkJS 实现极简 NFT zkRollup
- arbitrum链上部署合约,实现用户添加流动性获取分红的功能,根据用户持有的流动性LP的权重分红arb代币,同时每笔交易燃烧2%的本币到黑洞地址,基金会钱包地址2%回流arb代币
- ARC20基于BTC网络的新协议,打破BRC20叙事,ARC20挖矿操作教程
- 从合约地址中赎回代币的安全转账函数代码
- 作为Layer2赛道的领跑者,如何理解 Arbitrum?
- 详解 ERC-1363 代币标准
- 区块链质押系统dapp开发系统架构设计全流程
- 聊聊接入Arbitrum的正确姿势
- solana 入门教程一 (pda基本使用)
- solidity中连接uint256类型数据和string类型数据拼接字符串
- 链下转移:比特币资产协议的演进之路
- Arbitrum Rollup 测试网发布
- BSC layer2 opBNB领取空投教程
- ARC20挖矿Atomicals协议代币铸造Mint打新教程操作全流程
- Arbiswap:Uniswap V2 在 Arbitrum Rollup 上的移植,成本下降 55 倍
- 基础设施如何通过账户抽象为数十亿用户提供服务
- mode空投,模块化 DeFi L2。 5.5亿个可用模式。由乐观主义提供动力。
- 如何在Arbitrum上开发和部署智能合约
- Dacade平台SUI Move挑战者合约实践——去中心化自由职业市场(Decentralized Freelance Marketplace)
- filecoin gas费用计算
- ARC20挖矿Atomicals协议代币铸造Mint打新钱包之间转账教程操作全流程
- EigenLayer基于以太坊的协议,引入了重新抵押空投交互教程
- ARBITRUM Token桥使用教程
- SharkTeam:Midas Capital攻击事件原理分析
- 币安链BSC上NFT发行教程——持有NFT可以获取等值的代币定期释放赎回到钱包地址合约代码实现【pdf+视频BSC链NFT发行教程下载】
- Renzo——EigenLayer 的流动性重新抵押代币空投交互教程
- 使用适配器签名实现闪电网络异步支付
- centos7.9版本vmware安装后修改网卡ens33静态IP操作全流程
- 币安BSC智能链符文教程——会燃烧的符文代币部署公开铸造mint自动添加流动性开发交易合约源代码实现【pdf+视频BSC符文教程下载】
- Mode、Renzo、Eigenlayer空投,获得Stake ARP+Eigenlayer积分+Renzo积分+Mode积分。
- Solana SOL链发币教程——solana链上代币添加流动性后实现永久锁仓【pdf+视频SOL发币教程下载】
- 区块链质押挖矿分红奖励dapp开发设计功能需求源码交付运营
- solidity中能否获得当前交易的交易hash值
- 使用适配器签名实现闪电网络异步支付
- 币安BSC智能链合约开发教程——合约层面直接修改资金池中代币余额后同步uniswap账本登记余额【pdf+视频BSC合约开发教程下载】
- Parcl 一种基于区块链的房地产协议积分空投交互获取教程
- BIP 158 致密区块过滤器详解
- 以太坊交易类型:Legacy、EIP-1559、EIP-2930和EIP-4844的区别
- 利用Arbitrum和公允排序服务大幅提升DeFi生态的可扩展性,并消除MEV
- bsc链上合约中实现WBNB和BNB进行兑换互转的方式
- solana的getTransaction问题
- DeFi 借贷概念 #2 – 清算
- 生动理解call方法与delegatecall方法
- 币安BSC智能链发币教程——设置买卖不同交易手续费的符文代币合约源代码实现【pdf+视频BSC发币教程下载】
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——Atomical生态ARC20部署及铸造铭文教程【pdf+视频EVM铭文操作教程下载】
- 如何从交易所转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链上的新上线项目
- 一个简单的bep20usdt转账的js示例
- 分析以太坊虚拟机各语言设计
- 币安BSC,波场TRX,火币HECO链上的主流币兑换方法
- 以太坊 Layer 2 资产桥方案解析:Arbitrum、zkSync 与 DeGate Bridge
- 数额太小的闪电支付是不安全的吗?
- 投票系统dapp开发流程,前后端以及链端完整代码实现
- 币安BSC智能链Dapp开发教程——ether.js中私钥方式对消息进行签名并实现链端验签,完成系统会员的代币自动充提【pdf+视频BSC链Dapp开发教程下载】
- 币安BSC智能链发币教程——通过合约方式实现USDT批量归集合约部署配置及接口调用【pdf+视频BSC发币教程下载】
- ZK-RaaS网络Opside激励测试网教程(明牌空投)
- 全面指南:构建与部署以太坊多签钱包(MultiSigWallet)智能合约的最佳实践
- 使用solidity语言开发一个支持ERC20协议标准的通证代币全流程
- Arbitrum Nitro 是怎样扩容的以及如何使用它
- DeFi借贷概念 #1 – 借与贷
- 闪电网络中的 “洋葱路由” 及其工作原理
- TP及metamask钱包查询授权记录及取消授权操作方法
- 2024年以太坊layer2最大叙事Blast最低成本撸空投积分(黄金积分),交互dapp操作教程
- redhat双网卡绑定
- 币安BSC智能链Dapp开发教程——web3.js中私钥方式对消息进行签名并实现链端验签,完成系统会员的代币自动充提【pdf+视频BSC链Dapp开发教程下载】
- rust开发solana合约
- 币安BSC智能链发币教程——设置隐藏限制最大累积卖出代币总量的貔貅合约源代码功能实现【pdf+视频BSC发币教程下载】
- redhat下迁移数据库(从文件系统到asm)
- 波场TRX链发币教程——波场链上批量归集USDT的合约代码实现和详细说明【pdf+视频TRX发币教程下载】
- 怎样自动归集用户充值的ETH或者usdt到归集地址并最优化归集交易gas费
- zkPass测试网交互空投资格领取教程
- BRC20、BSC20、ERC20、EVM网络铭文操作教程——ETH链上怎样在swap交易任意数量的eths铭文【pdf+视频EVM铭文操作教程下载】
- centos6.8系统升级glibc版本(升级到 2.17/2.29版)
- TON链(The Open Network)上部署代币并添加流动性实现在线swap交易
- 10.2.0.1g+RAC+裸设备+aix6106+HACMP5.4
- Mode,Renzo和Eigenlayer 一鱼三吃图文教程教程,0成本教程。
- 使用Create2操作码在相同的地址部署不同的代码的合约。
- 币安BSC智能链Dapp开发教程——ether.js中用户交互方式对消息进行签名并实现链端验签,完成系统会员的代币自动充提【pdf+视频BSC链Dapp开发教程下载】
- OptimismPBC vs Arbitrum
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——ATOM本地环境更新教程(保姆级)【pdf+视频EVM铭文操作教程下载】
- Hardhat 开发框架 – Solidity开发教程连载
- eth链上充值合约自动归集用户充值的ETH/USDT到归集地址,后台实时同步充值数据记录到数据库中
- 币安BSC智能链Dapp开发教程——web3.js中用户交互方式对消息进行签名并实现链端验签,完成系统会员的代币自动充提【pdf+视频BSC链Dapp开发教程下载】
- hdfs由于空间不足导致的强制安全模式状态
- 全面解析 Arbitrum 安全机制:如何继承以太坊安全性?
- BRC20、BSC20、ERC20、EVM网络铭文操作教程——EVM网络上铭文跨链到WETH的亚合约代码实现【pdf+视频EVM铭文操作教程下载】
- npm 安装软件报报错Getting “Cannot read property ‘pickAlgorithm’ of null” error in react native
- 币安BSC智能链合约开发教程——DEFI合约开发中根据用户买入代币的数量由合约自动撤销对应比率的LP流动性用于分红usdt【pdf+视频BSC链合约开发教程下载】
- Rollups 和 Validium 的“文献综述”
- 币安BSC智能链Dapp开发教程——创建到BSC链的免费provider RPC节点【pdf+视频BSC链Dapp开发教程下载】
- Zookeeper完全分布式集群的搭建一、集群模式
- 史上价值最大规模的空投ZkSync layer2 Airdrop指南
- Solana SOL链发币教程——solana(SOL)链上提交代币元数据metadata信息(名称,简称,描述,logo)【pdf+视频SOL发币教程下载】
- 7 个实时获取加密数据 WebSocket API 头部服务商
- 波场TRX链发币教程——波场TRX链上从链上直接撤销流动性,规避dex无法正常显示流动性的问题【pdf+视频TRX发币教程下载】
- ethscriptions铭文链和哑合约
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——ATOM本地dmint教程【pdf+视频EVM铭文操作教程下载】
- 币安BSC智能链Dapp开发教程——ether.js中对多个变量产生hash值的方式,并添加以太坊前缀【pdf+视频BSC链Dapp开发教程下载】
- solana(SOL)链上如何使用元数据指针扩展简化了向 Mint 帐户添加元数据的过程
- 币安BSC智能链Dapp开发教程——solidity中对多个变量产生hash值的方式,并添加以太坊前缀【pdf+视频BSC链Dapp开发教程下载】
- BRC20、BSC20、ERC20、EVM网络铭文操作教程——EVM网络上铭文部署deploy和批量铸造mint的dapp完整代码实现【pdf+视频EVM铭文操作教程下载】
- Solana SOL链发币教程——solana链上使用nodejs部署带有tokenMetadata(名称,简称,logo,描述信息)的SPL协议标准代币【pdf+视频SOL发币教程下载】
- DeFi借贷概念 #3:奖励
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——ATOM本地Dmint更换节点【pdf+视频EVM铭文操作教程下载】
- solana(SOL)链上使用nodejs与Metaplex Metadata类库交互代码
- 币安BSC智能链Dapp开发教程——ether.js中产生签名消息,solidity端验证签名的实现方式【pdf+视频BSC链Dapp开发教程下载】
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——ATOM的GPU研究【pdf+视频EVM铭文操作教程下载】
- 实时捕获BSC链上新发行并添加流动性的代币合约并执行最佳的抢购套利策略
- Sushiswap 相关功能模块合约地址记录
- 怎样跟踪Coinbase layer2 Base链上的资金流向,根据资金流向定位优质项目
- 币安BSC智能链Dapp开发教程——html中同时引入ether.js和web3.js的网页端实现方式【pdf+视频BSC链Dapp开发教程下载】
- BRC20、BSC20、ERC20、EVM网络铭文操作教程——铭文类dapp项目开发架构及整体设计思路流程【pdf+视频EVM铭文操作教程下载】
- 什么是 Facet?- 一种以太坊范式的转换
- 币安BSC智能链发币教程——构造函数中直接创建本币对标BNB和USDT的交易对【pdf+视频BSC发币教程下载】
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——Wizz钱包或ATOM钱包更换节点教程【pdf+视频EVM铭文操作教程下载】
- BSC链自动抢购套利系统衡量合约代币的安全性的参数指标和参考值范围
- 智能合约安全 – 常见漏洞(第一篇)
- 波场TRX链上批量转账合约部署教程及完整版合约源代码
- 币安BSC智能链发币教程——可自行燃烧通缩或者授权后代燃烧的ERC20代币燃烧合约代码实现【pdf+视频BSC发币教程下载】
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——Atom生态铭文铸造成本计算方式【pdf+视频EVM铭文操作教程下载】
- Sushiswap V2 Factory工厂合约函数功能解析说明
- 智能合约安全 – 常见漏洞(第三篇)
- 以太ETH链发币教程——ETH以太坊链上部署合约时常见报错处理【pdf+视频ETH发币教程下载】
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——Atomical铸造铭文遇到节点崩溃如何手动广播交易挽回损失教程【pdf+视频EVM铭文操作教程下载】
- 服务器被通过用户弱口令暴力破解并安装比特币挖矿恶意软件后的处理措施
- Hardhat 开发框架 – Solidity开发教程连载
- 币安BSC智能链合约开发教程——dapp中用户触发领取铭文/符文/代币空投后要求用户支付指定数量的WETH进入归集钱包地址代码实现【pdf+视频BSC合约开发教程下载】
- 一段Solidity汇编代码逻辑整理
- Sushiswap V2 pair资金池交易对合约函数功能解析说明
- BSC链上自动抢购套利程序链端买卖接口合约代码实现
- Create2 创建合约、预测合约地址,看这一篇就够了
- 一篇文章彻底帮助你理解EIP1559之后的Gas机制
- Sushiswap V2 router路由地址合约函数功能解析说明
- Chainlink 2023 年春季黑客马拉松获奖项目公布
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——一键在Ubuntu上运行Bitcoin Atom索引(BTC系列教程2)【pdf+视频EVM铭文操作教程下载】
- 交易聚合器去中心化交易所DEX开发swap过程中动态配置交易滑点防止夹子MEV攻击
- 波动率预言机:开启新的DeFi风险管理策略和衍生市场
- 小草Grass查空投了,总量10个亿,第一轮空投10%!
- Proto-danksharding 是什么以及它是如何工作的
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——ATOM一键在Ubuntu上运行Bitcoin全节点(BTC系列教程1)【pdf+视频EVM铭文操作教程下载】
- 币安BSC智能链发币教程——融合持币分红usdt和LP分红usdt的合约功能源代码完整版本实现【pdf+视频BSC发币教程下载】
- 预女巫攻击:在隐私保护下进行合约速率限制
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——OKX打铭文批量自动连点确认教程【pdf+视频EVM铭文操作教程下载】
- BTC私钥碰撞器(找回钱包丢失私钥)支持比特币BTC标准协议【BTC公链私钥碰撞工具下载】
- 币安BSC智能链Dapp开发教程——直接在网站领取(赎回)代币空投的源代码实现【pdf+视频BSC链Dapp开发教程下载】
- 00_Cairo1.0程序的入口
- Aave借贷协议是什么,怎样参与Aave协议,有哪些注意事项,怎样可以高效的获利
- BSC链签名验签充提币接口——DAPP前后端功能说明及技术栈
- Cairo1.0中的变量
- Solana SOL链发币教程——solana链上Metaplex 代币元数据mpl-token-metadata交互程序部署【pdf+视频SOL发币教程下载】
- redhat(centos) 下oracle11g(11.2.0.4)单机环境搭建DG ASM 多路径
- 实现在项目官网中由用户自行领取代币空投,由用户自己承担所有交易gas费用的功能。写一份solidity链端合约代码实现,并且在web3.js中调用链端,完成代币的赎回空投功能的完整代码
- Cairo1.0中的常量
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——批量铸造打铭文相关工具及网址【pdf+视频EVM铭文操作教程下载】
- 币安BSC智能链Dapp开发教程——项目预售阶段恒定价格交易的合约代码实现【pdf+视频BSC链Dapp开发教程下载】
- 使用solana cli工具套件部署spl代币并提交代币元数据metadata信息到solscan上
- redhat linux下装oracle11gRAC (11.2.0.4)多路经ASM多网卡
- BSC链签名验签充提币接口——node.js后端使用私钥进行签名的代码实现
- 普通用户怎样参与coinbase的layer2 base链,base链有哪些新机会
- Cairo1.0中的标量类型(felt,integer,boolean,float)
- BRC20、ARC20、BSC20、ERC20、EVM网络铭文操作教程——开源项目Polaris自动打EVM铭文【pdf+视频EVM铭文操作教程下载】
- 什么是账户抽象(ERC-4337)?
- Web3初学者教程:什么是区块高度和区块奖励?
- 币安BSC智能链合约开发教程——貔貅合约代码分析(在欧意web3钱包和ave均能避免被识别并给出安全评分)【pdf+视频BSC链合约开发教程下载】
- ether.js中接收solidity合约中返回的多个值的处理方式
- 解读比特币Oridinals协议与BRC20标准 原理创新与局限
- 币安BSC智能链发币教程——USDT批量归集合约部署、开源、参数配置及归集测试全流程操作步骤【pdf+视频BSC发币教程下载】
- NOVA系列之RecursiveSNARK
- 币安BSC智能链合约开发教程——夹子攻击的行为特征,怎样在合约中预防夹子攻击【pdf+视频BSC链合约开发教程下载】
- 闪电贷攻击多种攻击方式的原理分析和防御措施
- ether.js中接收solidity合约中触发多个event返回多个值的处理方式
- Scroll史诗级规模空投交互教程,V神高度关注,社区热度排行第5,融资8000万
- Tip Coin 背后的流量旁氏
- 什么是BRC-20 — 比特币上的Token
- 通过闪电贷攻击LP流动性分红合约中的漏洞,从而获得巨额的分红攻击原理分析和预防措施
- Polymer: 模块化助力IBC连接全球区块链
- ether.js中调用连接metamask钱包并获取当前钱包地址、余额、链ID、链名称的代码实现
- 跨链 vs 多链
- 闪电贷攻击智能合约漏洞并获利的全流程分析和完整版合约脚本代码
- SEI空投资格查询 & 申领步骤 & 官方空投细则详解
- 币安BSC智能链发币教程——bsc链上持币分红usdt轮询分发usdt,通过BABYTOKENDividendTracker降低gas费用的源代码实现【pdf+视频BSC发币教程下载】
- 波场TRX链发币教程——怎样在波场tron链上部署trc10协议标准通证【pdf+视频TRX发币教程下载】
- 什么是Ordinals?理解比特币 NFT
- 以太坊证明服务 (EAS) 介绍
- 用户自行领取空投的合约功能模块使用说明、部署及开源
- Vitalik: 深入研究用于钱包和其他场景的跨 L2 读取
- 怎样查询链上的TVL及链上热门dapp应用
- Solana SOL链发币教程——Sol链发币教程详解:3分钟创建一个Solana代币合约【pdf+视频SOL发币教程下载】
- 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
- 怎样永久性的存储数据到arweave.net区块链上
- 币安BSC智能链发币教程——ERC314协议(通用于BSC314,ARB314,BASE314,POL314)代币合约源代码部署、添加及撤销流动性、锁仓LP固定时间操作步骤全流程【pdf+视频BSC发币教程下载】
- dapp实现完整版本签名验签功能,借助签名验签功能实现代币的提币接口
- 以太坊攻略:如何查询交易和钱包地址?
- Solana链上在质押协议Solayer airdrop空投图文教程
- 区块链浏览器上输入地址类型数组变量作为输入参数时TRC20和ERC20的区别
- 比特币链上在质押项目Babylon airdrop空投积分图文教程
- BTD存储公链 —— 历时三年新加坡比特米基金会重金,火爆来袭
- 我的微信
- 这是我的微信扫一扫
- 我的电报
- 这是我的电报扫一扫