07-2 Ethereum 钱包开发流程

1.以太坊基础知识

智能合约:

Smart Contracts 是以太坊最重要的特性之一,智能合约让以太坊有更多的可能性,让链不仅仅是链,还可以做很多的应用,智能合约是一段存储在以太坊区块链上的代码,可以自动执行协议的条款。例如,可以创建一个智能合约来管理众筹活动,当众筹达到目标时,合约会自动将资金转给项目方。

单位换算

1 Ether(ETH)=1 x 10^3 Finney = 1 x 10^6 Szabo = 1 x 10^9 Gwei = 1 x 10^12 Wei

以太坊虚拟机(EVM)

Ethereum Virtual Machine EVM 是以太坊的核心组件,EVM是一个图灵完备的虚拟机,他可以执行用以太坊脚本语言(Solidity)编写的任意代码。EVM使得智能合约的执行变得可能。也就是说你可以自己的代码放到EVM上去执行,

Dapps(去中心化应用)

Decentralized Applications Dapps 是基于以太坊区块链和智能合约构建的应用程序,与传统应用不同,Dapps没有中央服务,数据和应用逻辑是分布式的,这使得他们更加透明和安全。

ERC标准

ERC标准是以太坊的技术规范,用于创建代币和其他合约,最著名的是ERC-20标准,他定义了一种通用的接口,使得代码可以在不同的DApp之间互操作。ERC 提案仅仅是以太坊社区的一些建议和规范,并没有强制执行的机制。开发者可以选择是否遵循这些标准,但遵循 ERC 标准通常会提高智能合约和代币的互操作性,并使其更易于与其他以太坊应用和工具进行集成。大家遵守这个标准之后,更便于代币的流通,

ERC-721是另一种标准,用于创建非同质化代币(NFT)这些代码具有独特性和不可互换性

ETH1.0和ETH2.0

ETH1.0

共识机制:工作量证明(proof of work PoW):ETH1.0使用的是PoW共识机制,旷工通过解决复杂的数学问题来验证交易并添加区块,添加区块的旷工可以获得代币奖励,解决数学难题需要耗费巨大的GPU去运算,这个就是传统意义上的“挖矿”,这个过程能耗非常高,效率很低,且容易造成不平衡,就是拥有更多的显卡的人可以挖的更多的区块,

性能:吞吐量: ETH1.0每秒只能处理大约15-30笔交易,存在拓展性瓶颈,尤其是在网络负载(交易量大)的高峰期,交易确认时间和手续费会显著增加。

安全性:矿池集中化:PoW机制导致矿池的集中化问题,少数大型矿池控制了大部分的算力,有显卡的人就有算力,就可以挖掉区块。可能带来中心化风险

智能合约和DApps:ETH1.0支持智能合约和去中心化应用(DApps),比如去中心化金融(DeFi)、游戏和供应量管理

ETH-2.0

  • 共识机制: 权益证明(Proof of Stake, PoS):ETH2.0 使用PoS共识机制,验证者通过质押ETH来获得验证区块的机会。PoS大幅减少能耗,提高了效率和安全性。比如说全网有10个验证者,每个人质押不同数量的代币来获得出块的机会,最后出块的人会奖励代币的

  • 性能:

    • 分片链(Sharding chains ):ETH2.0引入了分片技术,通过将区块链分成多个并行链(分片 shard),每个分片处理不同的交易和智能合约,从而显著提高了网络的吞吐量和处理能力

    • 信标链(Beacon Chain):ETH2.0的核心链,负责协调分片和验证者活动,确保整个网络的同步和共识,信标链负责验证和维护网络的共识,并引入了验证者角色。信标链通过随机性机制来选择验证者参与区块的提议和验证。这种随机性确保了公平性和安全性,并防止潜在的攻击行为。信标链还引入了轮次(Epoch)的概念,将时间划分为较长的周期,以便进行验证者的轮换和共识机制的调整

  • 安全性:去中心化和抗攻击性:PoS机制下,恶意攻击者需要持有大量的ETH,成本高昂,使得网络更安全,此外,前面说的信标链通过随机性机制来选择验证者参与区块提议,有随机性,进一步分散风险。

  • 智能合约和DApps:ETH2.0保留了对智能合约的DApps的支持,同事通过更高的吞吐量和更低的交易费用,提升了用户体验和应用性能。

  • 过渡过程:合并(The Merge):ETH2.0并不是从零开始的新区块链,而是对ETH1.0的升级而来,合并将ETH1.0的现有链和ETH2.0的PoS链结合,实现无缝过渡。

