以支付为例,做一个区块链的托管,实现一个h5/Java版本的交易demo

以支付为例,做一个区块链的托管,实现一个h5/Java版本的交易demo


我的博客园地址 https://www.cnblogs.com/huangyingsheng/p/19685835


web2时代,需要办理bank card💳储蓄货币,在各平台或线下扫码支付,完成购物。

在web3时代,付款不再通过bank card💳结算,而是通过去中心化的钱包。

为此,我们需要准备web3交易需要的一些条件。

1.智能合约

web3的支付不再通过调用微信/支付宝/银行等接口来完成对用户的付款,而是通过 智能合约+用户钱包 的形式完成扣款,智能合约的功能很多,可以自动托管,代币,换币,DAO投票等等,此次,我们使用智能合约,完成一次自动托管的实现,所以我们需要一个智能合约,智能合约是通过solidity语言编写的,部署在链上,部署成功后会有一个合约地址,通过这个合约地址,我们就可以像一家一人公司一样到处收款了,收到的款项都会在合约中暂存,用户同意后,就会入账到钱包里,成为可以使用的余额了。

2.钱包

web3的时代,钱包是一个人的身份ID,也是交易的工具,以Mxxxxxx钱包为例。

开始

1.准备钱包,以 MetaMask 为例,下载的谷歌浏览器插件

选择人们网络,找到Custom,选择Sepolia,Sepolia是免费测试研发使用的代币
初次使用,SepoliaETH代币是0,可以免费领取

搜索找到 Sepolia PoW Faucet,免费领取代币,像图中一样。

准备好钱包后,我们就可以开始准备智能合约了,我们使用智能合约写一个简单的托管程序,功能是用户的资金付款后,进入我们的合约,用户确认收货后,合约自动将资金转入我们的钱包里。

下方是准备好的一段Solidity智能合约的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// SPDX-License-Identifier: GPL-3.0
//限制使用的solidity版本
pragma solidity >=0.8.2 <0.9.0;

