@zoralabs/cli 1.0.1 → 1.2.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/README.md CHANGED
@@ -25,26 +25,35 @@ zora explore
25
25
  zora get <address-or-name>
26
26
 
27
27
  # Check price history
28
- zora price-history <address-or-name>
28
+ zora get price-history <address-or-name>
29
+
30
+ # View recent trades on a coin
31
+ zora get trades <address-or-name>
32
+
33
+ # See top holders
34
+ zora get holders <address-or-name>
29
35
  ```
30
36
 
31
37
  ## Commands
32
38
 
33
- All commands support `--json` for machine-readable output. Commands with live data (`explore`, `balance`, `profile`) also support `--live` (interactive, default) and `--static` (snapshot). Use `--refresh <seconds>` to set the auto-refresh interval in `--live` mode.
34
-
35
- | Command | Description | Wallet required |
36
- | --------------- | --------------------------------------------------------- | --------------- |
37
- | `setup` | Guided first-time setup (wallet + API key + deposit info) | — |
38
- | `explore` | Browse top, new, and highest volume coins | No |
39
- | `get` | Look up a coin by address or name | No |
40
- | `price-history` | Display price history for a coin | No |
41
- | `auth` | Configure or check API key status | No |
42
- | `profile` | View creator or user profiles | No |
43
- | `buy` | Buy a coin | Yes |
44
- | `sell` | Sell a coin | Yes |
45
- | `balance` | Show wallet balances (ETH, USDC, ZORA) and coin positions | Yes |
46
- | `wallet` | Show wallet address, export key, or configure wallet | Yes |
47
- | `send` | Send tokens to another address | Yes |
39
+ All commands support `--json` for machine-readable output. Commands with live data (`explore`, `get`, `balance`, `profile`) also support `--live` (interactive, default) and `--static` (snapshot). Use `--refresh <seconds>` to set the auto-refresh interval in `--live` mode.
40
+
41
+ | Command | Description | Wallet required |
42
+ | ------------------- | --------------------------------------------------------- | --------------- |
43
+ | `setup` | Guided first-time setup (wallet + API key + deposit info) | — |
44
+ | `explore` | Browse top, new, and highest volume coins | No |
45
+ | `get` | Look up a coin by address or name | No |
46
+ | `get price-history` | Display price history for a coin | No |
47
+ | `get trades` | Show recent buy/sell activity on a coin | No |
48
+ | `get holders` | Show top holders of a coin | No |
49
+ | `auth` | Configure or check API key status | No |
50
+ | `agent` | Create a headless Privy account for an agent | No |
51
+ | `profile` | View creator or user profiles | No |
52
+ | `buy` | Buy a coin | Yes |
53
+ | `sell` | Sell a coin | Yes |
54
+ | `balance` | Show wallet balances (ETH, USDC, ZORA) and coin positions | Yes |
55
+ | `wallet` | Show wallet address, export key, or configure wallet | Yes |
56
+ | `send` | Send tokens to another address | Yes |
48
57
 
49
58
  Run `zora --help` or `zora <command> --help` for detailed usage.
50
59
 
@@ -63,11 +72,34 @@ The private key is stored locally at `~/.config/zora/wallet.json` with restricte
63
72
 
64
73
  ### Advanced
65
74
 
66
- To configure wallet or API key individually (without running the full setup flow). All commands work without an API key but may be rate-limited:
75
+ To configure wallet or API key individually (without running the full setup flow). All commands work without an API key but may be rate-limited. An API key also provides more accurate coin valuations in `zora balance` by using the SDK's liquidity-aware pricing:
67
76
 
68
77
  - `zora wallet configure` — create or import a wallet (`--create`, `--force`)
69
78
  - `zora auth configure` — save an API key; `zora auth status` — check current config
70
79
 
80
+ ## Agents
81
+
82
+ `zora agent create` stands up a complete Zora agent identity from an EOA, with no human interaction: a headless Privy account (Sign-In-With-Ethereum — no Privy dashboard, email, or OTP), a Zora profile, a smart wallet, a creator coin, and a first post. Every on-chain step is paymaster-sponsored, so the agent needs no ETH, and it authenticates with only the Privy session — never a `zora.co/settings/developer` API key.
83
+
84
+ ```bash
85
+ # Full onboarding: account → profile → smart wallet → creator coin → first post
86
+ zora agent create
87
+
88
+ # Dry run: create the account, profile, and smart wallet, but simulate the coin + post
89
+ zora agent create --dry-run
90
+
91
+ # JSON output for automation
92
+ zora agent create --json
93
+
94
+ # Use a specific key without saving it, and skip steps as needed
95
+ zora agent create --private-key 0x... --skip-post
96
+
97
+ # Choose the agent's own profile at creation (all optional; otherwise autogenerated)
98
+ zora agent create --username my-agent --bio "gm, I trade memecoins" --avatar ./pfp.png
99
+ ```
100
+
101
+ The output includes the agent's profile handle, its smart wallet, **links to the new profile, creator coin, and first post**, and a **Privy access token** (a session JWT, ~1h) to send as `Authorization: Bearer <token>` for further Zora API calls. The EOA is resolved from `--private-key`, then `ZORA_PRIVATE_KEY`, then the saved CLI wallet (`~/.config/zora/wallet.json`); otherwise a new one is generated and saved — back it up, as it owns the agent. The first post's artwork is a bundled, Zora-themed greeting card (no avatar/username, so it stays appropriate permanently).
102
+
71
103
  ## Documentation
72
104
 
73
105
  Full documentation is available at [cli.zora.com](https://cli.zora.com/).
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/messaging/client.ts
4
+ import { homedir, platform } from "os";
5
+ import { join } from "path";
6
+ import { mkdirSync } from "fs";
7
+ import {
8
+ Client,
9
+ ConsentState,
10
+ IdentifierKind,
11
+ LogLevel,
12
+ isText
13
+ } from "@xmtp/node-sdk";
14
+ import { ReactionCodec } from "@xmtp/content-type-reaction";
15
+ import { ReadReceiptCodec } from "@xmtp/content-type-read-receipt";
16
+ import { getAddress } from "viem";
17
+ var CONSENT_TO_SDK = {
18
+ allowed: ConsentState.Allowed,
19
+ unknown: ConsentState.Unknown,
20
+ denied: ConsentState.Denied
21
+ };
22
+ var consentFromSdk = (state) => {
23
+ if (state === ConsentState.Allowed) return "allowed";
24
+ if (state === ConsentState.Denied) return "denied";
25
+ return "unknown";
26
+ };
27
+ var toIdentifier = (address) => ({
28
+ identifier: address.toLowerCase(),
29
+ identifierKind: IdentifierKind.Ethereum
30
+ });
31
+ var formatContentType = (ct) => `${ct.authorityId}/${ct.typeId}:${ct.versionMajor}.${ct.versionMinor}`;
32
+ var asAddress = (value) => {
33
+ if (!value) return null;
34
+ try {
35
+ return getAddress(value);
36
+ } catch {
37
+ return null;
38
+ }
39
+ };
40
+ var defaultDbPath = (env, inboxId) => {
41
+ const base = platform() === "win32" ? join(
42
+ process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"),
43
+ "zora"
44
+ ) : join(homedir(), ".config", "zora");
45
+ const dir = join(base, "xmtp");
46
+ mkdirSync(dir, { recursive: true });
47
+ return join(dir, `xmtp-${env}-${inboxId}.db3`);
48
+ };
49
+ var buildXmtpSigner = (spec) => {
50
+ const getIdentifier = () => toIdentifier(spec.address);
51
+ if (spec.type === "EOA") {
52
+ return { type: "EOA", getIdentifier, signMessage: spec.signMessage };
53
+ }
54
+ return {
55
+ type: "SCW",
56
+ getIdentifier,
57
+ getChainId: () => BigInt(spec.chainId),
58
+ signMessage: spec.signMessage
59
+ };
60
+ };
61
+ var createMessagingClient = async (spec, options = {}) => {
62
+ const env = options.env ?? "production";
63
+ const signer = buildXmtpSigner(spec);
64
+ const clientOptions = {
65
+ env,
66
+ appVersion: "zora/cli",
67
+ codecs: [new ReactionCodec(), new ReadReceiptCodec()],
68
+ dbEncryptionKey: options.dbEncryptionKey,
69
+ dbPath: options.dbPath ?? ((inboxId) => defaultDbPath(env, inboxId)),
70
+ loggingLevel: LogLevel.Off
71
+ };
72
+ const client = await Client.create(signer, clientOptions);
73
+ if (options.registerInstallation) {
74
+ try {
75
+ await options.registerInstallation(client.installationId);
76
+ } catch {
77
+ }
78
+ }
79
+ const address = getAddress(spec.address);
80
+ const selfInboxId = client.inboxId;
81
+ const addressMapForDm = async (dm) => {
82
+ const members = await dm.members();
83
+ const map = /* @__PURE__ */ new Map();
84
+ for (const member of members) {
85
+ const addr = asAddress(member.accountIdentifiers[0]?.identifier);
86
+ if (addr) map.set(member.inboxId, addr);
87
+ }
88
+ return map;
89
+ };
90
+ const toMessage = (message, addrByInbox) => ({
91
+ id: message.id,
92
+ senderAddress: addrByInbox.get(message.senderInboxId) ?? null,
93
+ fromSelf: message.senderInboxId === selfInboxId,
94
+ text: isText(message) ? message.content ?? null : null,
95
+ contentType: formatContentType(message.contentType),
96
+ sentAtMs: Number(message.sentAtNs / 1000000n)
97
+ });
98
+ const findDm = (peerAddress) => client.conversations.fetchDmByIdentifier(toIdentifier(peerAddress));
99
+ return {
100
+ address,
101
+ async sync(consent) {
102
+ const states = consent?.map((c) => CONSENT_TO_SDK[c]);
103
+ await client.conversations.syncAll(states);
104
+ await client.conversations.sync();
105
+ },
106
+ async listDms(consent) {
107
+ const states = consent?.map((c) => CONSENT_TO_SDK[c]);
108
+ const dms = client.conversations.listDms({ consentStates: states });
109
+ return Promise.all(
110
+ dms.map(async (dm) => {
111
+ const [addrByInbox, lastMessage] = await Promise.all([
112
+ addressMapForDm(dm),
113
+ dm.lastMessage()
114
+ ]);
115
+ return {
116
+ id: dm.id,
117
+ peerAddress: addrByInbox.get(dm.peerInboxId) ?? null,
118
+ consent: consentFromSdk(dm.consentState()),
119
+ profile: null,
120
+ lastMessage: lastMessage ? toMessage(lastMessage, addrByInbox) : null
121
+ };
122
+ })
123
+ );
124
+ },
125
+ async readMessages(peerAddress, limit = 30) {
126
+ const dm = await findDm(peerAddress);
127
+ if (!dm) return [];
128
+ const [addrByInbox, messages] = await Promise.all([
129
+ addressMapForDm(dm),
130
+ dm.messages({ limit })
131
+ ]);
132
+ return messages.map((m) => toMessage(m, addrByInbox));
133
+ },
134
+ async sendText(peerAddress, text, gateNewConversation) {
135
+ let dm = await findDm(peerAddress);
136
+ if (!dm) {
137
+ if (gateNewConversation) await gateNewConversation(peerAddress);
138
+ dm = await client.conversations.createDmWithIdentifier(
139
+ toIdentifier(peerAddress)
140
+ );
141
+ }
142
+ const id = await dm.sendText(text);
143
+ return {
144
+ id,
145
+ senderAddress: address,
146
+ fromSelf: true,
147
+ text,
148
+ contentType: "xmtp.org/text:1.0",
149
+ sentAtMs: Date.now()
150
+ };
151
+ },
152
+ async setConsent(peerAddress, consent) {
153
+ const dm = await findDm(peerAddress);
154
+ if (!dm) {
155
+ throw new Error(`No conversation found with ${peerAddress}`);
156
+ }
157
+ await dm.updateConsentState(CONSENT_TO_SDK[consent]);
158
+ },
159
+ async close() {
160
+ }
161
+ };
162
+ };
163
+ export {
164
+ buildXmtpSigner,
165
+ createMessagingClient
166
+ };