主要改进和优点

  • 能耗:ETH2.0通过 PoS 机制大幅减少了能源消耗,相比 PoW 机制更环保(降交易费了)
  • 扩展性:分片技术和信标链的引入显著提高了网络的扩展性和吞吐量(大哥带小弟)
  • 去中心化:ETH2.0通过随机选择验证者和经济激励机制,增强了去中心化程度和安全性(随机验证者,叫你们挖挖挖矿,挖不到了吧)
  • 经济模型:ETH2.0引入了新的经济模型,通过质押和奖励机制,进一步激励网络参与者,提高网络的稳定性和安全性(验证者和抵押、奖励和惩罚、利息和通胀、质押和解之一,压币有收益,做恶有惩罚)

1.3 ETH2.0中的Epoch,Slot,Block和Block状态

epoch

  • ETH2.0 按照epoch 出块 (一个epoch一个纪元)
  • 每一个epoch有32个slot (slot 时隙)
  • 每一个slot 可以承载1个块

Slot(时隙)

  • 定义:Slot 是以太坊2.0中最基本的时间单位,每个slot 都有一个指定的时间长度。在每个slot 中,可能会有一个区块被提议并添加到链中。
  • 时间长度:一个slot 的长度为12秒。这意味着没12秒会有一个新的slot。
  • 功能:在每个slot 中,网络中的验证者将有机会提议一个新的区块。这些提议者是通过权益证明(PoS)随机选择的。

Epoch(纪元)

  • 定义:Epoch 是由多个连续的slot 组成的更长时间段。在Eth2.0中,Epoch 用于管理和组织验证者的活动。
  • 组成:一个Epoch 由 32个 slot 组成。
  • 时间长度:由于一个slot 是12秒,一个Epoch 的总长度就是12 * 32=384秒(6.4分钟)
  • 功能:Epoch 是用来实现共识机制的一部分。在每个Epoch 结束时,网络会进行状态和共识的检查和调整,包括对验证者的奖励和惩罚。

Block(区块)

  • 定义:Block 区块一个是包含交易和其他所有相关数据的记录单元。所有的数据都会包在这个块中,在ETH2.0中,每个 slot 可能会有一个区块被提议,只是提议,但不能保证每个 slot 都有区块的。
  • 内容:一个区块包含区块头交易列表状态根哈希签名等数据。
  • 创建过程:在每个slot 开始时,网络会随机选出一个验证者提议区块。该验证者将创建一个包含新交易和其他必要信息的区块,并广播到全网络上。

Safe (安全)

“Safe”状态指的是一个区块已经被多数验证者接受和认可,并且他很可能会成为最终的区块,但还没有达到完全最终确定的状态。

  • 条件:一个区块在被认为是“safe”时,意味着它已经收到了足够多的验证者投票(attestations),通常超过了一个特定的阈值,但还没有达到最终确定的标准。
  • 安全性:在“safe”状态下,区块的存在是相对安全的,不太可能被回滚或者被另一个不同的区块链分支所替代,也就是说不太可能提议了另一个区块。
  • 作用:这个状态用来提高网络对区块的信任度,即是在它还没有被完全最终确定之前。它帮助节点和用户判断哪些区块在短期内是可信的,一个中间状态

Finalized(最终确定)

“Finalized”状态指的是一个区块已经被永久地添加到区块链中,并且不可能被回滚或替代。这是区块链中最强的确认状态。

  • 条件:一个区块被认为是“finalized”时,必须通过了严格的共识验证,通常需要超过2/3的验证者投票同意。具体来说两个联系的epoch被最终确定时,意味着在这两个epoch之间的所有区块都被最终确定。
  • 安全性:一旦区块达到“finalized”的状态,它就不可逆转,保证了区块链的最终一致性和数据的永久性。这种状态防治了分叉和双花攻击的可能性。
  • 作用:最终确定的区块为用户和应用提供了最高级别的交易安全性和网络信任度。

1.4 以太坊钱包确认位

finalized

最终确认位之前的交易会被并入系统

2.离线地址生成

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
package main