/**
* @title Escrow
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract Escrow {

//收款方(一人公司,散户,中大型公司)的钱包地址
address public seller;

//发布该项目的时候,需要指明收款方钱包地址,完成注入
constructor(address _seller) {
seller = _seller;
}

// 买家付款 "external payable 会唤起钱包进行交易,此时钱存在于合约中,也就是该智能合约"
function deposit() external payable {
//付款金额大于0才能通过校验
require(msg.value > 0, "Must send ETH");
}

// 查看合约余额
function getBalance() external view returns (uint256) {
return address(this).balance;
}

// 买家确认收货
function confirmReceived() external {
// 获取余额
uint256 balance = address(this).balance;
// 校验余额
require(balance > 0, "No ETH to release");
// seller 就是发布该项目的时候指定的收款人钱包地址,此时调用call方法,将合约中的余额全部提取到钱包中
(bool success, ) = seller.call{value: balance}("");
require(success, "Transfer failed");
}
}

打开 remix.ethereum.org ,如下图所示,将上方的智能合约代码按照图中的步骤创建文件,粘贴代码,编译上传到链上节点。

获得链上的合约地址和ABI之后,我们可以做一个H5版本的支付,收货功能,体验一下链上交易。

下方准备一段H5代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>DApp Test</title>
<script src="https://cdn.jsdelivr.net/npm/ethers@6.6.2/dist/ethers.umd.min.js"></script>
</head>

<body>
<button id="connect">连接钱包</button>
<button id="pay">支付</button>
<button id="confirmBtn">确认收货</button>
<h5 id="show_log">...</h5>

<script>
let provider;
let signer;
let contract;

const contractAddress = "0xx...c6"; // 替换为你的合约地址
const abi = []; // 替换为你的合约ABI

async function init() {
provider = new ethers.BrowserProvider(window.ethereum);

await provider.send("eth_requestAccounts", []);

signer = await provider.getSigner();

contract = new ethers.Contract(contractAddress, abi, signer);

window.contract = contract;

document.getElementById("show_log").innerText =
"钱包初始化成功,地址: " + (await signer.getAddress());

console.log("合约初始化完成");
}

async function pay() {
const tx = await contract.deposit({
value: ethers.parseEther("0.01"),
});

console.log("tx:", tx); //此处可以得到 tx.hash ,通过这个 hash 可以在区块浏览器上,或者java后端通过远程调用查看交易详情,获取交易状态等信息
document.getElementById("show_log").innerText =
"支付中... tx: " + tx.hash;

const receipt = await tx.wait();

console.log("receipt:", receipt);
}

async function confirm() {
const tx = await window.contract.confirmReceived();
console.log("msg:", "确认收货中...");
await tx.wait();
console.log("msg:", "收货确认完成 tx: " + tx.hash);
}

document.getElementById("connect").onclick = init;
document.getElementById("pay").onclick = pay;
document.getElementById("confirmBtn").onclick = confirm;
</script>
</body>
</html>

在浏览器中打开上方的H5页面,主义H5代码中要替换之前保存的合约地址和ABI信息!!!

可以通过创建新的账户,领取免费的测试Sepolia代币,往直前的账户里支付,确认收货,发现金额正常的增加,交易成功。

按钮下方的tx后方的hash可以作为查询支付状态的key,在java后端中后台查询链上交易情况,但是要先做一些准备。

首先,需要注册一个key用来连接链上RPC,infura注册就可以免费用一个key,注册后跟着页面提示操作后,copy对应的key

在钱包中,我们可以找到Sepolia测试的RPC地址

        <!-- Web3j 核心依赖 -->
        <dependency>
            <groupId>org.web3j</groupId>
            <artifactId>core</artifactId>
            <version>4.11.0</version>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static void main(String[] args) {

// 连接到 Ethereum 网络
String nodeUrl = "https://sepolia.infura.io/v3/替换为你的key"; // 或其他 Ethereum 节点 URL
Web3j web3 = Web3j.build(new HttpService(nodeUrl));

// 交易哈希
String txHash = "替换为你的hash"; // 使用实际的交易哈希

try {
// 获取交易回执
EthGetTransactionReceipt receipt = web3.ethGetTransactionReceipt(txHash).send();

if (receipt.getTransactionReceipt().isPresent()) {
TransactionReceipt transactionReceipt = receipt.getTransactionReceipt().get();

// 检查交易是否成功 0x1代表成功
if (transactionReceipt.getStatus().equals("0x1")) {
System.out.println("Transaction successful!");
// 在这里你可以进行成功后续的业务处理
} else {
System.out.println("Transaction failed!");
// 失败的处理逻辑
}
} else {
System.out.println("Transaction not mined yet.");
// 交易尚未被打包进区块,可能需要继续查询
}
} catch (Exception e) {
e.printStackTrace();
}

}

之前的转账是以H5页面唤起钱包支付的方式实现,但是Java也可以实现直接通过私钥和钱包地址完成转账。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 public static void main(String[] args) throws Exception {
// 1 连接区块链RPC
Web3j web3j = Web3j.build(
new HttpService("https://sepolia.infura.io/v3/替换为 your key")
);

// 2 A 钱包私钥
Credentials credentialsA = Credentials.create(
"上图中复制到的钱包私钥"
);

// 3 钱包 B 地址(收款地址)
String addressB = "我的第二个钱包地址(领免费代币的那种钱包地址,非钱包私钥)";

// 4 打印 A 的余额
BigInteger balanceA = web3j.ethGetBalance(credentialsA.getAddress(),
DefaultBlockParameterName.LATEST)
.send().getBalance();
System.out.println("钱包 A 余额: " + Convert.fromWei(balanceA.toString(), Convert.Unit.ETHER) + " ETH");

// 5 转账操作
System.out.println("开始向 B 转账 0.01 ETH...");
TransactionReceipt receipt = Transfer.sendFunds(
web3j,
credentialsA,
addressB,
BigDecimal.valueOf(0.1), // 转账 0.01 ETH
Convert.Unit.ETHER
).send();

System.out.println("交易完成,交易哈希: " + receipt.getTransactionHash());
}

之前我们调用智能合约是通过H5的形式,唤起钱包插件完成支付,智能合约的调用,Java也可以操作。

首先需要本地安装web3

brew install web3j 

如果上方因为网络等问题,可以采用离线安装

1
2
3
4
5
6
7
8
9
10
11
12
13
https://github.com/LFDT-web3j/web3j-cli/releases/tag/v1.8.0
例: 下载 web3j-shadow-1.8.0.tar


tar -xvf web3j-shadow-1.8.0.tar
cd web3j-shadow-1.8.0

nano ~/.zshrc.pre-oh-my-zsh
export PATH=$PATH:pwd/web3j-shadow-1.8.0/bin
source ~/.zshrc.pre-oh-my-zsh

web3j --version

安装好web3j后,我们需要将之前智能合约的ABI信息在项目中新建文件保存。

通过下方web3j的命令对我们的ABI文件生成一个Counter.java的类文件,通过该文件就可以访问智能合约中的方法了

1
2
3
4
5
web3j generate solidity \
-a Counter.abi \
-o src/main/java \
-p contracts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) throws Exception {
// 1 连接区块链RPC
Web3j web3j = Web3j.build(
new HttpService("https://sepolia.infura.io/v3/your key")
);
// 2 A 钱包私钥
Credentials credentialsA = Credentials.create(
"your 钱包私钥"
);
// 6️⃣ 调用你的 Counter 合约写操作(原有逻辑)
String contractAddress = "0x4Cab79Bf05CC942F6c50aF031C9b8cf758bC7A7E";
Counter counter = Counter.load(
contractAddress,
web3j,
credentialsA,
new org.web3j.tx.gas.DefaultGasProvider()
);
System.out.println("写入 Counter 合约值 100...");
BigInteger balance = counter.getBalance().send();
System.out.println("余额:" + balance);
//有其他需求,可以在智能合约中编写更多的逻辑
System.out.println("操作完成!");
}


总结:智能合约是链上服务,每次读操作免费,但是写操作都需要手续费,可以做托管、代币,换币,DAO投票,但是不适合高并发,所以业务逻辑还是会以私人服务器或者公司服务器的形式运行,只有关键的操作走链上,比如扣款,换币种等关键操作在链上完成。