@reppo/cli 0.1.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/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/api/platform.d.ts +39 -0
- package/dist/api/platform.d.ts.map +1 -0
- package/dist/api/platform.js +148 -0
- package/dist/api/platform.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +60 -0
- package/dist/bin.js.map +1 -0
- package/dist/chain/abis.d.ts +576 -0
- package/dist/chain/abis.d.ts.map +1 -0
- package/dist/chain/abis.js +66 -0
- package/dist/chain/abis.js.map +1 -0
- package/dist/chain/addresses.d.ts +27 -0
- package/dist/chain/addresses.d.ts.map +1 -0
- package/dist/chain/addresses.js +39 -0
- package/dist/chain/addresses.js.map +1 -0
- package/dist/chain/clients.d.ts +46 -0
- package/dist/chain/clients.d.ts.map +1 -0
- package/dist/chain/clients.js +48 -0
- package/dist/chain/clients.js.map +1 -0
- package/dist/chain/contracts.d.ts +28 -0
- package/dist/chain/contracts.d.ts.map +1 -0
- package/dist/chain/contracts.js +70 -0
- package/dist/chain/contracts.js.map +1 -0
- package/dist/chain/errors.d.ts +14 -0
- package/dist/chain/errors.d.ts.map +1 -0
- package/dist/chain/errors.js +72 -0
- package/dist/chain/errors.js.map +1 -0
- package/dist/commands/_base.d.ts +19 -0
- package/dist/commands/_base.d.ts.map +1 -0
- package/dist/commands/_base.js +40 -0
- package/dist/commands/_base.js.map +1 -0
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +93 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/claim-emissions.d.ts +11 -0
- package/dist/commands/claim-emissions.d.ts.map +1 -0
- package/dist/commands/claim-emissions.js +192 -0
- package/dist/commands/claim-emissions.js.map +1 -0
- package/dist/commands/extend-lock.d.ts +11 -0
- package/dist/commands/extend-lock.d.ts.map +1 -0
- package/dist/commands/extend-lock.js +196 -0
- package/dist/commands/extend-lock.js.map +1 -0
- package/dist/commands/grant-access.d.ts +11 -0
- package/dist/commands/grant-access.d.ts.map +1 -0
- package/dist/commands/grant-access.js +191 -0
- package/dist/commands/grant-access.js.map +1 -0
- package/dist/commands/lock.d.ts +11 -0
- package/dist/commands/lock.d.ts.map +1 -0
- package/dist/commands/lock.js +192 -0
- package/dist/commands/lock.js.map +1 -0
- package/dist/commands/mint-pod.d.ts +15 -0
- package/dist/commands/mint-pod.d.ts.map +1 -0
- package/dist/commands/mint-pod.js +286 -0
- package/dist/commands/mint-pod.js.map +1 -0
- package/dist/commands/query/balance.d.ts +9 -0
- package/dist/commands/query/balance.d.ts.map +1 -0
- package/dist/commands/query/balance.js +91 -0
- package/dist/commands/query/balance.js.map +1 -0
- package/dist/commands/query/datanet.d.ts +10 -0
- package/dist/commands/query/datanet.d.ts.map +1 -0
- package/dist/commands/query/datanet.js +128 -0
- package/dist/commands/query/datanet.js.map +1 -0
- package/dist/commands/query/emissions-due.d.ts +18 -0
- package/dist/commands/query/emissions-due.d.ts.map +1 -0
- package/dist/commands/query/emissions-due.js +170 -0
- package/dist/commands/query/emissions-due.js.map +1 -0
- package/dist/commands/query/pod.d.ts +8 -0
- package/dist/commands/query/pod.d.ts.map +1 -0
- package/dist/commands/query/pod.js +88 -0
- package/dist/commands/query/pod.js.map +1 -0
- package/dist/commands/query/voting-power.d.ts +9 -0
- package/dist/commands/query/voting-power.d.ts.map +1 -0
- package/dist/commands/query/voting-power.js +82 -0
- package/dist/commands/query/voting-power.js.map +1 -0
- package/dist/commands/register-agent.d.ts +9 -0
- package/dist/commands/register-agent.d.ts.map +1 -0
- package/dist/commands/register-agent.js +115 -0
- package/dist/commands/register-agent.js.map +1 -0
- package/dist/commands/unlock.d.ts +11 -0
- package/dist/commands/unlock.d.ts.map +1 -0
- package/dist/commands/unlock.js +181 -0
- package/dist/commands/unlock.js.map +1 -0
- package/dist/commands/vote.d.ts +13 -0
- package/dist/commands/vote.d.ts.map +1 -0
- package/dist/commands/vote.js +174 -0
- package/dist/commands/vote.js.map +1 -0
- package/dist/config/load.d.ts +19 -0
- package/dist/config/load.d.ts.map +1 -0
- package/dist/config/load.js +66 -0
- package/dist/config/load.js.map +1 -0
- package/dist/output/format.d.ts +43 -0
- package/dist/output/format.d.ts.map +1 -0
- package/dist/output/format.js +58 -0
- package/dist/output/format.js.map +1 -0
- package/dist/state/db.d.ts +47 -0
- package/dist/state/db.d.ts.map +1 -0
- package/dist/state/db.js +169 -0
- package/dist/state/db.js.map +1 -0
- package/dist/state/idempotency.d.ts +66 -0
- package/dist/state/idempotency.d.ts.map +1 -0
- package/dist/state/idempotency.js +173 -0
- package/dist/state/idempotency.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reppo claim-emissions --pod <id> --epoch <n>` — claim a pod's
|
|
3
|
+
* emissions for a given epoch.
|
|
4
|
+
*
|
|
5
|
+
* Pre-flight (mainnet, full ABI):
|
|
6
|
+
* 1. INVALID_POD_ID, INVALID_EPOCH (parse)
|
|
7
|
+
* 2. NOT_POD_OWNER (ownerOf(podId) !== caller)
|
|
8
|
+
* 3. ALREADY_CLAIMED (hasPodOwnerClaimedEmissions(epoch, podId) === true)
|
|
9
|
+
* 4. NO_EMISSIONS_DUE (getPodEmissionsOfEpoch(epoch, podId) === 0)
|
|
10
|
+
*
|
|
11
|
+
* Pre-flight (testnet, partial ABI):
|
|
12
|
+
* 1. INVALID_POD_ID, INVALID_EPOCH
|
|
13
|
+
* 2. NOT_POD_OWNER
|
|
14
|
+
* (testnet ABI doesn't include hasPodOwnerClaimedEmissions or
|
|
15
|
+
* getPodEmissionsOfEpoch — caller bears the cost of a doomed
|
|
16
|
+
* re-claim. Documented in usage.)
|
|
17
|
+
*
|
|
18
|
+
* Two-phase write protocol via peekIdempotent.
|
|
19
|
+
*
|
|
20
|
+
* Args fingerprint: { podId, epoch } — re-using one --idempotency-key
|
|
21
|
+
* across different (podId, epoch) is rejected with IDEMPOTENCY_ARGS_MISMATCH.
|
|
22
|
+
*/
|
|
23
|
+
import { Option } from 'clipanion';
|
|
24
|
+
import { formatUnits } from 'viem';
|
|
25
|
+
import { BaseCommand } from './_base.js';
|
|
26
|
+
import { cliError, emit } from '../output/format.js';
|
|
27
|
+
import { createClients, nextNonce } from '../chain/clients.js';
|
|
28
|
+
import { podManager } from '../chain/contracts.js';
|
|
29
|
+
import { decodeRevert } from '../chain/errors.js';
|
|
30
|
+
import { begin, markSubmitted, markConfirmed, markFailed, peekIdempotent } from '../state/idempotency.js';
|
|
31
|
+
const COMMAND = 'claim-emissions';
|
|
32
|
+
export class ClaimEmissionsCommand extends BaseCommand {
|
|
33
|
+
static paths = [['claim-emissions']];
|
|
34
|
+
static usage = BaseCommand.Usage({
|
|
35
|
+
description: 'Claim a pod\'s emissions for a given epoch.',
|
|
36
|
+
examples: [
|
|
37
|
+
['Claim emissions for pod 34, epoch 3940',
|
|
38
|
+
'reppo claim-emissions --pod 34 --epoch 3940'],
|
|
39
|
+
['With idempotency key',
|
|
40
|
+
'reppo claim-emissions --pod 34 --epoch 3940 --idempotency-key claim-34-3940'],
|
|
41
|
+
['Dry-run',
|
|
42
|
+
'reppo claim-emissions --pod 34 --epoch 3940 --dry-run'],
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
pod = Option.String('--pod', { required: true });
|
|
46
|
+
epoch = Option.String('--epoch', { required: true });
|
|
47
|
+
idempotencyKey = Option.String('--idempotency-key');
|
|
48
|
+
dryRun = Option.Boolean('--dry-run', false);
|
|
49
|
+
async execute() {
|
|
50
|
+
try {
|
|
51
|
+
const cfg = this.loadConfig();
|
|
52
|
+
const pk = cfg.privateKey;
|
|
53
|
+
if (!pk) {
|
|
54
|
+
throw cliError('MISSING_PRIVATE_KEY', 'No signing key available.', 'Set REPPO_PRIVATE_KEY in env.');
|
|
55
|
+
}
|
|
56
|
+
let podId;
|
|
57
|
+
try {
|
|
58
|
+
podId = BigInt(this.pod);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw cliError('INVALID_POD_ID', `Pod id must be a non-negative integer; got "${this.pod}".`);
|
|
62
|
+
}
|
|
63
|
+
let epoch;
|
|
64
|
+
try {
|
|
65
|
+
epoch = BigInt(this.epoch);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
throw cliError('INVALID_EPOCH', `Epoch must be a non-negative integer; got "${this.epoch}".`);
|
|
69
|
+
}
|
|
70
|
+
const args = { podId: podId.toString(), epoch: epoch.toString() };
|
|
71
|
+
const decision = await peekIdempotent(this.idempotencyKey, COMMAND, args, this.dryRun);
|
|
72
|
+
if (decision.kind === 'return-confirmed') {
|
|
73
|
+
emit({ ...decision.result, idempotent: true, status: 'confirmed' }, [`(cached, confirmed) tx: ${decision.txHash ?? 'n/a'}`]);
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
if (decision.kind === 'return-submitted') {
|
|
77
|
+
emit({ ...decision.result, idempotent: true, status: 'submitted' }, [`(cached, submitted but not confirmed yet) tx: ${decision.txHash ?? 'n/a'}`,
|
|
78
|
+
`Re-run after the tx confirms, or check the explorer.`]);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
const clients = createClients({
|
|
82
|
+
network: cfg.network,
|
|
83
|
+
privateKey: pk,
|
|
84
|
+
...(cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}),
|
|
85
|
+
});
|
|
86
|
+
const pm = podManager(cfg.network);
|
|
87
|
+
// Pre-flight: ownership (both networks support ownerOf).
|
|
88
|
+
const owner = await clients.publicClient.readContract({
|
|
89
|
+
address: pm.address, abi: pm.abi, functionName: 'ownerOf', args: [podId],
|
|
90
|
+
}).catch(() => {
|
|
91
|
+
throw cliError('POD_NOT_FOUND', `Pod ${podId} does not exist on ${cfg.network}.`, 'Verify the podId; check `reppo query pod <id>` first.');
|
|
92
|
+
});
|
|
93
|
+
if (owner.toLowerCase() !== clients.account.address.toLowerCase()) {
|
|
94
|
+
throw cliError('NOT_POD_OWNER', `Pod ${podId} is owned by ${owner}, not by the configured signer ${clients.account.address}.`, 'Use the private key of the pod owner.');
|
|
95
|
+
}
|
|
96
|
+
// Mainnet-only pre-flight: claim status + amount due.
|
|
97
|
+
// Testnet's PodManager ABI doesn't expose these views (the testnet fork is leaner).
|
|
98
|
+
let amountDue = null;
|
|
99
|
+
if (cfg.network === 'mainnet') {
|
|
100
|
+
const [alreadyClaimed, due] = await Promise.all([
|
|
101
|
+
clients.publicClient.readContract({
|
|
102
|
+
address: pm.address, abi: pm.abi, functionName: 'hasPodOwnerClaimedEmissions', args: [epoch, podId],
|
|
103
|
+
}),
|
|
104
|
+
clients.publicClient.readContract({
|
|
105
|
+
address: pm.address, abi: pm.abi, functionName: 'getPodEmissionsOfEpoch', args: [epoch, podId],
|
|
106
|
+
}),
|
|
107
|
+
]);
|
|
108
|
+
if (alreadyClaimed) {
|
|
109
|
+
throw cliError('ALREADY_CLAIMED', `Pod ${podId} emissions for epoch ${epoch} have already been claimed.`, 'Nothing to do — pick a different epoch.');
|
|
110
|
+
}
|
|
111
|
+
if (due === 0n) {
|
|
112
|
+
throw cliError('NO_EMISSIONS_DUE', `Pod ${podId} has no emissions due for epoch ${epoch}.`, 'Try a different epoch (e.g. one where the pod received votes).');
|
|
113
|
+
}
|
|
114
|
+
amountDue = due;
|
|
115
|
+
}
|
|
116
|
+
if (this.dryRun) {
|
|
117
|
+
const sim = await clients.publicClient.simulateContract({
|
|
118
|
+
address: pm.address, abi: pm.abi, functionName: 'claimPodOwnerEmissions',
|
|
119
|
+
args: [podId, epoch], account: clients.account,
|
|
120
|
+
}).catch((e) => {
|
|
121
|
+
const decoded = decodeRevert(e);
|
|
122
|
+
throw cliError(decoded.code, 'Simulation reverted', decoded.hint);
|
|
123
|
+
});
|
|
124
|
+
emit({
|
|
125
|
+
simulated: true,
|
|
126
|
+
podId: podId.toString(),
|
|
127
|
+
epoch: epoch.toString(),
|
|
128
|
+
owner,
|
|
129
|
+
...(amountDue !== null
|
|
130
|
+
? { amountDue: { raw: amountDue.toString(), formatted: formatUnits(amountDue, 18) } }
|
|
131
|
+
: { amountDue: { unavailable: 'testnet ABI does not expose getPodEmissionsOfEpoch' } }),
|
|
132
|
+
gas: sim.request.gas?.toString() ?? null,
|
|
133
|
+
});
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
if (this.idempotencyKey)
|
|
137
|
+
await begin(this.idempotencyKey, COMMAND, args);
|
|
138
|
+
let tx;
|
|
139
|
+
try {
|
|
140
|
+
const nonce = await nextNonce(clients.publicClient, clients.account.address);
|
|
141
|
+
tx = await clients.walletClient.writeContract({
|
|
142
|
+
address: pm.address, abi: pm.abi, functionName: 'claimPodOwnerEmissions',
|
|
143
|
+
args: [podId, epoch],
|
|
144
|
+
chain: clients.walletClient.chain, account: clients.account, nonce,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
const decoded = decodeRevert(e);
|
|
149
|
+
if (this.idempotencyKey)
|
|
150
|
+
await markFailed(this.idempotencyKey, COMMAND, args, decoded.code);
|
|
151
|
+
throw cliError(decoded.code, 'claim-emissions tx failed to submit', decoded.hint);
|
|
152
|
+
}
|
|
153
|
+
// markSubmitted BEFORE waitForReceipt — closes the retry-resend window.
|
|
154
|
+
if (this.idempotencyKey)
|
|
155
|
+
await markSubmitted(this.idempotencyKey, COMMAND, args, tx);
|
|
156
|
+
const receipt = await clients.publicClient.waitForTransactionReceipt({ hash: tx, timeout: 120_000 });
|
|
157
|
+
if (receipt.status === 'reverted') {
|
|
158
|
+
if (this.idempotencyKey)
|
|
159
|
+
await markFailed(this.idempotencyKey, COMMAND, args, 'TX_REVERTED', tx);
|
|
160
|
+
throw cliError('TX_REVERTED', `claim-emissions tx reverted: ${tx}`);
|
|
161
|
+
}
|
|
162
|
+
const result = {
|
|
163
|
+
txHash: tx,
|
|
164
|
+
podId: podId.toString(),
|
|
165
|
+
epoch: epoch.toString(),
|
|
166
|
+
...(amountDue !== null
|
|
167
|
+
? { amountClaimed: { raw: amountDue.toString(), formatted: formatUnits(amountDue, 18) } }
|
|
168
|
+
: { amountClaimed: { unavailable: 'testnet ABI does not expose getPodEmissionsOfEpoch' } }),
|
|
169
|
+
block: receipt.blockNumber.toString(),
|
|
170
|
+
basescanUrl: cfg.network === 'mainnet'
|
|
171
|
+
? `https://basescan.org/tx/${tx}`
|
|
172
|
+
: `https://sepolia.basescan.org/tx/${tx}`,
|
|
173
|
+
};
|
|
174
|
+
if (this.idempotencyKey)
|
|
175
|
+
await markConfirmed(this.idempotencyKey, COMMAND, args, result, tx);
|
|
176
|
+
const amountStr = amountDue !== null
|
|
177
|
+
? `${formatUnits(amountDue, 18)} REPPO`
|
|
178
|
+
: '(amount unknown — testnet)';
|
|
179
|
+
emit(result, [
|
|
180
|
+
`✓ Claimed pod ${podId} emissions for epoch ${epoch}`,
|
|
181
|
+
` amount: ${amountStr}`,
|
|
182
|
+
` tx: ${result.basescanUrl}`,
|
|
183
|
+
` block: ${receipt.blockNumber}`,
|
|
184
|
+
]);
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
this.handleError(err);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=claim-emissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-emissions.js","sourceRoot":"","sources":["../../src/commands/claim-emissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE1G,MAAM,OAAO,GAAG,iBAAiB,CAAC;AAElC,MAAM,OAAO,qBAAsB,SAAQ,WAAW;IACpD,MAAM,CAAU,KAAK,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAE9C,MAAM,CAAU,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC;QACxC,WAAW,EAAE,6CAA6C;QAC1D,QAAQ,EAAE;YACR,CAAC,wCAAwC;gBACvC,6CAA6C,CAAC;YAChD,CAAC,sBAAsB;gBACrB,6EAA6E,CAAC;YAChF,CAAC,SAAS;gBACR,uDAAuD,CAAC;SAC3D;KACF,CAAC,CAAC;IAEH,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACpD,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAE5C,KAAK,CAAC,OAAO;QACX,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YAC9B,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;YAC1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,QAAQ,CACZ,qBAAqB,EACrB,2BAA2B,EAC3B,+BAA+B,CAChC,CAAC;YACJ,CAAC;YAED,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBACH,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,QAAQ,CAAC,gBAAgB,EAAE,+CAA+C,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;YAChG,CAAC;YAED,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBACH,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,QAAQ,CAAC,eAAe,EAAE,8CAA8C,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;YAChG,CAAC;YAED,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;YAElE,MAAM,QAAQ,GAAG,MAAM,cAAc,CACnC,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAChD,CAAC;YACF,IAAI,QAAQ,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBACzC,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,EAChE,CAAC,2BAA2B,QAAQ,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC;gBAC3D,OAAO,CAAC,CAAC;YACX,CAAC;YACD,IAAI,QAAQ,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBACzC,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,EAChE,CAAC,iDAAiD,QAAQ,CAAC,MAAM,IAAI,KAAK,EAAE;oBAC3E,sDAAsD,CAAC,CAAC,CAAC;gBAC5D,OAAO,CAAC,CAAC;YACX,CAAC;YAED,MAAM,OAAO,GAAG,aAAa,CAAC;gBAC5B,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,UAAU,EAAE,EAAE;gBACd,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9C,CAAC,CAAC;YACH,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAEnC,yDAAyD;YACzD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC;gBACpD,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC;aACzE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBACZ,MAAM,QAAQ,CACZ,eAAe,EACf,OAAO,KAAK,sBAAsB,GAAG,CAAC,OAAO,GAAG,EAChD,uDAAuD,CACxD,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;gBAClE,MAAM,QAAQ,CACZ,eAAe,EACf,OAAO,KAAK,gBAAgB,KAAK,kCAAkC,OAAO,CAAC,OAAO,CAAC,OAAO,GAAG,EAC7F,uCAAuC,CACxC,CAAC;YACJ,CAAC;YAED,sDAAsD;YACtD,oFAAoF;YACpF,IAAI,SAAS,GAAkB,IAAI,CAAC;YACpC,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC9B,MAAM,CAAC,cAAc,EAAE,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;oBAC9C,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC;wBAChC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,6BAA6B,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;qBACpG,CAAC;oBACF,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC;wBAChC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,wBAAwB,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;qBAC/F,CAAC;iBACH,CAAC,CAAC;gBACH,IAAI,cAAc,EAAE,CAAC;oBACnB,MAAM,QAAQ,CACZ,iBAAiB,EACjB,OAAO,KAAK,wBAAwB,KAAK,6BAA6B,EACtE,yCAAyC,CAC1C,CAAC;gBACJ,CAAC;gBACD,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;oBACf,MAAM,QAAQ,CACZ,kBAAkB,EAClB,OAAO,KAAK,mCAAmC,KAAK,GAAG,EACvD,gEAAgE,CACjE,CAAC;gBACJ,CAAC;gBACD,SAAS,GAAG,GAAG,CAAC;YAClB,CAAC;YAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,gBAAgB,CAAC;oBACtD,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,wBAAwB;oBACxE,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO;iBAC/C,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;oBACb,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;oBAChC,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,qBAAqB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBACpE,CAAC,CAAC,CAAC;gBACH,IAAI,CAAC;oBACH,SAAS,EAAE,IAAI;oBACf,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE;oBACvB,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE;oBACvB,KAAK;oBACL,GAAG,CAAC,SAAS,KAAK,IAAI;wBACpB,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;wBACrF,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,WAAW,EAAE,oDAAoD,EAAE,EAAE,CAAC;oBACzF,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,IAAI;iBACzC,CAAC,CAAC;gBACH,OAAO,CAAC,CAAC;YACX,CAAC;YAED,IAAI,IAAI,CAAC,cAAc;gBAAE,MAAM,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;YAEzE,IAAI,EAAiB,CAAC;YACtB,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC7E,EAAE,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC;oBAC5C,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,wBAAwB;oBACxE,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;oBACpB,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK;iBACnE,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;gBAChC,IAAI,IAAI,CAAC,cAAc;oBAAE,MAAM,UAAU,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC5F,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,qCAAqC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YACpF,CAAC;YAED,wEAAwE;YACxE,IAAI,IAAI,CAAC,cAAc;gBAAE,MAAM,aAAa,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YAErF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,yBAAyB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YACrG,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAClC,IAAI,IAAI,CAAC,cAAc;oBAAE,MAAM,UAAU,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;gBACjG,MAAM,QAAQ,CAAC,aAAa,EAAE,gCAAgC,EAAE,EAAE,CAAC,CAAC;YACtE,CAAC;YAED,MAAM,MAAM,GAAG;gBACb,MAAM,EAAE,EAAE;gBACV,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE;gBACvB,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE;gBACvB,GAAG,CAAC,SAAS,KAAK,IAAI;oBACpB,CAAC,CAAC,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;oBACzF,CAAC,CAAC,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,oDAAoD,EAAE,EAAE,CAAC;gBAC7F,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE;gBACrC,WAAW,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS;oBACpC,CAAC,CAAC,2BAA2B,EAAE,EAAE;oBACjC,CAAC,CAAC,mCAAmC,EAAE,EAAE;aAC5C,CAAC;YACF,IAAI,IAAI,CAAC,cAAc;gBAAE,MAAM,aAAa,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;YAE7F,MAAM,SAAS,GAAG,SAAS,KAAK,IAAI;gBAClC,CAAC,CAAC,GAAG,WAAW,CAAC,SAAS,EAAE,EAAE,CAAC,QAAQ;gBACvC,CAAC,CAAC,4BAA4B,CAAC;YACjC,IAAI,CAAC,MAAM,EAAE;gBACX,iBAAiB,KAAK,wBAAwB,KAAK,EAAE;gBACrD,aAAa,SAAS,EAAE;gBACxB,SAAS,MAAM,CAAC,WAAW,EAAE;gBAC7B,YAAY,OAAO,CAAC,WAAW,EAAE;aAClC,CAAC,CAAC;YACH,OAAO,CAAC,CAAC;QACX,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from './_base.js';
|
|
2
|
+
export declare class ExtendLockCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
static usage: import("clipanion").Usage;
|
|
5
|
+
lockupId: string;
|
|
6
|
+
duration: string;
|
|
7
|
+
idempotencyKey: string | undefined;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
execute(): Promise<number>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=extend-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extend-lock.d.ts","sourceRoot":"","sources":["../../src/commands/extend-lock.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASzC,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,OAAgB,KAAK,aAAqB;IAE1C,OAAgB,KAAK,4BAUlB;IAEH,QAAQ,SAAqC;IAC7C,QAAQ,SAAgG;IACxG,cAAc,qBAAsC;IACpD,MAAM,UAAsC;IAEtC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;CAoLjC"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reppo extend-lock <lockupId> --duration <seconds>` — extend an
|
|
3
|
+
* existing veREPPO lockup's expiry by `duration` more seconds.
|
|
4
|
+
*
|
|
5
|
+
* Pre-flight checks (read-only) before sending:
|
|
6
|
+
* 1. Caller owns the lockup (ownerOf(lockupId) === caller address)
|
|
7
|
+
* 2. Duration is within [minStakeDuration, maxStakeDuration]
|
|
8
|
+
* 3. Capture lockupData(lockupId) for before/after state in the result
|
|
9
|
+
*
|
|
10
|
+
* Idempotency two-phase write protocol:
|
|
11
|
+
* begin → submit tx → markSubmitted → wait receipt → markConfirmed
|
|
12
|
+
*
|
|
13
|
+
* Args fingerprint: { lockupId, duration } — re-using one --idempotency-key
|
|
14
|
+
* with a different lockupId or duration is rejected with
|
|
15
|
+
* IDEMPOTENCY_ARGS_MISMATCH.
|
|
16
|
+
*/
|
|
17
|
+
import { Option } from 'clipanion';
|
|
18
|
+
import { BaseCommand } from './_base.js';
|
|
19
|
+
import { cliError, emit } from '../output/format.js';
|
|
20
|
+
import { createClients, nextNonce } from '../chain/clients.js';
|
|
21
|
+
import { veReppo } from '../chain/contracts.js';
|
|
22
|
+
import { decodeRevert } from '../chain/errors.js';
|
|
23
|
+
import { begin, markSubmitted, markConfirmed, markFailed, peekIdempotent } from '../state/idempotency.js';
|
|
24
|
+
const COMMAND = 'extend-lock';
|
|
25
|
+
export class ExtendLockCommand extends BaseCommand {
|
|
26
|
+
static paths = [['extend-lock']];
|
|
27
|
+
static usage = BaseCommand.Usage({
|
|
28
|
+
description: 'Extend an existing veREPPO lockup by `duration` more seconds.',
|
|
29
|
+
examples: [
|
|
30
|
+
['Extend lockup 7 by 1 day',
|
|
31
|
+
'reppo extend-lock 7 --duration 86400'],
|
|
32
|
+
['With idempotency key',
|
|
33
|
+
'reppo extend-lock 7 --duration 86400 --idempotency-key extend-7-1d'],
|
|
34
|
+
['Dry-run (simulate only)',
|
|
35
|
+
'reppo extend-lock 7 --duration 86400 --dry-run'],
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
lockupId = Option.String({ required: true });
|
|
39
|
+
duration = Option.String('--duration', { required: true, description: 'Seconds to add to the lockup' });
|
|
40
|
+
idempotencyKey = Option.String('--idempotency-key');
|
|
41
|
+
dryRun = Option.Boolean('--dry-run', false);
|
|
42
|
+
async execute() {
|
|
43
|
+
try {
|
|
44
|
+
const cfg = this.loadConfig();
|
|
45
|
+
const pk = cfg.privateKey;
|
|
46
|
+
if (!pk) {
|
|
47
|
+
throw cliError('MISSING_PRIVATE_KEY', 'No signing key available.', 'Set REPPO_PRIVATE_KEY in env.');
|
|
48
|
+
}
|
|
49
|
+
let lockupId;
|
|
50
|
+
try {
|
|
51
|
+
lockupId = BigInt(this.lockupId);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw cliError('INVALID_LOCKUP_ID', `Lockup id must be a non-negative integer; got "${this.lockupId}".`);
|
|
55
|
+
}
|
|
56
|
+
let duration;
|
|
57
|
+
try {
|
|
58
|
+
duration = BigInt(this.duration);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw cliError('INVALID_DURATION', `Duration must be a non-negative integer (seconds); got "${this.duration}".`);
|
|
62
|
+
}
|
|
63
|
+
if (duration <= 0n) {
|
|
64
|
+
throw cliError('INVALID_DURATION', 'Duration must be greater than 0 seconds.');
|
|
65
|
+
}
|
|
66
|
+
const args = { lockupId: lockupId.toString(), duration: duration.toString() };
|
|
67
|
+
const decision = await peekIdempotent(this.idempotencyKey, COMMAND, args, this.dryRun);
|
|
68
|
+
if (decision.kind === 'return-confirmed') {
|
|
69
|
+
emit({ ...decision.result, idempotent: true, status: 'confirmed' }, [`(cached, confirmed) tx: ${decision.txHash ?? 'n/a'}`]);
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
if (decision.kind === 'return-submitted') {
|
|
73
|
+
emit({ ...decision.result, idempotent: true, status: 'submitted' }, [`(cached, submitted but not confirmed yet) tx: ${decision.txHash ?? 'n/a'}`,
|
|
74
|
+
`Re-run after the tx confirms, or check the explorer.`]);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
const clients = createClients({
|
|
78
|
+
network: cfg.network,
|
|
79
|
+
privateKey: pk,
|
|
80
|
+
...(cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}),
|
|
81
|
+
});
|
|
82
|
+
const vr = veReppo(cfg.network); // throws ADDRESS_NOT_CONFIGURED on TBD networks
|
|
83
|
+
// Pre-flight: ownership
|
|
84
|
+
const owner = await clients.publicClient.readContract({
|
|
85
|
+
address: vr.address, abi: vr.abi, functionName: 'ownerOf', args: [lockupId],
|
|
86
|
+
}).catch(() => {
|
|
87
|
+
// Lockup doesn't exist (ownerOf reverts per ERC-721).
|
|
88
|
+
throw cliError('LOCKUP_NOT_FOUND', `Lockup ${lockupId} does not exist on ${cfg.network}.`, 'Verify the lockupId; check `reppo query voting-power` to find your active lockups.');
|
|
89
|
+
});
|
|
90
|
+
if (owner.toLowerCase() !== clients.account.address.toLowerCase()) {
|
|
91
|
+
throw cliError('NOT_LOCKUP_OWNER', `Lockup ${lockupId} is owned by ${owner}, not by the configured signer ${clients.account.address}.`, 'Use the private key of the lockup owner, or pass an idempotency key derived from a different intent.');
|
|
92
|
+
}
|
|
93
|
+
// Pre-flight: duration bounds
|
|
94
|
+
const [minDur, maxDur] = await Promise.all([
|
|
95
|
+
clients.publicClient.readContract({
|
|
96
|
+
address: vr.address, abi: vr.abi, functionName: 'minStakeDuration', args: [],
|
|
97
|
+
}),
|
|
98
|
+
clients.publicClient.readContract({
|
|
99
|
+
address: vr.address, abi: vr.abi, functionName: 'maxStakeDuration', args: [],
|
|
100
|
+
}),
|
|
101
|
+
]);
|
|
102
|
+
if (duration < minDur || duration > maxDur) {
|
|
103
|
+
throw cliError('DURATION_OUT_OF_BOUNDS', `Duration ${duration}s is outside the allowed range [${minDur}, ${maxDur}].`, `Pass --duration with a value in seconds between ${minDur} and ${maxDur}.`);
|
|
104
|
+
}
|
|
105
|
+
// Capture before-state for the result payload (also surfaces in dry-run).
|
|
106
|
+
const lockupBefore = await clients.publicClient.readContract({
|
|
107
|
+
address: vr.address, abi: vr.abi, functionName: 'lockupData', args: [lockupId],
|
|
108
|
+
});
|
|
109
|
+
if (this.dryRun) {
|
|
110
|
+
const sim = await clients.publicClient.simulateContract({
|
|
111
|
+
address: vr.address, abi: vr.abi, functionName: 'extendLock',
|
|
112
|
+
args: [lockupId, duration], account: clients.account,
|
|
113
|
+
}).catch((e) => {
|
|
114
|
+
const decoded = decodeRevert(e);
|
|
115
|
+
throw cliError(decoded.code, 'Simulation reverted', decoded.hint);
|
|
116
|
+
});
|
|
117
|
+
emit({
|
|
118
|
+
simulated: true,
|
|
119
|
+
lockupId: lockupId.toString(),
|
|
120
|
+
durationAdded: duration.toString(),
|
|
121
|
+
owner,
|
|
122
|
+
lockupBefore: {
|
|
123
|
+
amount: lockupBefore[0].toString(),
|
|
124
|
+
expiresAt: lockupBefore[1].toString(),
|
|
125
|
+
votingPower: lockupBefore[3].toString(),
|
|
126
|
+
},
|
|
127
|
+
gas: sim.request.gas?.toString() ?? null,
|
|
128
|
+
});
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
if (this.idempotencyKey)
|
|
132
|
+
await begin(this.idempotencyKey, COMMAND, args);
|
|
133
|
+
let tx;
|
|
134
|
+
try {
|
|
135
|
+
const nonce = await nextNonce(clients.publicClient, clients.account.address);
|
|
136
|
+
tx = await clients.walletClient.writeContract({
|
|
137
|
+
address: vr.address, abi: vr.abi, functionName: 'extendLock',
|
|
138
|
+
args: [lockupId, duration],
|
|
139
|
+
chain: clients.walletClient.chain, account: clients.account, nonce,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
const decoded = decodeRevert(e);
|
|
144
|
+
if (this.idempotencyKey)
|
|
145
|
+
await markFailed(this.idempotencyKey, COMMAND, args, decoded.code);
|
|
146
|
+
throw cliError(decoded.code, 'extend-lock tx failed to submit', decoded.hint);
|
|
147
|
+
}
|
|
148
|
+
// markSubmitted BEFORE waitForReceipt — closes the retry-resend window.
|
|
149
|
+
if (this.idempotencyKey)
|
|
150
|
+
await markSubmitted(this.idempotencyKey, COMMAND, args, tx);
|
|
151
|
+
const receipt = await clients.publicClient.waitForTransactionReceipt({ hash: tx, timeout: 120_000 });
|
|
152
|
+
if (receipt.status === 'reverted') {
|
|
153
|
+
if (this.idempotencyKey)
|
|
154
|
+
await markFailed(this.idempotencyKey, COMMAND, args, 'TX_REVERTED', tx);
|
|
155
|
+
throw cliError('TX_REVERTED', `extend-lock tx reverted: ${tx}`);
|
|
156
|
+
}
|
|
157
|
+
// Read the new state for the result payload.
|
|
158
|
+
const lockupAfter = await clients.publicClient.readContract({
|
|
159
|
+
address: vr.address, abi: vr.abi, functionName: 'lockupData', args: [lockupId],
|
|
160
|
+
});
|
|
161
|
+
const result = {
|
|
162
|
+
lockupId: lockupId.toString(),
|
|
163
|
+
durationAdded: duration.toString(),
|
|
164
|
+
txHash: tx,
|
|
165
|
+
block: receipt.blockNumber.toString(),
|
|
166
|
+
basescanUrl: cfg.network === 'mainnet'
|
|
167
|
+
? `https://basescan.org/tx/${tx}`
|
|
168
|
+
: `https://sepolia.basescan.org/tx/${tx}`,
|
|
169
|
+
lockupBefore: {
|
|
170
|
+
amount: lockupBefore[0].toString(),
|
|
171
|
+
expiresAt: lockupBefore[1].toString(),
|
|
172
|
+
votingPower: lockupBefore[3].toString(),
|
|
173
|
+
},
|
|
174
|
+
lockupAfter: {
|
|
175
|
+
amount: lockupAfter[0].toString(),
|
|
176
|
+
expiresAt: lockupAfter[1].toString(),
|
|
177
|
+
votingPower: lockupAfter[3].toString(),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
if (this.idempotencyKey)
|
|
181
|
+
await markConfirmed(this.idempotencyKey, COMMAND, args, result, tx);
|
|
182
|
+
emit(result, [
|
|
183
|
+
`✓ Extended lockup ${lockupId} by ${duration}s`,
|
|
184
|
+
` expiresAt: ${lockupBefore[1]} → ${lockupAfter[1]}`,
|
|
185
|
+
` votingPower: ${lockupBefore[3]} → ${lockupAfter[3]}`,
|
|
186
|
+
` tx: ${result.basescanUrl}`,
|
|
187
|
+
` block: ${receipt.blockNumber}`,
|
|
188
|
+
]);
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
this.handleError(err);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=extend-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extend-lock.js","sourceRoot":"","sources":["../../src/commands/extend-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from './_base.js';
|
|
2
|
+
export declare class GrantAccessCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
static usage: import("clipanion").Usage;
|
|
5
|
+
datanet: string;
|
|
6
|
+
to: string | undefined;
|
|
7
|
+
idempotencyKey: string | undefined;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
execute(): Promise<number>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=grant-access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grant-access.d.ts","sourceRoot":"","sources":["../../src/commands/grant-access.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASzC,qBAAa,kBAAmB,SAAQ,WAAW;IACjD,OAAgB,KAAK,aAAsB;IAE3C,OAAgB,KAAK,4BAYlB;IAEH,OAAO,SAAyG;IAChH,EAAE,qBAA6H;IAC/H,cAAc,qBAAsC;IACpD,MAAM,UAAsC;IAEtC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;CA2KjC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reppo grant-access --datanet <id> [--to <addr>]` — pay the REPPO
|
|
3
|
+
* access fee and grant `--to` access to the datanet. Defaults `--to`
|
|
4
|
+
* to the address derived from REPPO_PRIVATE_KEY.
|
|
5
|
+
*
|
|
6
|
+
* "Datanet" is the user-facing term; on-chain it's a "subnet" (the
|
|
7
|
+
* function is `accessSubnetWithREPPOFee` and we read `validSubnet` /
|
|
8
|
+
* `hasSubnetAccess` / `getAccessFeeREPPO`). The CLI surface translates.
|
|
9
|
+
*
|
|
10
|
+
* Pre-flight checks (all surface error codes BEFORE the cache write,
|
|
11
|
+
* so retries under the same --idempotency-key are safe):
|
|
12
|
+
* 1. validSubnet(datanetId) → DATANET_NOT_FOUND
|
|
13
|
+
* 2. hasSubnetAccess(datanetId, to) → ACCESS_ALREADY_GRANTED (cheap savings)
|
|
14
|
+
* 3. getAccessFeeREPPO(datanetId) → fee amount (also surfaced in the result)
|
|
15
|
+
* 4. REPPO balance >= fee → INSUFFICIENT_REPPO_BALANCE
|
|
16
|
+
* 5. REPPO allowance(caller→sm) ≥ → INSUFFICIENT_ALLOWANCE (matches the
|
|
17
|
+
* fee 0xfb8f41b2 selector errors.ts decodes)
|
|
18
|
+
*
|
|
19
|
+
* Idempotency two-phase write protocol:
|
|
20
|
+
* begin → submit tx → markSubmitted → wait receipt → markConfirmed
|
|
21
|
+
*
|
|
22
|
+
* Args fingerprint: { datanetId, to } — re-using one --idempotency-key
|
|
23
|
+
* for a different (datanetId, to) is rejected with IDEMPOTENCY_ARGS_MISMATCH.
|
|
24
|
+
*/
|
|
25
|
+
import { Option } from 'clipanion';
|
|
26
|
+
import { formatUnits, isAddress } from 'viem';
|
|
27
|
+
import { BaseCommand } from './_base.js';
|
|
28
|
+
import { cliError, emit } from '../output/format.js';
|
|
29
|
+
import { createClients, nextNonce } from '../chain/clients.js';
|
|
30
|
+
import { subnetManager, reppoToken } from '../chain/contracts.js';
|
|
31
|
+
import { decodeRevert } from '../chain/errors.js';
|
|
32
|
+
import { begin, markSubmitted, markConfirmed, markFailed, peekIdempotent } from '../state/idempotency.js';
|
|
33
|
+
const COMMAND = 'grant-access';
|
|
34
|
+
export class GrantAccessCommand extends BaseCommand {
|
|
35
|
+
static paths = [['grant-access']];
|
|
36
|
+
static usage = BaseCommand.Usage({
|
|
37
|
+
description: 'Pay the REPPO access fee and grant a datanet access to an address.',
|
|
38
|
+
examples: [
|
|
39
|
+
['Grant the wallet derived from REPPO_PRIVATE_KEY access to datanet 19',
|
|
40
|
+
'reppo grant-access --datanet 19'],
|
|
41
|
+
['Grant a different address access',
|
|
42
|
+
'reppo grant-access --datanet 19 --to 0x726c…E31d'],
|
|
43
|
+
['With idempotency key',
|
|
44
|
+
'reppo grant-access --datanet 19 --idempotency-key grant-19-self'],
|
|
45
|
+
['Dry-run',
|
|
46
|
+
'reppo grant-access --datanet 19 --dry-run'],
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
datanet = Option.String('--datanet', { required: true, description: 'Datanet (subnet) ID to grant access to' });
|
|
50
|
+
to = Option.String('--to', { description: 'Address to grant access to (defaults to address derived from REPPO_PRIVATE_KEY)' });
|
|
51
|
+
idempotencyKey = Option.String('--idempotency-key');
|
|
52
|
+
dryRun = Option.Boolean('--dry-run', false);
|
|
53
|
+
async execute() {
|
|
54
|
+
try {
|
|
55
|
+
const cfg = this.loadConfig();
|
|
56
|
+
const pk = cfg.privateKey;
|
|
57
|
+
if (!pk) {
|
|
58
|
+
throw cliError('MISSING_PRIVATE_KEY', 'No signing key available.', 'Set REPPO_PRIVATE_KEY in env.');
|
|
59
|
+
}
|
|
60
|
+
let datanetId;
|
|
61
|
+
try {
|
|
62
|
+
datanetId = BigInt(this.datanet);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw cliError('INVALID_DATANET_ID', `Datanet id must be a non-negative integer; got "${this.datanet}".`);
|
|
66
|
+
}
|
|
67
|
+
const clients = createClients({
|
|
68
|
+
network: cfg.network,
|
|
69
|
+
privateKey: pk,
|
|
70
|
+
...(cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}),
|
|
71
|
+
});
|
|
72
|
+
const target = this.to
|
|
73
|
+
? (isAddress(this.to)
|
|
74
|
+
? this.to
|
|
75
|
+
: (() => { throw cliError('INVALID_ADDRESS', `Invalid --to address: ${this.to}`); })())
|
|
76
|
+
: clients.account.address;
|
|
77
|
+
const args = { datanetId: datanetId.toString(), to: target.toLowerCase() };
|
|
78
|
+
const decision = await peekIdempotent(this.idempotencyKey, COMMAND, args, this.dryRun);
|
|
79
|
+
if (decision.kind === 'return-confirmed') {
|
|
80
|
+
emit({ ...decision.result, idempotent: true, status: 'confirmed' }, [`(cached, confirmed) tx: ${decision.txHash ?? 'n/a'}`]);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
if (decision.kind === 'return-submitted') {
|
|
84
|
+
emit({ ...decision.result, idempotent: true, status: 'submitted' }, [`(cached, submitted but not confirmed yet) tx: ${decision.txHash ?? 'n/a'}`,
|
|
85
|
+
`Re-run after the tx confirms, or check the explorer.`]);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
// Throwing variants — write commands must fail loud on TBD.
|
|
89
|
+
const sm = subnetManager(cfg.network);
|
|
90
|
+
const reppo = reppoToken(cfg.network);
|
|
91
|
+
// Pre-flight in parallel where independent.
|
|
92
|
+
const [valid, alreadyHasAccess, fee, balance, allowance] = await Promise.all([
|
|
93
|
+
clients.publicClient.readContract({
|
|
94
|
+
address: sm.address, abi: sm.abi, functionName: 'validSubnet', args: [datanetId],
|
|
95
|
+
}),
|
|
96
|
+
clients.publicClient.readContract({
|
|
97
|
+
address: sm.address, abi: sm.abi, functionName: 'hasSubnetAccess', args: [datanetId, target],
|
|
98
|
+
}),
|
|
99
|
+
clients.publicClient.readContract({
|
|
100
|
+
address: sm.address, abi: sm.abi, functionName: 'getAccessFeeREPPO', args: [datanetId],
|
|
101
|
+
}),
|
|
102
|
+
clients.publicClient.readContract({
|
|
103
|
+
address: reppo.address, abi: reppo.abi, functionName: 'balanceOf', args: [clients.account.address],
|
|
104
|
+
}),
|
|
105
|
+
clients.publicClient.readContract({
|
|
106
|
+
address: reppo.address, abi: reppo.abi, functionName: 'allowance', args: [clients.account.address, sm.address],
|
|
107
|
+
}),
|
|
108
|
+
]);
|
|
109
|
+
if (!valid) {
|
|
110
|
+
throw cliError('DATANET_NOT_FOUND', `Datanet ${datanetId} does not exist on ${cfg.network}.`, `Verify the id; check \`reppo query subnet ${datanetId}\` before granting access.`);
|
|
111
|
+
}
|
|
112
|
+
if (alreadyHasAccess) {
|
|
113
|
+
throw cliError('ACCESS_ALREADY_GRANTED', `${target} already has access to datanet ${datanetId}.`, 'Nothing to do — skip the call.');
|
|
114
|
+
}
|
|
115
|
+
if (balance < fee) {
|
|
116
|
+
throw cliError('INSUFFICIENT_REPPO_BALANCE', `Caller has ${formatUnits(balance, 18)} REPPO but the fee is ${formatUnits(fee, 18)} REPPO.`, 'Acquire more REPPO before granting access.');
|
|
117
|
+
}
|
|
118
|
+
if (allowance < fee) {
|
|
119
|
+
throw cliError('INSUFFICIENT_ALLOWANCE', `REPPO allowance from ${clients.account.address} to SubnetManager is ${formatUnits(allowance, 18)}, ` +
|
|
120
|
+
`below the fee of ${formatUnits(fee, 18)} REPPO.`, `Approve the SubnetManager (${sm.address}) for at least ${formatUnits(fee, 18)} REPPO. ` +
|
|
121
|
+
`Auto-approval lands in v0.2; for the alpha, send the approve() tx manually (e.g. via cast).`);
|
|
122
|
+
}
|
|
123
|
+
if (this.dryRun) {
|
|
124
|
+
const sim = await clients.publicClient.simulateContract({
|
|
125
|
+
address: sm.address, abi: sm.abi, functionName: 'accessSubnetWithREPPOFee',
|
|
126
|
+
args: [datanetId, target], account: clients.account,
|
|
127
|
+
}).catch((e) => {
|
|
128
|
+
const decoded = decodeRevert(e);
|
|
129
|
+
throw cliError(decoded.code, 'Simulation reverted', decoded.hint);
|
|
130
|
+
});
|
|
131
|
+
emit({
|
|
132
|
+
simulated: true,
|
|
133
|
+
datanetId: datanetId.toString(),
|
|
134
|
+
to: target,
|
|
135
|
+
feeREPPO: { raw: fee.toString(), formatted: formatUnits(fee, 18) },
|
|
136
|
+
gas: sim.request.gas?.toString() ?? null,
|
|
137
|
+
});
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
if (this.idempotencyKey)
|
|
141
|
+
await begin(this.idempotencyKey, COMMAND, args);
|
|
142
|
+
let tx;
|
|
143
|
+
try {
|
|
144
|
+
const nonce = await nextNonce(clients.publicClient, clients.account.address);
|
|
145
|
+
tx = await clients.walletClient.writeContract({
|
|
146
|
+
address: sm.address, abi: sm.abi, functionName: 'accessSubnetWithREPPOFee',
|
|
147
|
+
args: [datanetId, target],
|
|
148
|
+
chain: clients.walletClient.chain, account: clients.account, nonce,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
const decoded = decodeRevert(e);
|
|
153
|
+
if (this.idempotencyKey)
|
|
154
|
+
await markFailed(this.idempotencyKey, COMMAND, args, decoded.code);
|
|
155
|
+
throw cliError(decoded.code, 'grant-access tx failed to submit', decoded.hint);
|
|
156
|
+
}
|
|
157
|
+
// markSubmitted BEFORE waitForReceipt — closes the retry-resend window.
|
|
158
|
+
if (this.idempotencyKey)
|
|
159
|
+
await markSubmitted(this.idempotencyKey, COMMAND, args, tx);
|
|
160
|
+
const receipt = await clients.publicClient.waitForTransactionReceipt({ hash: tx, timeout: 120_000 });
|
|
161
|
+
if (receipt.status === 'reverted') {
|
|
162
|
+
if (this.idempotencyKey)
|
|
163
|
+
await markFailed(this.idempotencyKey, COMMAND, args, 'TX_REVERTED', tx);
|
|
164
|
+
throw cliError('TX_REVERTED', `grant-access tx reverted: ${tx}`);
|
|
165
|
+
}
|
|
166
|
+
const result = {
|
|
167
|
+
txHash: tx,
|
|
168
|
+
datanetId: datanetId.toString(),
|
|
169
|
+
to: target,
|
|
170
|
+
feeREPPO: { raw: fee.toString(), formatted: formatUnits(fee, 18) },
|
|
171
|
+
block: receipt.blockNumber.toString(),
|
|
172
|
+
basescanUrl: cfg.network === 'mainnet'
|
|
173
|
+
? `https://basescan.org/tx/${tx}`
|
|
174
|
+
: `https://sepolia.basescan.org/tx/${tx}`,
|
|
175
|
+
};
|
|
176
|
+
if (this.idempotencyKey)
|
|
177
|
+
await markConfirmed(this.idempotencyKey, COMMAND, args, result, tx);
|
|
178
|
+
emit(result, [
|
|
179
|
+
`✓ Granted ${target} access to datanet ${datanetId}`,
|
|
180
|
+
` fee paid: ${formatUnits(fee, 18)} REPPO`,
|
|
181
|
+
` tx: ${result.basescanUrl}`,
|
|
182
|
+
` block: ${receipt.blockNumber}`,
|
|
183
|
+
]);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
this.handleError(err);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=grant-access.js.map
|