import (
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum/crypto"
"github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39"
)

// 生成一个以太坊的钱包
func main() {
fmt.Println("开始")
// 首先你可以生成一个随机熵,熵源助记词是BIP-39,
entropy, _ := bip39.NewEntropy(128)
fmt.Println("entroy:", entropy)
// 通过熵源生成助记词 ==> 注意,不一定要有助记词才有种子,只是助记词方便备份,可以转成种子,你要直接由种子也行,但不好记
mnemonic, _ := bip39.NewMnemonic(entropy)
fmt.Println("mnemonic:", mnemonic)
// 通过助记词生成种子Seed
seed := bip39.NewSeed(mnemonic, "") // password盐值不要加
fmt.Println("seed", seed)

// 接下来就是将种子恢复出主私钥 masterKey 这里进入到了BIP-32了 a
masterKey, _ := bip32.NewMasterKey(seed)
// 注意,此时还是主私钥,接下来要派生子私钥,派生出来的子私钥才是真正的“私钥”才能对应链的公钥,才能解压缩出地址
fmt.Println("masterKey", masterKey)

// 现在要派生出对应以太坊的子私钥,遵循BIP-44
// 接下来进入BIP-44 完成派生,完成对应path参数 m / purpose' / coin_type' / account' / change / address_index
// 通过主私钥派生出子私钥,FirstHardenedChild = uint32(0x80000000) 是一个常量,对应强化派生范围
key, _ := masterKey.NewChildKey(bip32.FirstHardenedChild + 44) // purpose' : 44 是固定值,即BIP-44标准,强化派生
key, _ = key.NewChildKey(bip32.FirstHardenedChild + uint32(60)) // coin_type' :60是以太坊标识', 继续强化派生
key, _ = key.NewChildKey(bip32.FirstHardenedChild + uint32(0)) // account' : 0 标记账户类型,从0开始,强化派生
key, _ = key.NewChildKey(uint32(0)) // change :0 外部可见地址, 1 找零地址(外部不可见),通常是 0,普通派生
key, _ = key.NewChildKey(uint32(0)) // 地址索引 0 1 2 3 这样索引,普通派生
// 派生完毕,对应的path 就是 " m/44'/60'/0'/0/0 "

// 子私钥key 已经出来了,先打印私钥,key.Key就是私钥,注意要转化进制
ethPrivateKey := hex.EncodeToString(key.Key) // 编码成字符串 , 这里打印出来的私钥就可以
fmt.Println("privateKey", ethPrivateKey)
// 子私钥 key 里面就包含他对应的公钥属性,拿出来即可,因为公钥是私钥椭圆曲线加密得来的结果
ethPublicKey := hex.EncodeToString(key.PublicKey().Key) // 编码成字符串
fmt.Println("ethPublicKey", ethPublicKey)

// 现在拿到公钥了,先对公钥进行压缩 keccak256 压缩成 32 byte
compressPubKey, _ := crypto.DecompressPubkey(key.PublicKey().Key)
fmt.Println("compressPubKey", compressPubKey)
// 压缩32字节后取最后 20 byte 就是地址了
ethAddre := crypto.PubkeyToAddress(*compressPubKey).Hex() // Hex是16进制转字符串
fmt.Println("ethAddre", ethAddre) // ethAddre 0x38B59D6D4ef6A4991926Cf04c7c2092a0E86140F

// 最后把 ethPrivateKey 导入到 metaMask 里面去验证下,私钥和地址对上了没
}

用python写一遍

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
from bip32utils import BIP32Key
from bip32utils import BIP32_HARDEN
from bip39 import Mnemonic
from ethereum.utils import privtoaddr

def eth_wallet():
# 生成助记词和种子
entropy = Mnemonic().generate_entropy(128)
mnemonic = Mnemonic().to_mnemonic(entropy)
seed = Mnemonic().to_seed(mnemonic)

# 派生主私钥
bip32_key = BIP32Key.fromEntropy(seed)

# 派生以太坊子私钥
path = "m/44'/60'/0'/0/0"
key = bip32_key.ChildKey(
BIP32_HARDEN + 44
).ChildKey(
BIP32_HARDEN + 60
).ChildKey(
BIP32_HARDEN + 0
).ChildKey(
0
).ChildKey(
0
)

# 获取以太坊私钥
eth_private_key = key.WalletImportFormat()

