@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,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reppo query voting-power [address]` — show veREPPO voting power and
|
|
3
|
+
* lockup count for an address. Defaults to the address derived from
|
|
4
|
+
* REPPO_PRIVATE_KEY if no address is given.
|
|
5
|
+
*
|
|
6
|
+
* veReppo is the lockup NFT: each lockup the user has minted is a token
|
|
7
|
+
* counted by `balanceOf(address)`, and `votingPowerOf(address)` aggregates
|
|
8
|
+
* the points across all of them. An agent that just locked needs both:
|
|
9
|
+
* "did the lockup mint succeed?" (lockupCount went up) and "how much
|
|
10
|
+
* voting power did I get?" (votingPowerOf delta).
|
|
11
|
+
*
|
|
12
|
+
* If veReppo is TBD on the chosen network (mainnet today), each field
|
|
13
|
+
* emits `{ unavailable: "<reason>" }` rather than a misleading 0.
|
|
14
|
+
*/
|
|
15
|
+
import { Option } from 'clipanion';
|
|
16
|
+
import { formatUnits, isAddress } from 'viem';
|
|
17
|
+
import { privateKeyToAddress } from 'viem/accounts';
|
|
18
|
+
import { BaseCommand } from '../_base.js';
|
|
19
|
+
import { cliError, emit } from '../../output/format.js';
|
|
20
|
+
import { createReadClient } from '../../chain/clients.js';
|
|
21
|
+
import { tryVeReppo } from '../../chain/contracts.js';
|
|
22
|
+
function unavailable(network) {
|
|
23
|
+
return { unavailable: `veReppo address not configured for ${network}.` };
|
|
24
|
+
}
|
|
25
|
+
export class QueryVotingPowerCommand extends BaseCommand {
|
|
26
|
+
static paths = [['query', 'voting-power']];
|
|
27
|
+
static usage = BaseCommand.Usage({
|
|
28
|
+
description: 'Show veREPPO voting power and lockup count for an address.',
|
|
29
|
+
examples: [
|
|
30
|
+
['Query the wallet derived from REPPO_PRIVATE_KEY', 'reppo query voting-power'],
|
|
31
|
+
['Query a specific address', 'reppo query voting-power 0x726c…E31d'],
|
|
32
|
+
['JSON output', 'reppo query voting-power --json'],
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
address = Option.String({ required: false });
|
|
36
|
+
async execute() {
|
|
37
|
+
try {
|
|
38
|
+
const cfg = this.loadConfig();
|
|
39
|
+
const addr = this.resolveAddress(cfg.privateKey);
|
|
40
|
+
const client = createReadClient({ network: cfg.network, ...(cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}) });
|
|
41
|
+
const ve = tryVeReppo(cfg.network);
|
|
42
|
+
const votingPower = ve
|
|
43
|
+
? await client.readContract({ ...ve, functionName: 'votingPowerOf', args: [addr] })
|
|
44
|
+
.then((v) => ({ raw: v.toString(), formatted: formatUnits(v, 18) }))
|
|
45
|
+
: unavailable(cfg.network);
|
|
46
|
+
const lockupCount = ve
|
|
47
|
+
? await client.readContract({ ...ve, functionName: 'balanceOf', args: [addr] })
|
|
48
|
+
.then((v) => ({ value: v.toString() }))
|
|
49
|
+
: unavailable(cfg.network);
|
|
50
|
+
const result = { address: addr, network: cfg.network, votingPower, lockupCount };
|
|
51
|
+
const vpFmt = 'unavailable' in votingPower
|
|
52
|
+
? `(unavailable: ${votingPower.unavailable})`
|
|
53
|
+
: votingPower.formatted;
|
|
54
|
+
const lcFmt = 'unavailable' in lockupCount
|
|
55
|
+
? `(unavailable: ${lockupCount.unavailable})`
|
|
56
|
+
: lockupCount.value;
|
|
57
|
+
emit(result, [
|
|
58
|
+
`Address: ${addr}`,
|
|
59
|
+
`Network: ${cfg.network}`,
|
|
60
|
+
`Voting power: ${vpFmt}`,
|
|
61
|
+
`Lockup count: ${lcFmt}`,
|
|
62
|
+
]);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
this.handleError(err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
resolveAddress(pk) {
|
|
70
|
+
if (this.address) {
|
|
71
|
+
if (!isAddress(this.address)) {
|
|
72
|
+
throw cliError('INVALID_ADDRESS', `Invalid address: ${this.address}`);
|
|
73
|
+
}
|
|
74
|
+
return this.address;
|
|
75
|
+
}
|
|
76
|
+
if (!pk) {
|
|
77
|
+
throw cliError('MISSING_ADDRESS', 'No address provided and REPPO_PRIVATE_KEY not set.', 'Pass an address argument or set REPPO_PRIVATE_KEY in env.');
|
|
78
|
+
}
|
|
79
|
+
return privateKeyToAddress(pk);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=voting-power.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voting-power.js","sourceRoot":"","sources":["../../../src/commands/query/voting-power.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from './_base.js';
|
|
2
|
+
export declare class RegisterAgentCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
static usage: import("clipanion").Usage;
|
|
5
|
+
agentName: string;
|
|
6
|
+
description: string;
|
|
7
|
+
execute(): Promise<number>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=register-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-agent.d.ts","sourceRoot":"","sources":["../../src/commands/register-agent.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAqBzC,qBAAa,oBAAqB,SAAQ,WAAW;IACnD,OAAgB,KAAK,aAAe;IAEpC,OAAgB,KAAK,4BAQlB;IAEH,SAAS,SAA0F;IACnG,WAAW,SAA0F;IAE/F,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;CA+EjC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reppo register-agent --name <s> --description <s>` — register a new
|
|
3
|
+
* agent identity on the Reppo platform. Returns a persistent agent
|
|
4
|
+
* id + accessToken + walletAddress on Base.
|
|
5
|
+
*
|
|
6
|
+
* Endpoint: `POST https://reppo.ai/api/v1/agents/register` (no auth).
|
|
7
|
+
*
|
|
8
|
+
* The Reppo platform exposes two distinct agent auth surfaces:
|
|
9
|
+
* - `reppo.ai/api/v1/agents/...` — uses the persistent `accessToken`
|
|
10
|
+
* returned by this command (Bearer header). Used for pod-metadata
|
|
11
|
+
* submissions and agent-scoped reads.
|
|
12
|
+
* - `api.reppo.xyz/...` — uses an EIP-191 wallet-signed 24h session
|
|
13
|
+
* token (separate `reppo auth` flow, lands in v0.2 of this CLI).
|
|
14
|
+
*
|
|
15
|
+
* Each call to /agents/register creates a NEW identity with a NEW
|
|
16
|
+
* walletAddress. Re-registering does NOT recover a previous agent —
|
|
17
|
+
* pods minted by the prior agent stay attributed to that prior
|
|
18
|
+
* id/wallet. Save the returned credentials.
|
|
19
|
+
*
|
|
20
|
+
* The new walletAddress starts unfunded. Send ETH (gas) and REPPO
|
|
21
|
+
* (publishing fee) to it before the agent attempts on-chain mints.
|
|
22
|
+
*
|
|
23
|
+
* Output schema:
|
|
24
|
+
* { id, accessToken, walletAddress }
|
|
25
|
+
*
|
|
26
|
+
* No private key required — registration is permissionless. The CLI's
|
|
27
|
+
* REPPO_PRIVATE_KEY is unused here; the agent's wallet is provisioned
|
|
28
|
+
* server-side and its credentials live in the accessToken.
|
|
29
|
+
*/
|
|
30
|
+
import { Option } from 'clipanion';
|
|
31
|
+
import { BaseCommand } from './_base.js';
|
|
32
|
+
import { cliError, emit } from '../output/format.js';
|
|
33
|
+
const COMMAND = 'register-agent';
|
|
34
|
+
// Hardcoded — the agents API lives at reppo.ai/api/v1, separate host from
|
|
35
|
+
// REPPO_API_URL (which points at api.reppo.xyz for wallet-auth-based calls).
|
|
36
|
+
// If/when those unify, replace with cfg.apiUrl + a path constant.
|
|
37
|
+
const AGENTS_API_BASE = 'https://reppo.ai/api/v1';
|
|
38
|
+
export class RegisterAgentCommand extends BaseCommand {
|
|
39
|
+
static paths = [[COMMAND]];
|
|
40
|
+
static usage = BaseCommand.Usage({
|
|
41
|
+
description: 'Register a new agent identity on the Reppo platform.',
|
|
42
|
+
examples: [
|
|
43
|
+
['Register a new agent',
|
|
44
|
+
'reppo register-agent --name "MyBot" --description "Posts pod summaries to X"'],
|
|
45
|
+
['JSON output (capture credentials programmatically)',
|
|
46
|
+
'reppo register-agent --name "MyBot" --description "..." --json'],
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
agentName = Option.String('--name', { required: true, description: 'Display name for the agent' });
|
|
50
|
+
description = Option.String('--description', { required: true, description: 'What the agent does' });
|
|
51
|
+
async execute() {
|
|
52
|
+
try {
|
|
53
|
+
const trimmedName = this.agentName.trim();
|
|
54
|
+
const trimmedDesc = this.description.trim();
|
|
55
|
+
if (!trimmedName) {
|
|
56
|
+
throw cliError('INVALID_NAME', '--name must not be empty.');
|
|
57
|
+
}
|
|
58
|
+
if (!trimmedDesc) {
|
|
59
|
+
throw cliError('INVALID_DESCRIPTION', '--description must not be empty.');
|
|
60
|
+
}
|
|
61
|
+
const url = `${AGENTS_API_BASE}/agents/register`;
|
|
62
|
+
let response;
|
|
63
|
+
try {
|
|
64
|
+
response = await fetch(url, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({ name: trimmedName, description: trimmedDesc }),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
// Network error (DNS, TLS, connection refused).
|
|
72
|
+
throw cliError('PLATFORM_API_UNREACHABLE', `Could not reach the Reppo platform at ${url}: ${e.message}.`, 'Check your internet connection. If the platform is down, try again in a few minutes.');
|
|
73
|
+
}
|
|
74
|
+
let body = null;
|
|
75
|
+
try {
|
|
76
|
+
body = (await response.json());
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Non-JSON response. Surface as PLATFORM_API_ERROR with the raw status.
|
|
80
|
+
}
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const platformMsg = body?.error ?? body?.message ?? `HTTP ${response.status}`;
|
|
83
|
+
throw cliError('PLATFORM_API_ERROR', `Registration failed: ${platformMsg}`, `If the error persists, file an issue with the request body (name + description) and the HTTP status (${response.status}).`);
|
|
84
|
+
}
|
|
85
|
+
const data = body?.data;
|
|
86
|
+
if (!data?.id || !data.accessToken || !data.walletAddress) {
|
|
87
|
+
throw cliError('PLATFORM_API_INVALID_RESPONSE', `Reppo platform returned 200 but the response is missing expected fields (id, accessToken, walletAddress).`, 'Capture the full HTTP response and file an issue.');
|
|
88
|
+
}
|
|
89
|
+
const result = {
|
|
90
|
+
id: data.id,
|
|
91
|
+
accessToken: data.accessToken,
|
|
92
|
+
walletAddress: data.walletAddress,
|
|
93
|
+
};
|
|
94
|
+
emit(result, [
|
|
95
|
+
`✓ Registered new agent "${trimmedName}"`,
|
|
96
|
+
``,
|
|
97
|
+
` id: ${data.id}`,
|
|
98
|
+
` walletAddress: ${data.walletAddress}`,
|
|
99
|
+
` accessToken: ${data.accessToken}`,
|
|
100
|
+
``,
|
|
101
|
+
`⚠ SAVE THESE CREDENTIALS NOW.`,
|
|
102
|
+
` - The accessToken is the agent's persistent credential — don't share it.`,
|
|
103
|
+
` - Re-running register-agent creates a NEW agent identity with a NEW walletAddress.`,
|
|
104
|
+
` Pods previously minted by an old agent stay attributed to the old wallet.`,
|
|
105
|
+
` - Fund ${data.walletAddress} with ETH (for gas) and REPPO (for publishing fees)`,
|
|
106
|
+
` before the agent attempts on-chain mints.`,
|
|
107
|
+
]);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
this.handleError(err);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=register-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-agent.js","sourceRoot":"","sources":["../../src/commands/register-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from './_base.js';
|
|
2
|
+
export declare class UnlockCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
static usage: import("clipanion").Usage;
|
|
5
|
+
lockupId: string;
|
|
6
|
+
to: string | undefined;
|
|
7
|
+
idempotencyKey: string | undefined;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
execute(): Promise<number>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=unlock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unlock.d.ts","sourceRoot":"","sources":["../../src/commands/unlock.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASzC,qBAAa,aAAc,SAAQ,WAAW;IAC5C,OAAgB,KAAK,aAAgB;IAErC,OAAgB,KAAK,4BAYlB;IAEH,QAAQ,SAAqC;IAC7C,EAAE,qBAAwG;IAC1G,cAAc,qBAAsC;IACpD,MAAM,UAAsC;IAEtC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;CA6JjC"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reppo unlock <lockupId> [--to <address>]` — withdraw an expired
|
|
3
|
+
* veREPPO lockup, returning the locked REPPO to `--to` (default: caller).
|
|
4
|
+
*
|
|
5
|
+
* On-chain function: `withdraw(uint256 tokenId, address to)`. The CLI
|
|
6
|
+
* surface uses "unlock" because that's the user-facing concept; the
|
|
7
|
+
* contract uses "withdraw" because it transfers the underlying REPPO.
|
|
8
|
+
*
|
|
9
|
+
* Pre-flight checks (all surface error codes BEFORE the cache write,
|
|
10
|
+
* so retries under the same --idempotency-key are safe):
|
|
11
|
+
* 1. INVALID_LOCKUP_ID (parse)
|
|
12
|
+
* 2. INVALID_ADDRESS (--to)
|
|
13
|
+
* 3. LOCKUP_NOT_FOUND (ownerOf reverts — token doesn't exist)
|
|
14
|
+
* 4. NOT_LOCKUP_OWNER (ownerOf !== caller)
|
|
15
|
+
* 5. LOCKUP_NOT_EXPIRED (expiresAt > current block timestamp)
|
|
16
|
+
*
|
|
17
|
+
* Two-phase write protocol via peekIdempotent.
|
|
18
|
+
*
|
|
19
|
+
* Args fingerprint: { lockupId, to } — re-using one --idempotency-key
|
|
20
|
+
* across different (lockupId, to) is rejected with IDEMPOTENCY_ARGS_MISMATCH.
|
|
21
|
+
*/
|
|
22
|
+
import { Option } from 'clipanion';
|
|
23
|
+
import { formatUnits, isAddress } from 'viem';
|
|
24
|
+
import { BaseCommand } from './_base.js';
|
|
25
|
+
import { cliError, emit } from '../output/format.js';
|
|
26
|
+
import { createClients, nextNonce } from '../chain/clients.js';
|
|
27
|
+
import { veReppo } from '../chain/contracts.js';
|
|
28
|
+
import { decodeRevert } from '../chain/errors.js';
|
|
29
|
+
import { begin, markSubmitted, markConfirmed, markFailed, peekIdempotent } from '../state/idempotency.js';
|
|
30
|
+
const COMMAND = 'unlock';
|
|
31
|
+
export class UnlockCommand extends BaseCommand {
|
|
32
|
+
static paths = [['unlock']];
|
|
33
|
+
static usage = BaseCommand.Usage({
|
|
34
|
+
description: 'Withdraw an expired veREPPO lockup, returning the locked REPPO.',
|
|
35
|
+
examples: [
|
|
36
|
+
['Withdraw lockup 7 to the caller',
|
|
37
|
+
'reppo unlock 7'],
|
|
38
|
+
['Withdraw to a different address',
|
|
39
|
+
'reppo unlock 7 --to 0x726c…E31d'],
|
|
40
|
+
['With idempotency key',
|
|
41
|
+
'reppo unlock 7 --idempotency-key unlock-7'],
|
|
42
|
+
['Dry-run',
|
|
43
|
+
'reppo unlock 7 --dry-run'],
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
lockupId = Option.String({ required: true });
|
|
47
|
+
to = Option.String('--to', { description: 'Address to receive the unlocked REPPO (defaults to caller)' });
|
|
48
|
+
idempotencyKey = Option.String('--idempotency-key');
|
|
49
|
+
dryRun = Option.Boolean('--dry-run', false);
|
|
50
|
+
async execute() {
|
|
51
|
+
try {
|
|
52
|
+
const cfg = this.loadConfig();
|
|
53
|
+
const pk = cfg.privateKey;
|
|
54
|
+
if (!pk) {
|
|
55
|
+
throw cliError('MISSING_PRIVATE_KEY', 'No signing key available.', 'Set REPPO_PRIVATE_KEY in env.');
|
|
56
|
+
}
|
|
57
|
+
let lockupId;
|
|
58
|
+
try {
|
|
59
|
+
lockupId = BigInt(this.lockupId);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw cliError('INVALID_LOCKUP_ID', `Lockup id must be a non-negative integer; got "${this.lockupId}".`);
|
|
63
|
+
}
|
|
64
|
+
const clients = createClients({
|
|
65
|
+
network: cfg.network,
|
|
66
|
+
privateKey: pk,
|
|
67
|
+
...(cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}),
|
|
68
|
+
});
|
|
69
|
+
const target = this.to
|
|
70
|
+
? (isAddress(this.to)
|
|
71
|
+
? this.to
|
|
72
|
+
: (() => { throw cliError('INVALID_ADDRESS', `Invalid --to address: ${this.to}`); })())
|
|
73
|
+
: clients.account.address;
|
|
74
|
+
const args = { lockupId: lockupId.toString(), to: target.toLowerCase() };
|
|
75
|
+
const decision = await peekIdempotent(this.idempotencyKey, COMMAND, args, this.dryRun);
|
|
76
|
+
if (decision.kind === 'return-confirmed') {
|
|
77
|
+
emit({ ...decision.result, idempotent: true, status: 'confirmed' }, [`(cached, confirmed) tx: ${decision.txHash ?? 'n/a'}`]);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
if (decision.kind === 'return-submitted') {
|
|
81
|
+
emit({ ...decision.result, idempotent: true, status: 'submitted' }, [`(cached, submitted but not confirmed yet) tx: ${decision.txHash ?? 'n/a'}`,
|
|
82
|
+
`Re-run after the tx confirms, or check the explorer.`]);
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
// Throwing variant — write commands must fail loud on TBD.
|
|
86
|
+
const vr = veReppo(cfg.network);
|
|
87
|
+
// Pre-flight: ownership
|
|
88
|
+
const owner = await clients.publicClient.readContract({
|
|
89
|
+
address: vr.address, abi: vr.abi, functionName: 'ownerOf', args: [lockupId],
|
|
90
|
+
}).catch(() => {
|
|
91
|
+
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.');
|
|
92
|
+
});
|
|
93
|
+
if (owner.toLowerCase() !== clients.account.address.toLowerCase()) {
|
|
94
|
+
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.');
|
|
95
|
+
}
|
|
96
|
+
// Pre-flight: expiry. Reading lockupData + current block timestamp in
|
|
97
|
+
// parallel — withdraw() reverts if called before expiresAt, so we
|
|
98
|
+
// surface LOCKUP_NOT_EXPIRED as a structured error instead of letting
|
|
99
|
+
// gas burn on a doomed tx.
|
|
100
|
+
const [lockup, latestBlock] = await Promise.all([
|
|
101
|
+
clients.publicClient.readContract({
|
|
102
|
+
address: vr.address, abi: vr.abi, functionName: 'lockupData', args: [lockupId],
|
|
103
|
+
}),
|
|
104
|
+
clients.publicClient.getBlock({ blockTag: 'latest' }),
|
|
105
|
+
]);
|
|
106
|
+
const [amount, expiresAt] = lockup;
|
|
107
|
+
const now = latestBlock.timestamp;
|
|
108
|
+
if (expiresAt > now) {
|
|
109
|
+
const remaining = expiresAt - now;
|
|
110
|
+
throw cliError('LOCKUP_NOT_EXPIRED', `Lockup ${lockupId} expires at ${expiresAt} (${remaining}s remaining); current block timestamp is ${now}.`, `Wait until block timestamp ≥ ${expiresAt}, or use \`reppo extend-lock\` if you want to keep voting power.`);
|
|
111
|
+
}
|
|
112
|
+
if (this.dryRun) {
|
|
113
|
+
const sim = await clients.publicClient.simulateContract({
|
|
114
|
+
address: vr.address, abi: vr.abi, functionName: 'withdraw',
|
|
115
|
+
args: [lockupId, target], account: clients.account,
|
|
116
|
+
}).catch((e) => {
|
|
117
|
+
const decoded = decodeRevert(e);
|
|
118
|
+
throw cliError(decoded.code, 'Simulation reverted', decoded.hint);
|
|
119
|
+
});
|
|
120
|
+
emit({
|
|
121
|
+
simulated: true,
|
|
122
|
+
lockupId: lockupId.toString(),
|
|
123
|
+
to: target,
|
|
124
|
+
amount: { raw: amount.toString(), formatted: formatUnits(amount, 18) },
|
|
125
|
+
expiresAt: expiresAt.toString(),
|
|
126
|
+
gas: sim.request.gas?.toString() ?? null,
|
|
127
|
+
});
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
if (this.idempotencyKey)
|
|
131
|
+
await begin(this.idempotencyKey, COMMAND, args);
|
|
132
|
+
let tx;
|
|
133
|
+
try {
|
|
134
|
+
const nonce = await nextNonce(clients.publicClient, clients.account.address);
|
|
135
|
+
tx = await clients.walletClient.writeContract({
|
|
136
|
+
address: vr.address, abi: vr.abi, functionName: 'withdraw',
|
|
137
|
+
args: [lockupId, target],
|
|
138
|
+
chain: clients.walletClient.chain, account: clients.account, nonce,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
const decoded = decodeRevert(e);
|
|
143
|
+
if (this.idempotencyKey)
|
|
144
|
+
await markFailed(this.idempotencyKey, COMMAND, args, decoded.code);
|
|
145
|
+
throw cliError(decoded.code, 'unlock tx failed to submit', decoded.hint);
|
|
146
|
+
}
|
|
147
|
+
// markSubmitted BEFORE waitForReceipt — closes the retry-resend window.
|
|
148
|
+
if (this.idempotencyKey)
|
|
149
|
+
await markSubmitted(this.idempotencyKey, COMMAND, args, tx);
|
|
150
|
+
const receipt = await clients.publicClient.waitForTransactionReceipt({ hash: tx, timeout: 120_000 });
|
|
151
|
+
if (receipt.status === 'reverted') {
|
|
152
|
+
if (this.idempotencyKey)
|
|
153
|
+
await markFailed(this.idempotencyKey, COMMAND, args, 'TX_REVERTED', tx);
|
|
154
|
+
throw cliError('TX_REVERTED', `unlock tx reverted: ${tx}`);
|
|
155
|
+
}
|
|
156
|
+
const result = {
|
|
157
|
+
txHash: tx,
|
|
158
|
+
lockupId: lockupId.toString(),
|
|
159
|
+
to: target,
|
|
160
|
+
amount: { raw: amount.toString(), formatted: formatUnits(amount, 18) },
|
|
161
|
+
block: receipt.blockNumber.toString(),
|
|
162
|
+
basescanUrl: cfg.network === 'mainnet'
|
|
163
|
+
? `https://basescan.org/tx/${tx}`
|
|
164
|
+
: `https://sepolia.basescan.org/tx/${tx}`,
|
|
165
|
+
};
|
|
166
|
+
if (this.idempotencyKey)
|
|
167
|
+
await markConfirmed(this.idempotencyKey, COMMAND, args, result, tx);
|
|
168
|
+
emit(result, [
|
|
169
|
+
`✓ Unlocked lockup ${lockupId}`,
|
|
170
|
+
` ${formatUnits(amount, 18)} REPPO returned to ${target}`,
|
|
171
|
+
` tx: ${result.basescanUrl}`,
|
|
172
|
+
` block: ${receipt.blockNumber}`,
|
|
173
|
+
]);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
this.handleError(err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=unlock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unlock.js","sourceRoot":"","sources":["../../src/commands/unlock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from './_base.js';
|
|
2
|
+
export declare class VoteCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
static usage: import("clipanion").Usage;
|
|
5
|
+
pod: string;
|
|
6
|
+
subnet: string;
|
|
7
|
+
like: boolean;
|
|
8
|
+
dislike: boolean;
|
|
9
|
+
idempotencyKey: string | undefined;
|
|
10
|
+
dryRun: boolean;
|
|
11
|
+
execute(): Promise<number>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=vote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vote.d.ts","sourceRoot":"","sources":["../../src/commands/vote.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASzC,qBAAa,WAAY,SAAQ,WAAW;IAC1C,OAAgB,KAAK,aAAc;IAEnC,OAAgB,KAAK,4BAUlB;IAEH,GAAG,SAA2E;IAC9E,MAAM,SAA2E;IACjF,IAAI,UAAmC;IACvC,OAAO,UAAsC;IAC7C,cAAc,qBAAsC;IACpD,MAAM,UAAsC;IAEtC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;CA2JjC"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reppo vote --pod <id> --subnet <id> --like|--dislike` — cast an
|
|
3
|
+
* on-chain vote against a creative pod. Uses REPPO_VOTER_PRIVATE_KEY
|
|
4
|
+
* if set (separates voter and publisher EOAs since publishers cannot
|
|
5
|
+
* vote on their own pods), else falls back to REPPO_PRIVATE_KEY.
|
|
6
|
+
*
|
|
7
|
+
* Pre-flight checks (read-only) before sending:
|
|
8
|
+
* 1. Voter has voting power (veREPPO > 0)
|
|
9
|
+
* 2. Voter has subnet access
|
|
10
|
+
* Both produce structured errors with recovery hints.
|
|
11
|
+
*
|
|
12
|
+
* Idempotency two-phase write protocol:
|
|
13
|
+
* begin → submit tx → markSubmitted → wait receipt → markConfirmed
|
|
14
|
+
* A retry that fires after submit but before receipt will see the
|
|
15
|
+
* 'submitted' record and short-circuit with the cached txHash.
|
|
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 { podManager, subnetManager, 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 = 'vote';
|
|
25
|
+
export class VoteCommand extends BaseCommand {
|
|
26
|
+
static paths = [['vote']];
|
|
27
|
+
static usage = BaseCommand.Usage({
|
|
28
|
+
description: 'Cast a vote on a Reppo pod (testnet or mainnet).',
|
|
29
|
+
examples: [
|
|
30
|
+
['Like pod 34 in subnet 19',
|
|
31
|
+
'reppo vote --pod 34 --subnet 19 --like'],
|
|
32
|
+
['Dislike with idempotency key',
|
|
33
|
+
'reppo vote --pod 35 --subnet 19 --dislike --idempotency-key job-3858-B'],
|
|
34
|
+
['Dry-run (simulate only)',
|
|
35
|
+
'reppo vote --pod 34 --subnet 19 --like --dry-run'],
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
pod = Option.String('--pod', { required: true, description: 'Pod token ID' });
|
|
39
|
+
subnet = Option.String('--subnet', { required: true, description: 'Subnet ID' });
|
|
40
|
+
like = Option.Boolean('--like', false);
|
|
41
|
+
dislike = Option.Boolean('--dislike', false);
|
|
42
|
+
idempotencyKey = Option.String('--idempotency-key');
|
|
43
|
+
dryRun = Option.Boolean('--dry-run', false);
|
|
44
|
+
async execute() {
|
|
45
|
+
try {
|
|
46
|
+
if (this.like === this.dislike) {
|
|
47
|
+
throw cliError('INVALID_VOTE', 'Pass exactly one of --like or --dislike.', '--like and --dislike are mutually exclusive and one is required.');
|
|
48
|
+
}
|
|
49
|
+
const cfg = this.loadConfig();
|
|
50
|
+
const pk = cfg.voterPrivateKey ?? cfg.privateKey;
|
|
51
|
+
if (!pk) {
|
|
52
|
+
throw cliError('MISSING_PRIVATE_KEY', 'No signing key available.', 'Set REPPO_VOTER_PRIVATE_KEY (preferred) or REPPO_PRIVATE_KEY in env.');
|
|
53
|
+
}
|
|
54
|
+
const podId = BigInt(this.pod);
|
|
55
|
+
const subnetId = BigInt(this.subnet);
|
|
56
|
+
const likeBool = this.like;
|
|
57
|
+
// Args fingerprint baked into the cache so re-using one key with
|
|
58
|
+
// different (--pod, --subnet, --like) is rejected with
|
|
59
|
+
// IDEMPOTENCY_ARGS_MISMATCH instead of silently returning the
|
|
60
|
+
// wrong cached result.
|
|
61
|
+
const args = { podId: podId.toString(), subnetId: subnetId.toString(), like: likeBool };
|
|
62
|
+
// Dry-run NEVER consults or mutates the idempotency cache. A
|
|
63
|
+
// simulation is read-only by definition; returning a cached real
|
|
64
|
+
// tx hash with `simulated: true` would be a lie, and writing
|
|
65
|
+
// pending/submitted records would let a sim poison the cache for
|
|
66
|
+
// the subsequent real call. peekIdempotent enforces that policy
|
|
67
|
+
// (4th arg: isDryRun) and centralizes the pending/failed-after-
|
|
68
|
+
// broadcast guards so future commands share the same rules.
|
|
69
|
+
const decision = await peekIdempotent(this.idempotencyKey, COMMAND, args, this.dryRun);
|
|
70
|
+
if (decision.kind === 'return-confirmed') {
|
|
71
|
+
emit({ ...decision.result, idempotent: true, status: 'confirmed' }, [`(cached, confirmed) tx: ${decision.txHash ?? 'n/a'}`]);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
if (decision.kind === 'return-submitted') {
|
|
75
|
+
emit({ ...decision.result, idempotent: true, status: 'submitted' }, [`(cached, submitted but not confirmed yet) tx: ${decision.txHash ?? 'n/a'}`,
|
|
76
|
+
`Re-run after the tx confirms, or check the explorer.`]);
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
const clients = createClients({
|
|
80
|
+
network: cfg.network,
|
|
81
|
+
privateKey: pk,
|
|
82
|
+
...(cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}),
|
|
83
|
+
});
|
|
84
|
+
const pm = podManager(cfg.network);
|
|
85
|
+
const sm = subnetManager(cfg.network);
|
|
86
|
+
const vr = veReppo(cfg.network);
|
|
87
|
+
// Pre-flight: voting power
|
|
88
|
+
const power = (await clients.publicClient.readContract({
|
|
89
|
+
address: vr.address, abi: vr.abi, functionName: 'votingPowerOf', args: [clients.account.address],
|
|
90
|
+
}));
|
|
91
|
+
if (power === 0n) {
|
|
92
|
+
throw cliError('INSUFFICIENT_VOTING_POWER', 'Voter has zero voting power.', 'Run `reppo lock <amount> --duration <seconds>` first.');
|
|
93
|
+
}
|
|
94
|
+
// Pre-flight: subnet access
|
|
95
|
+
const hasAccess = (await clients.publicClient.readContract({
|
|
96
|
+
address: sm.address, abi: sm.abi, functionName: 'hasSubnetAccess', args: [subnetId, clients.account.address],
|
|
97
|
+
}));
|
|
98
|
+
if (!hasAccess) {
|
|
99
|
+
throw cliError('VOTER_LACKS_SUBNET_ACCESS', `Voter lacks subnet ${subnetId} access.`, `Run \`reppo grant-access --subnet ${subnetId}\` first.`);
|
|
100
|
+
}
|
|
101
|
+
if (this.dryRun) {
|
|
102
|
+
const sim = await clients.publicClient.simulateContract({
|
|
103
|
+
address: pm.address, abi: pm.abi, functionName: 'vote',
|
|
104
|
+
args: [podId, subnetId, likeBool], account: clients.account,
|
|
105
|
+
}).catch((e) => {
|
|
106
|
+
const decoded = decodeRevert(e);
|
|
107
|
+
throw cliError(decoded.code, 'Simulation reverted', decoded.hint);
|
|
108
|
+
});
|
|
109
|
+
emit({
|
|
110
|
+
simulated: true,
|
|
111
|
+
podId: podId.toString(),
|
|
112
|
+
subnetId: subnetId.toString(),
|
|
113
|
+
like: likeBool,
|
|
114
|
+
voterPower: power.toString(),
|
|
115
|
+
gas: sim.request.gas?.toString() ?? null,
|
|
116
|
+
});
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
// Two-phase write: begin → submit → markSubmitted → wait → markConfirmed.
|
|
120
|
+
if (this.idempotencyKey)
|
|
121
|
+
await begin(this.idempotencyKey, COMMAND, args);
|
|
122
|
+
let tx;
|
|
123
|
+
try {
|
|
124
|
+
const nonce = await nextNonce(clients.publicClient, clients.account.address);
|
|
125
|
+
tx = await clients.walletClient.writeContract({
|
|
126
|
+
address: pm.address, abi: pm.abi, functionName: 'vote',
|
|
127
|
+
args: [podId, subnetId, likeBool],
|
|
128
|
+
chain: clients.walletClient.chain, account: clients.account, nonce,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
const decoded = decodeRevert(e);
|
|
133
|
+
if (this.idempotencyKey)
|
|
134
|
+
await markFailed(this.idempotencyKey, COMMAND, args, decoded.code);
|
|
135
|
+
throw cliError(decoded.code, 'Vote tx failed to submit', decoded.hint);
|
|
136
|
+
}
|
|
137
|
+
// Persist 'submitted' BEFORE waiting for the receipt — that's the
|
|
138
|
+
// window where an agent retry could otherwise re-send.
|
|
139
|
+
if (this.idempotencyKey)
|
|
140
|
+
await markSubmitted(this.idempotencyKey, COMMAND, args, tx);
|
|
141
|
+
const receipt = await clients.publicClient.waitForTransactionReceipt({ hash: tx, timeout: 120_000 });
|
|
142
|
+
if (receipt.status === 'reverted') {
|
|
143
|
+
// Pass tx hash so the cached failed entry retains it for forensics
|
|
144
|
+
// AND so the same-key-retry guard (above) refuses re-broadcast.
|
|
145
|
+
if (this.idempotencyKey)
|
|
146
|
+
await markFailed(this.idempotencyKey, COMMAND, args, 'TX_REVERTED', tx);
|
|
147
|
+
throw cliError('TX_REVERTED', `Vote tx reverted: ${tx}`);
|
|
148
|
+
}
|
|
149
|
+
const result = {
|
|
150
|
+
txHash: tx,
|
|
151
|
+
podId: podId.toString(),
|
|
152
|
+
subnetId: subnetId.toString(),
|
|
153
|
+
like: likeBool,
|
|
154
|
+
voterPower: power.toString(),
|
|
155
|
+
block: receipt.blockNumber.toString(),
|
|
156
|
+
basescanUrl: cfg.network === 'mainnet'
|
|
157
|
+
? `https://basescan.org/tx/${tx}`
|
|
158
|
+
: `https://sepolia.basescan.org/tx/${tx}`,
|
|
159
|
+
};
|
|
160
|
+
if (this.idempotencyKey)
|
|
161
|
+
await markConfirmed(this.idempotencyKey, COMMAND, args, result, tx);
|
|
162
|
+
emit(result, [
|
|
163
|
+
`✓ Voted on pod ${podId} (${likeBool ? 'like' : 'dislike'})`,
|
|
164
|
+
` tx: ${result.basescanUrl}`,
|
|
165
|
+
` block: ${receipt.blockNumber}`,
|
|
166
|
+
]);
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
this.handleError(err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=vote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vote.js","sourceRoot":"","sources":["../../src/commands/vote.ts"],"names":[],"mappings":"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"}
|