@zoralabs/cli 1.1.0 → 1.3.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 +24 -0
- package/dist/client-4HW4ZUHD.js +166 -0
- package/dist/index.js +7328 -1804
- package/package.json +18 -7
- package/scripts/gen-card-fonts.py +76 -0
- package/scripts/gen-card-wasm.mjs +57 -0
- package/scripts/generate-skill-hashes.ts +210 -0
- package/scripts/patch-xmtp-binding.mjs +118 -0
- package/scripts/postinstall.mjs +14 -0
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ All commands support `--json` for machine-readable output. Commands with live da
|
|
|
47
47
|
| `get trades` | Show recent buy/sell activity on a coin | No |
|
|
48
48
|
| `get holders` | Show top holders of a coin | No |
|
|
49
49
|
| `auth` | Configure or check API key status | No |
|
|
50
|
+
| `agent` | Create a headless Privy account for an agent | No |
|
|
50
51
|
| `profile` | View creator or user profiles | No |
|
|
51
52
|
| `buy` | Buy a coin | Yes |
|
|
52
53
|
| `sell` | Sell a coin | Yes |
|
|
@@ -76,6 +77,29 @@ To configure wallet or API key individually (without running the full setup flow
|
|
|
76
77
|
- `zora wallet configure` — create or import a wallet (`--create`, `--force`)
|
|
77
78
|
- `zora auth configure` — save an API key; `zora auth status` — check current config
|
|
78
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
|
+
|
|
79
103
|
## Documentation
|
|
80
104
|
|
|
81
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
|
+
};
|