# 获取以太坊地址
eth_address = privtoaddr(key.PrivateKey().to_string()).hex()

print("助记词:", mnemonic)
print("私钥:", eth_private_key)
print("地址:", eth_address)

eth_wallet()

3.离线交易签名

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package main

import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"log"
"math/big"
)

var (
PrivateKeyStr = "c535faxxxxxxxxxxxxxxxxxxxxxxxxx" // go生成的账户 发送发私钥
fromAddrStr = "0xa3856a939A623EdBde8f908037d3F33FceBC5408" // 对应go生成的账户 发送方地址
toAddrStr = "0x38B59D6D4ef6A4991926Cf04c7c2092a0E86140F" // 接收方地址
ethUrl = "https://eth-sepolia.g.alchemy.com/v2/fzgfj4QuLlNEyn2LrLZsseBAClGdMnyP" //API URL
)

func main() {

// 把私钥个地址转码一下
privateKey, _ := crypto.HexToECDSA(PrivateKeyStr)
toAddr := common.HexToAddress(toAddrStr)

// 新建连接,连上以太坊
client, err := ethclient.Dial(ethUrl)
if err != nil {
log.Fatal(err)
}

// Get the balance of an account 第0步:查询账户余额,要先确认自己有没有钱
fromAddr := common.HexToAddress(fromAddrStr)
balance, err := client.BalanceAt(context.Background(), fromAddr, nil)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Account balance: %d\n", balance) // 1008465873901900000

// Get the latest known block 第0步:获取最新块高,刷新
block, err := client.BlockByNumber(context.Background(), nil)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Latest block: %d\n", block.Number().Uint64()) //6055020

// 生成交易数据,这里注意fromAddr要转成 account common.Address对象

// 第一步拿nonce,这个拿nonce的的方法已经封装好了的,直接用就可,也可以去网站上copy url下来发请求,不过那样不优雅
nonce, err := client.PendingNonceAt(context.Background(), fromAddr)
if err != nil {
log.Fatal(err)
}
fmt.Printf("nonce: %d\n", nonce)

// 拿到gasPrice ,也可以不用拿,直接指定就可以
gasPriceNow, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatalf("Failed to retrieve gas price: %v", err)
}
fmt.Printf("gasPriceNow: %d\n", gasPriceNow)

gasPrice := big.NewInt(2000000000)
amount := big.NewInt(1100000000000000)
gasLimit := uint64(21208)
//gasPrice := big.NewInt(1000)
data := []byte("Hello, World!")

// 第二步 构建交易对象 ---> 生成一个未签名的交易
tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: &toAddr,
Value: amount,
Gas: gasLimit,
GasPrice: gasPrice,
Data: data,
})

// 第三步 对交易进行签名,签名前先拿到链ID,即你要上的链
chainID, err := client.NetworkID(context.Background()) // 拿到链ID
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) // 对交易进行签名
if err != nil {
log.Fatal(err)
}

// 第四步 发送交易,这一步就是广播
err = client.SendTransaction(context.Background(), signedTx) // 广播出去
if err != nil {
log.Fatal(err)
}

fmt.Printf("转账交易已发送,交易哈希\n:%s\n", signedTx.Hash().Hex())
// 第一笔交易哈希:0xb4f9ff541c3423e5bbffbb63f78fed2b384cde60b74b9a3838f05eb172c65e90 , 第一次发交易的时候gasPrice 花费的比较低,所以第二次改了gasPrice后再发
// 前面的一直在pending中 有点问题,
// 第二笔交易哈希:转账交易已发送,交易哈希:0xd65407692e1f872d47ce1231a80d9afbf0dbcf8336362f78262d4fd872a55c81
// 第三笔交易哈希:转账交易已发送,交易哈希:0xcb297210b49a5a3747c27783ab3610b8b287134792e50ccbf6afe86c6c96edfd
// 第四笔交易哈希:转账交易已发送,交易哈希:0xc922a93fd5a5eabe7819623a65aa6b572e755658f3a9f24e71a64f3d533b1a87
// 第五笔交易哈希:转账交易已发送,交易哈希:0x4a8e649836a69d22e2b979c7330f03b97e7017b4a89d27e9c646051725f89a2c
// 第六笔交易哈希:转账交易已发送,交易哈希:0xae0b39c8cb2e02f7c647778f4325f0cca6392fe54c8f144a8e4ae58da79484eb
// 显示pending 然后转indexing状态 最后Success成功
// pending 状态: This txn hash was found in our secondary node and should be picked up by our indexer in a short while.
// indexing 状态:This transaction has been included and will be reflected in a short while.

}

