genlayer 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/CLAUDE.md +55 -0
- package/README.md +121 -8
- package/dist/index.js +2550 -253
- package/docs/delegator-guide.md +203 -0
- package/docs/validator-guide.md +260 -0
- package/package.json +2 -2
- package/src/commands/account/create.ts +23 -0
- package/src/commands/account/import.ts +81 -0
- package/src/commands/account/index.ts +67 -0
- package/src/commands/{keygen → account}/lock.ts +6 -7
- package/src/commands/account/send.ts +150 -0
- package/src/commands/account/show.ts +60 -0
- package/src/commands/{keygen → account}/unlock.ts +12 -12
- package/src/commands/network/setNetwork.ts +5 -4
- package/src/commands/staking/StakingAction.ts +125 -0
- package/src/commands/staking/delegatorClaim.ts +41 -0
- package/src/commands/staking/delegatorExit.ts +50 -0
- package/src/commands/staking/delegatorJoin.ts +42 -0
- package/src/commands/staking/index.ts +224 -0
- package/src/commands/staking/setIdentity.ts +61 -0
- package/src/commands/staking/setOperator.ts +40 -0
- package/src/commands/staking/stakingInfo.ts +292 -0
- package/src/commands/staking/validatorClaim.ts +38 -0
- package/src/commands/staking/validatorDeposit.ts +35 -0
- package/src/commands/staking/validatorExit.ts +44 -0
- package/src/commands/staking/validatorJoin.ts +47 -0
- package/src/commands/staking/validatorPrime.ts +35 -0
- package/src/index.ts +4 -2
- package/src/lib/actions/BaseAction.ts +43 -10
- package/tests/actions/create.test.ts +18 -18
- package/tests/actions/lock.test.ts +7 -7
- package/tests/actions/setNetwork.test.ts +18 -57
- package/tests/actions/staking.test.ts +323 -0
- package/tests/actions/unlock.test.ts +12 -12
- package/tests/commands/account.test.ts +121 -0
- package/tests/commands/staking.test.ts +211 -0
- package/tests/index.test.ts +6 -2
- package/tests/libs/baseAction.test.ts +7 -1
- package/src/commands/keygen/create.ts +0 -23
- package/src/commands/keygen/index.ts +0 -39
- package/tests/commands/keygen.test.ts +0 -123
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Delegator Guide
|
|
2
|
+
|
|
3
|
+
This guide walks you through delegating GEN tokens to a validator on the GenLayer testnet.
|
|
4
|
+
|
|
5
|
+
## What is Delegation?
|
|
6
|
+
|
|
7
|
+
Delegation allows you to stake your GEN tokens with an existing validator without running validator infrastructure yourself. You earn staking rewards proportional to your stake.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- Node.js installed
|
|
12
|
+
- GenLayer CLI installed (`npm install -g genlayer`)
|
|
13
|
+
- GEN tokens for staking
|
|
14
|
+
|
|
15
|
+
## Step 1: Create an Account
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
genlayer account create
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
You'll be prompted to set a password. This creates an encrypted keystore file.
|
|
22
|
+
|
|
23
|
+
## Step 2: Set Network to Testnet
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
genlayer network testnet-asimov
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Step 3: Fund Your Account
|
|
30
|
+
|
|
31
|
+
Transfer GEN tokens to your address. Check your balance:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
genlayer account
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Step 4: Check Minimum Delegation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
genlayer staking epoch-info
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Note the `delegatorMinStake` - you need at least this amount.
|
|
44
|
+
|
|
45
|
+
## Step 5: Find a Validator
|
|
46
|
+
|
|
47
|
+
List all active validators:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
genlayer staking active-validators
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Output:
|
|
54
|
+
```
|
|
55
|
+
{
|
|
56
|
+
count: 6,
|
|
57
|
+
validators: [
|
|
58
|
+
'0xa8f1BF1e5e709593b4468d7ac5DC315Ea3CAe130',
|
|
59
|
+
'0xe9246A020cbb4fC6C46e60677981879c9219e8B9',
|
|
60
|
+
...
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Get details about a specific validator:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
genlayer staking validator-info --validator 0xa8f1BF1e5e709593b4468d7ac5DC315Ea3CAe130
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Look for:
|
|
72
|
+
- `live: true` - Validator is active
|
|
73
|
+
- `banned: 'Not banned'` - Validator is in good standing
|
|
74
|
+
- `identity` - Validator's metadata (moniker, website, etc.)
|
|
75
|
+
|
|
76
|
+
## Step 6: Unlock Your Account (Optional)
|
|
77
|
+
|
|
78
|
+
For convenience:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
genlayer account unlock
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Step 7: Delegate to a Validator
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
genlayer staking delegator-join --validator 0xa8f1...130 --amount 100gen
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Options:
|
|
91
|
+
- `--validator <address>` - Validator address to delegate to (required)
|
|
92
|
+
- `--amount <amount>` - Amount to stake (e.g., `100gen`)
|
|
93
|
+
|
|
94
|
+
## Step 8: Verify Your Delegation
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
genlayer staking stake-info --validator 0xa8f1...130
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Output:
|
|
101
|
+
```
|
|
102
|
+
{
|
|
103
|
+
delegator: '0x86D0d159483CBf01E920ECfF8bB7F0Cd7E964E7E',
|
|
104
|
+
validator: '0xa8f1BF1e5e709593b4468d7ac5DC315Ea3CAe130',
|
|
105
|
+
shares: '100000000000000000000',
|
|
106
|
+
stake: '100 GEN',
|
|
107
|
+
projectedReward: '0.2 GEN per epoch',
|
|
108
|
+
pendingDeposits: 'None',
|
|
109
|
+
pendingWithdrawals: 'None'
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The `projectedReward` shows your estimated earnings per epoch based on current inflation and your stake weight.
|
|
114
|
+
|
|
115
|
+
## Managing Your Delegation
|
|
116
|
+
|
|
117
|
+
### Check Your Stake
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
genlayer staking stake-info --validator 0xa8f1...130
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Withdraw (Exit) Delegation
|
|
124
|
+
|
|
125
|
+
To withdraw your stake:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
genlayer staking delegator-exit --validator 0xa8f1...130 --shares 50
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Options:
|
|
132
|
+
- `--validator <address>` - Validator you delegated to
|
|
133
|
+
- `--shares <shares>` - Number of shares to withdraw
|
|
134
|
+
|
|
135
|
+
This initiates a withdrawal. Your tokens enter an **unbonding period of 7 epochs** before they can be claimed.
|
|
136
|
+
|
|
137
|
+
Check your pending withdrawals with `stake-info`:
|
|
138
|
+
```
|
|
139
|
+
pendingWithdrawals: [
|
|
140
|
+
{
|
|
141
|
+
epoch: '5',
|
|
142
|
+
shares: '50',
|
|
143
|
+
stake: '50 GEN',
|
|
144
|
+
claimableAtEpoch: '12',
|
|
145
|
+
status: 'Unbonding (4 epochs remaining)'
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Claim Withdrawals
|
|
151
|
+
|
|
152
|
+
After the 7-epoch unbonding period, claim your tokens:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
genlayer staking delegator-claim --validator 0xa8f1...130
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Choosing a Validator
|
|
159
|
+
|
|
160
|
+
Consider these factors when choosing a validator:
|
|
161
|
+
|
|
162
|
+
1. **Uptime** - Validators with high uptime earn more rewards
|
|
163
|
+
2. **Reputation** - Check their identity metadata and community presence
|
|
164
|
+
3. **Stake** - Higher stake may indicate trust from the community
|
|
165
|
+
4. **Not banned/quarantined** - Avoid validators with issues
|
|
166
|
+
|
|
167
|
+
Check quarantined validators:
|
|
168
|
+
```bash
|
|
169
|
+
genlayer staking quarantined-validators
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Check banned validators:
|
|
173
|
+
```bash
|
|
174
|
+
genlayer staking banned-validators
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Troubleshooting
|
|
178
|
+
|
|
179
|
+
### "No account found"
|
|
180
|
+
Run `genlayer account create` first.
|
|
181
|
+
|
|
182
|
+
### "Insufficient balance"
|
|
183
|
+
Ensure you have enough GEN. Check with `genlayer account`.
|
|
184
|
+
|
|
185
|
+
### "Below minimum stake"
|
|
186
|
+
Check minimum with `genlayer staking epoch-info` and increase your amount.
|
|
187
|
+
|
|
188
|
+
### "Validator not found"
|
|
189
|
+
Verify the validator address is correct and they are still active.
|
|
190
|
+
|
|
191
|
+
### Transaction Stuck
|
|
192
|
+
Check the transaction status:
|
|
193
|
+
```bash
|
|
194
|
+
genlayer receipt <tx-hash>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Lock Your Account
|
|
198
|
+
|
|
199
|
+
When done, lock your account:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
genlayer account lock
|
|
203
|
+
```
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Validator Guide
|
|
2
|
+
|
|
3
|
+
This guide walks you through becoming a validator on the GenLayer testnet using the CLI.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js installed
|
|
8
|
+
- GenLayer CLI installed (`npm install -g genlayer`)
|
|
9
|
+
- GEN tokens for staking (minimum stake required)
|
|
10
|
+
|
|
11
|
+
## Step 1: Create an Account
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
genlayer account create
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
You'll be prompted to set a password. This creates an encrypted keystore file.
|
|
18
|
+
|
|
19
|
+
## Step 2: View Your Account
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
genlayer account
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Output:
|
|
26
|
+
```
|
|
27
|
+
{
|
|
28
|
+
address: '0x86D0d159483CBf01E920ECfF8bB7F0Cd7E964E7E',
|
|
29
|
+
balance: '0 GEN',
|
|
30
|
+
network: 'localnet',
|
|
31
|
+
status: 'locked'
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Step 3: Set Network to Testnet
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
genlayer network testnet-asimov
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Verify with:
|
|
42
|
+
```bash
|
|
43
|
+
genlayer account
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You should see `network: 'Asimov Testnet'`.
|
|
47
|
+
|
|
48
|
+
## Step 4: Fund Your Account
|
|
49
|
+
|
|
50
|
+
Transfer GEN tokens to your address. You can:
|
|
51
|
+
- Use the faucet (if available)
|
|
52
|
+
- Transfer from another account:
|
|
53
|
+
```bash
|
|
54
|
+
genlayer account send <your-address> 50000gen
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Step 5: Check Staking Requirements
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
genlayer staking epoch-info
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Output:
|
|
64
|
+
```
|
|
65
|
+
{
|
|
66
|
+
currentEpoch: '2',
|
|
67
|
+
epochStarted: '2025-01-15T00:00:00.000Z',
|
|
68
|
+
nextEpochEstimate: '2025-01-16T00:00:00.000Z',
|
|
69
|
+
timeUntilNextEpoch: '12h 30m',
|
|
70
|
+
minEpochDuration: '24h 0m',
|
|
71
|
+
validatorMinStake: '42000 GEN',
|
|
72
|
+
delegatorMinStake: '42 GEN',
|
|
73
|
+
activeValidatorsCount: '6',
|
|
74
|
+
epochInflation: '1000 GEN',
|
|
75
|
+
totalWeight: '500000000000000000000000',
|
|
76
|
+
totalClaimed: '500 GEN'
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Note the `validatorMinStake` - you need at least this amount.
|
|
81
|
+
|
|
82
|
+
## Step 6: Unlock Your Account (Optional)
|
|
83
|
+
|
|
84
|
+
For convenience, unlock your account to avoid entering password repeatedly:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
genlayer account unlock
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This caches your private key in the OS keychain.
|
|
91
|
+
|
|
92
|
+
## Step 7: Join as Validator
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
genlayer staking validator-join --amount 42000gen --operator 0xOperator...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
- `--amount <amount>` - Stake amount (e.g., `42000gen` or `42000`)
|
|
100
|
+
- `--operator <address>` - Operator address (recommended, see below)
|
|
101
|
+
|
|
102
|
+
### Why Use an Operator Address?
|
|
103
|
+
|
|
104
|
+
**Recommended:** Use a separate operator address for security.
|
|
105
|
+
|
|
106
|
+
- **Validator wallet** - Holds your staked funds (keep offline/cold)
|
|
107
|
+
- **Operator wallet** - Signs blocks and performs validator duties (hot wallet on server)
|
|
108
|
+
|
|
109
|
+
This way, if your operator server is compromised, your staked funds remain safe.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Create operator account on your validator server
|
|
113
|
+
genlayer account create --output ./operator.json
|
|
114
|
+
|
|
115
|
+
# Get operator address
|
|
116
|
+
cat ./operator.json | jq -r .address
|
|
117
|
+
# 0xOperator123...
|
|
118
|
+
|
|
119
|
+
# Join with separate operator (run from your main wallet)
|
|
120
|
+
genlayer staking validator-join --amount 42000gen --operator 0xOperator123...
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
You can change the operator later:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
genlayer staking set-operator --validator 0xYourValidator... --operator 0xNewOperator...
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Step 8: Verify Your Validator Status
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
genlayer staking validator-info
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Output:
|
|
136
|
+
```
|
|
137
|
+
{
|
|
138
|
+
validator: '0x86D0d159483CBf01E920ECfF8bB7F0Cd7E964E7E',
|
|
139
|
+
vStake: '42000 GEN',
|
|
140
|
+
vShares: '42000000000000000000000',
|
|
141
|
+
live: true,
|
|
142
|
+
banned: 'Not banned',
|
|
143
|
+
...
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Step 9: Set Validator Identity (Metadata)
|
|
148
|
+
|
|
149
|
+
Set your validator's public identity so delegators can find you:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
genlayer staking set-identity \
|
|
153
|
+
--validator 0x86D0...7E \
|
|
154
|
+
--moniker "My Validator" \
|
|
155
|
+
--website "https://myvalidator.com" \
|
|
156
|
+
--description "Reliable validator with 99.9% uptime" \
|
|
157
|
+
--twitter "myvalidator" \
|
|
158
|
+
--github "myvalidator"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Required:**
|
|
162
|
+
- `--validator <address>` - Your validator address
|
|
163
|
+
- `--moniker <name>` - Display name for your validator
|
|
164
|
+
|
|
165
|
+
**Optional:**
|
|
166
|
+
- `--logo-uri <uri>` - Logo image URL
|
|
167
|
+
- `--website <url>` - Website URL
|
|
168
|
+
- `--description <text>` - Description of your validator
|
|
169
|
+
- `--email <email>` - Contact email
|
|
170
|
+
- `--twitter <handle>` - Twitter handle
|
|
171
|
+
- `--telegram <handle>` - Telegram handle
|
|
172
|
+
- `--github <handle>` - GitHub handle
|
|
173
|
+
- `--extra-cid <cid>` - Additional data as IPFS CID
|
|
174
|
+
|
|
175
|
+
Your identity will show in `validator-info`:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
genlayer staking validator-info
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Output will include:
|
|
182
|
+
```
|
|
183
|
+
{
|
|
184
|
+
...
|
|
185
|
+
identity: {
|
|
186
|
+
moniker: 'My Validator',
|
|
187
|
+
website: 'https://myvalidator.com',
|
|
188
|
+
twitter: 'myvalidator',
|
|
189
|
+
github: 'myvalidator'
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Managing Your Validator
|
|
195
|
+
|
|
196
|
+
### Add More Stake
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
genlayer staking validator-deposit --amount 1000gen
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Check Active Validators
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
genlayer staking active-validators
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Exit as Validator
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
genlayer staking validator-exit --shares 100
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
This initiates a withdrawal. Your tokens enter an **unbonding period of 7 epochs** before they can be claimed.
|
|
215
|
+
|
|
216
|
+
Check your pending withdrawals with `validator-info`:
|
|
217
|
+
```
|
|
218
|
+
selfStakePendingWithdrawals: [
|
|
219
|
+
{
|
|
220
|
+
epoch: '5',
|
|
221
|
+
shares: '100',
|
|
222
|
+
stake: '100 GEN',
|
|
223
|
+
claimableAtEpoch: '12',
|
|
224
|
+
status: 'Unbonding (4 epochs remaining)'
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Claim Withdrawals
|
|
230
|
+
|
|
231
|
+
After the 7-epoch unbonding period:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
genlayer staking validator-claim
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Troubleshooting
|
|
238
|
+
|
|
239
|
+
### "No account found"
|
|
240
|
+
Run `genlayer account create` first.
|
|
241
|
+
|
|
242
|
+
### "Insufficient balance"
|
|
243
|
+
Ensure you have enough GEN. Check with `genlayer account`.
|
|
244
|
+
|
|
245
|
+
### "Below minimum stake"
|
|
246
|
+
Check minimum with `genlayer staking epoch-info` and increase your stake amount.
|
|
247
|
+
|
|
248
|
+
### Transaction Stuck
|
|
249
|
+
Check the transaction status:
|
|
250
|
+
```bash
|
|
251
|
+
genlayer receipt <tx-hash>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Lock Your Account
|
|
255
|
+
|
|
256
|
+
When done, lock your account to remove the cached private key:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
genlayer account lock
|
|
260
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genlayer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "GenLayer Command Line Tool",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"dotenv": "^17.0.0",
|
|
66
66
|
"ethers": "^6.13.4",
|
|
67
67
|
"fs-extra": "^11.3.0",
|
|
68
|
-
"genlayer-js": "^0.
|
|
68
|
+
"genlayer-js": "^0.18.5",
|
|
69
69
|
"inquirer": "^12.0.0",
|
|
70
70
|
"keytar": "^7.9.0",
|
|
71
71
|
"node-fetch": "^3.0.0",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {BaseAction} from "../../lib/actions/BaseAction";
|
|
2
|
+
|
|
3
|
+
export interface CreateAccountOptions {
|
|
4
|
+
output: string;
|
|
5
|
+
overwrite: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class CreateAccountAction extends BaseAction {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async execute(options: CreateAccountOptions): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
this.startSpinner("Creating encrypted keystore...");
|
|
16
|
+
await this.createKeypair(options.output, options.overwrite);
|
|
17
|
+
|
|
18
|
+
this.succeedSpinner(`Account created and saved to: ${options.output}`);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
this.failSpinner("Failed to create account", error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {BaseAction} from "../../lib/actions/BaseAction";
|
|
2
|
+
import {ethers} from "ethers";
|
|
3
|
+
import {writeFileSync, existsSync} from "fs";
|
|
4
|
+
import {KeystoreData} from "../../lib/interfaces/KeystoreData";
|
|
5
|
+
|
|
6
|
+
export interface ImportAccountOptions {
|
|
7
|
+
privateKey?: string;
|
|
8
|
+
output: string;
|
|
9
|
+
overwrite: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ImportAccountAction extends BaseAction {
|
|
13
|
+
private static readonly MIN_PASSWORD_LENGTH = 8;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async execute(options: ImportAccountOptions): Promise<void> {
|
|
20
|
+
try {
|
|
21
|
+
const privateKey = options.privateKey || await this.promptPrivateKey();
|
|
22
|
+
|
|
23
|
+
const normalizedKey = this.normalizePrivateKey(privateKey);
|
|
24
|
+
this.validatePrivateKey(normalizedKey);
|
|
25
|
+
|
|
26
|
+
const finalOutputPath = this.getFilePath(options.output);
|
|
27
|
+
|
|
28
|
+
if (existsSync(finalOutputPath) && !options.overwrite) {
|
|
29
|
+
this.failSpinner(`File at ${finalOutputPath} already exists. Use '--overwrite' to replace.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const wallet = new ethers.Wallet(normalizedKey);
|
|
33
|
+
|
|
34
|
+
const password = await this.promptPassword("Enter a password to encrypt your keystore (minimum 8 characters):");
|
|
35
|
+
const confirmPassword = await this.promptPassword("Confirm password:");
|
|
36
|
+
|
|
37
|
+
if (password !== confirmPassword) {
|
|
38
|
+
this.failSpinner("Passwords do not match");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (password.length < ImportAccountAction.MIN_PASSWORD_LENGTH) {
|
|
42
|
+
this.failSpinner(`Password must be at least ${ImportAccountAction.MIN_PASSWORD_LENGTH} characters long`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.startSpinner("Encrypting and saving keystore...");
|
|
46
|
+
|
|
47
|
+
const encryptedJson = await wallet.encrypt(password);
|
|
48
|
+
|
|
49
|
+
const keystoreData: KeystoreData = {
|
|
50
|
+
version: 1,
|
|
51
|
+
encrypted: encryptedJson,
|
|
52
|
+
address: wallet.address,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
writeFileSync(finalOutputPath, JSON.stringify(keystoreData, null, 2));
|
|
56
|
+
this.writeConfig("keyPairPath", finalOutputPath);
|
|
57
|
+
|
|
58
|
+
await this.keychainManager.removePrivateKey();
|
|
59
|
+
|
|
60
|
+
this.succeedSpinner(`Account imported and saved to: ${finalOutputPath}`);
|
|
61
|
+
this.logInfo(`Address: ${wallet.address}`);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
this.failSpinner("Failed to import account", error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private async promptPrivateKey(): Promise<string> {
|
|
68
|
+
return this.promptPassword("Enter private key to import:");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private normalizePrivateKey(key: string): string {
|
|
72
|
+
const trimmed = key.trim();
|
|
73
|
+
return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private validatePrivateKey(key: string): void {
|
|
77
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
|
|
78
|
+
this.failSpinner("Invalid private key format. Expected 64 hex characters (with or without 0x prefix).");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {Command} from "commander";
|
|
2
|
+
import {ShowAccountAction} from "./show";
|
|
3
|
+
import {CreateAccountAction, CreateAccountOptions} from "./create";
|
|
4
|
+
import {ImportAccountAction, ImportAccountOptions} from "./import";
|
|
5
|
+
import {UnlockAccountAction} from "./unlock";
|
|
6
|
+
import {LockAccountAction} from "./lock";
|
|
7
|
+
import {SendAction, SendOptions} from "./send";
|
|
8
|
+
|
|
9
|
+
export function initializeAccountCommands(program: Command) {
|
|
10
|
+
const accountCommand = program
|
|
11
|
+
.command("account")
|
|
12
|
+
.description("Manage your account (address, balance, keys)")
|
|
13
|
+
.action(async () => {
|
|
14
|
+
// Default action: show account info
|
|
15
|
+
const showAction = new ShowAccountAction();
|
|
16
|
+
await showAction.execute();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
accountCommand
|
|
20
|
+
.command("create")
|
|
21
|
+
.description("Create a new account with encrypted keystore")
|
|
22
|
+
.option("--output <path>", "Path to save the keystore", "./keypair.json")
|
|
23
|
+
.option("--overwrite", "Overwrite existing file", false)
|
|
24
|
+
.action(async (options: CreateAccountOptions) => {
|
|
25
|
+
const createAction = new CreateAccountAction();
|
|
26
|
+
await createAction.execute(options);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
accountCommand
|
|
30
|
+
.command("import")
|
|
31
|
+
.description("Import an account from a private key")
|
|
32
|
+
.option("--private-key <key>", "Private key to import (will prompt if not provided)")
|
|
33
|
+
.option("--output <path>", "Path to save the keystore", "./keypair.json")
|
|
34
|
+
.option("--overwrite", "Overwrite existing file", false)
|
|
35
|
+
.action(async (options: ImportAccountOptions) => {
|
|
36
|
+
const importAction = new ImportAccountAction();
|
|
37
|
+
await importAction.execute(options);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
accountCommand
|
|
41
|
+
.command("send <to> <amount>")
|
|
42
|
+
.description("Send GEN to an address")
|
|
43
|
+
.option("--rpc <rpcUrl>", "RPC URL for the network")
|
|
44
|
+
.option("--network <network>", "Network to use (localnet, testnet-asimov)")
|
|
45
|
+
.action(async (to: string, amount: string, options: {rpc?: string; network?: string}) => {
|
|
46
|
+
const sendAction = new SendAction();
|
|
47
|
+
await sendAction.execute({to, amount, rpc: options.rpc, network: options.network});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
accountCommand
|
|
51
|
+
.command("unlock")
|
|
52
|
+
.description("Unlock account by caching private key in OS keychain")
|
|
53
|
+
.action(async () => {
|
|
54
|
+
const unlockAction = new UnlockAccountAction();
|
|
55
|
+
await unlockAction.execute();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
accountCommand
|
|
59
|
+
.command("lock")
|
|
60
|
+
.description("Lock account by removing private key from OS keychain")
|
|
61
|
+
.action(async () => {
|
|
62
|
+
const lockAction = new LockAccountAction();
|
|
63
|
+
await lockAction.execute();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return program;
|
|
67
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {BaseAction} from "../../lib/actions/BaseAction";
|
|
2
2
|
|
|
3
|
-
export class
|
|
3
|
+
export class LockAccountAction extends BaseAction {
|
|
4
4
|
async execute(): Promise<void> {
|
|
5
5
|
this.startSpinner("Checking keychain availability...");
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ export class LockAction extends BaseAction {
|
|
|
14
14
|
|
|
15
15
|
const hasCachedKey = await this.keychainManager.getPrivateKey();
|
|
16
16
|
if (!hasCachedKey) {
|
|
17
|
-
this.succeedSpinner("
|
|
17
|
+
this.succeedSpinner("Account is already locked.");
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -22,10 +22,9 @@ export class LockAction extends BaseAction {
|
|
|
22
22
|
|
|
23
23
|
try {
|
|
24
24
|
await this.keychainManager.removePrivateKey();
|
|
25
|
-
|
|
26
|
-
this.succeedSpinner("Wallet locked successfully! Your private key has been removed from the OS keychain.");
|
|
25
|
+
this.succeedSpinner("Account locked! Private key removed from OS keychain.");
|
|
27
26
|
} catch (error) {
|
|
28
|
-
this.failSpinner("Failed to lock
|
|
27
|
+
this.failSpinner("Failed to lock account.", error);
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
|
-
}
|
|
30
|
+
}
|