@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 +49 -17
- package/dist/client-4HW4ZUHD.js +166 -0
- package/dist/index.js +9639 -2834
- package/package.json +21 -8
- package/scripts/gen-card-fonts.py +76 -0
- package/scripts/gen-card-wasm.mjs +57 -0
- package/scripts/patch-xmtp-binding.mjs +118 -0
- package/scripts/postinstall.mjs +86 -0
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
|
|
36
|
-
|
|
|
37
|
-
| `setup`
|
|
38
|
-
| `explore`
|
|
39
|
-
| `get`
|
|
40
|
-
| `price-history` | Display price history for a coin | No |
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
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
|
+
};
|