4.以太坊 RPC 接口

这里在做的就是扫链,钱包都要扫链

4.1.检查网络的 RPC 接口是否可以

先调接口看下链是不是处于活跃的状态,如果是才能继续下去

4.2.获取最新块高

调eth block number获取到最新的快高是多 比如说当前是第101个块

4.3.根据块高获取块里面的信息

从我之前上一次扫到的块(比如说第100)到我这一次扫到的块(第101)之间,扫到交易就下一步

4.4.根据交易 Hash 获取交易详情

扫到交易之后就要去解析交易。然后判断from 和to 的地址是什么地址,来判断是转出还是转入是归集还是冷热互转

4.5.获取交易状态

解析交易就是获取交易状态是成功的吗,fail 还是 success

4.6. 获取签名需要的参数 Nonce

4.7. 获取签名需要的参数 Gas

  • 这里其实不要用你获取到的当前gasprice,你可以调高一点,更容易被链上处理

4.8. 发送交易到区块链网络

  • 请求参数
  • 返回值

交易流程

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package main

import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"log"
"math/big"
)

var (
PrivateKeyStr = "c535facd6873ca2b3718e3ede4f626ae126c99b4a26b4354da704d8dc78b43c1" // go生成的账户 发送发私钥
fromAddrStr = "0xa3856a939A623EdBde8f908037d3F33FceBC5408" // 对应go生成的账户 发送方地址
toAddrStr = "0x38B59D6D4ef6A4991926Cf04c7c2092a0E86140F" // 接收方地址
ethUrl = "https://eth-sepolia.g.alchemy.com/v2/fzgfj4QuLlNEyn2LrLZsseBAClGdMnyP" //API URL
)

func main() {

// 把私钥个地址转码一下
privateKey, _ := crypto.HexToECDSA(PrivateKeyStr)
toAddr := common.HexToAddress(toAddrStr)

// 新建连接,连上以太坊
client, err := ethclient.Dial(ethUrl)
if err != nil {
log.Fatal(err)
}

// Get the balance of an account 第0步:查询账户余额,要先确认自己有没有钱
fromAddr := common.HexToAddress(fromAddrStr)
balance, err := client.BalanceAt(context.Background(), fromAddr, nil)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Account balance: %d\n", balance) // 1008465873901900000

// Get the latest known block 第0步:获取最新块高,刷新
block, err := client.BlockByNumber(context.Background(), nil)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Latest block: %d\n", block.Number().Uint64()) //6055020

// 生成交易数据,这里注意fromAddr要转成 account common.Address对象
// 第一步拿nonce,这个拿nonce的的方法已经封装好了的,直接用就可,也可以去网站上copy url下来发请求,不过那样不优雅
nonce, err := client.PendingNonceAt(context.Background(), fromAddr)
if err != nil {
log.Fatal(err)
}
fmt.Printf("nonce: %d\n", nonce)

// 拿到gasPrice ,也可以不用拿,直接指定就可以
gasPriceNow, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatalf("Failed to retrieve gas price: %v", err)
}
fmt.Printf("gasPriceNow: %d\n", gasPriceNow)

gasPrice := big.NewInt(2000000000)
amount := big.NewInt(1100000000000000)
gasLimit := uint64(21208)
//gasPrice := big.NewInt(1000)
data := []byte("Hello, World!")

// 第二步 构建交易对象 ---> 生成一个未签名的交易
tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: &toAddr,
Value: amount,
Gas: gasLimit,
GasPrice: gasPrice,
Data: data,
})

// 第三步 对交易进行签名,签名前先拿到链ID,即你要上的链
chainID, err := client.NetworkID(context.Background()) // 拿到链ID
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) // 对交易进行签名
if err != nil {
log.Fatal(err)
}

// 第四步 发送交易,这一步就是广播
err = client.SendTransaction(context.Background(), signedTx) // 广播出去
if err != nil {
log.Fatal(err)
}

fmt.Printf("转账交易已发送,交易哈希\n:%s\n", signedTx.Hash().Hex())
// 第七笔交易哈希:转账交易已发送,交易哈希:0x93afb74b4c4f0bca6c1ccdf4a13fe555f7a8768201804c0e21ec33b50febec46
// 第八笔交易哈希:转账交易已发送,交易哈希:
// 前面的一直在pending中 有点问题,
// 第八笔交易哈希:转账交易已发送,交易哈希:0xd65407692e1f872d47ce1231a80d9afbf0dbcf8336362f78262d4fd872a55c81
// 第八笔交易哈希:转账交易已发送,交易哈希:0xcb297210b49a5a3747c27783ab3610b8b287134792e50ccbf6afe86c6c96edfd
// 第八笔交易哈希:转账交易已发送,交易哈希:0xc922a93fd5a5eabe7819623a65aa6b572e755658f3a9f24e71a64f3d533b1a87
// 第八笔交易哈希:转账交易已发送,交易哈希:0x4a8e649836a69d22e2b979c7330f03b97e7017b4a89d27e9c646051725f89a2c
// 第八笔交易哈希:转账交易已发送,交易哈希:0xae0b39c8cb2e02f7c647778f4325f0cca6392fe54c8f144a8e4ae58da79484eb
// 显示pending 然后转indexing状态 最后Success成功
// pending 状态: This txn hash was found in our secondary node and should be picked up by our indexer in a short while.
// indexing 状态:This transaction has been included and will be reflected in a short while.

}

Python代码实现

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
from web3 import Web3
import json

# 设置连接和账户信息
private_key = "c535facd68xxxxxxxxxxx26ae126c99bda704d8dc78b43c1" # 发送方私钥
from_address = "0xa3856a9xxxxxxxxe8f90835408" # 发送方地址
to_address = "0x38B59D6Dxxxxxxxx6Cf04c786140F" # 接收方地址
eth_url = "https://eth-sepolia.g.alchemy.com/v2/fzgfj4QuLlNEyn2LrLZsseBAClGdMnyP" # API URL

# 连接到以太坊节点
web3 = Web3(Web3.HTTPProvider(eth_url))
if not web3.is_connected():
print("Failed to connect to Ethereum node.")
exit()

# 检查账户余额
balance = web3.eth.get_balance(from_address)
print(f"Account balance: {web3.from_wei(balance, 'ether')} ETH")

# 获取最新区块号
block = web3.eth.get_block('latest')
print(f"Latest block: {block.number}")

# 获取交易计数 (nonce) 这个才是关键
nonce = web3.eth.get_transaction_count(from_address)
print(f"Nonce: {nonce}")

# 建议的 gas 价格 可以不接受建议
gas_price = web3.eth.gas_price
print(f"Gas price: {web3.from_wei(gas_price, 'gwei')} Gwei")

# 创建交易
transaction = {
'to': to_address,
# 'value': web3.toWei(0.0011, 'ether'), # 发送的以太币数量
'value': 1100000000000000, # 发送的以太币数量
'gas': 21320, # Gas limit
'gasPrice': 2000000000, # Gas price
'nonce': nonce,
'data': web3.to_hex(b"Hello, World! python"), # 附加的数据
'chainId': web3.eth.chain_id # 链 ID
}

# 对交易进行签名
signed_tx = web3.eth.account.sign_transaction(transaction, private_key)
print(f"Transaction hash: {signed_tx.hash.hex()}")

# 发送交易
tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f"Transaction sent, hash: {tx_hash.hex()}")
# 第一笔交易:0x22af000c45f51c03e88ea21281ccd571359e653341aef2a7fadbdc96b41eee49
# 第二笔交易:0xa502a7a7f589555eb0ce9bbd2d48220c860bbd0b7bf33b97819932ac0b092d6e
# 第三笔交易:0x775b97666b65444f2bb1b2bad852dbb9b56020fdbab513336cdec46bad1c2a36

5.中心化钱包

5.1 离线地址生成

  • 调用签名机生成密钥对,签名机吐出公钥,(交易所钱包私钥被统一管控)

  • 拿到公钥就可以使用公钥导出地址了


07-2 Ethereum 钱包开发流程
http://example.com/2024/05/30/07-2Ethereum钱包开发流程/
作者
Wangxiaowang
发布于
2024年5月30日
许可协议