@sage-protocol/cli 0.7.9 → 0.8.2
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.
Potentially problematic release.
This version of @sage-protocol/cli might be problematic. Click here for more details.
- package/README.md +32 -11
- package/dist/cli/auth0-privy-wallet-manager.js +2 -1
- package/dist/cli/commands/dao.js +1 -1
- package/dist/cli/commands/install.js +180 -38
- package/dist/cli/commands/library.js +275 -267
- package/dist/cli/commands/personal.js +69 -2
- package/dist/cli/commands/wallet.js +5 -10
- package/dist/cli/index.js +5 -0
- package/dist/cli/privy-auth-wallet-manager.js +47 -13
- package/dist/cli/services/config/chain-defaults.js +1 -1
- package/dist/cli/services/config/manager.js +8 -0
- package/dist/cli/services/config/schema.js +1 -0
- package/dist/cli/services/ipfs/onboarding.js +11 -0
- package/dist/cli/services/wallet/context.js +15 -8
- package/dist/cli/subdao-manager.js +1 -1
- package/dist/cli/utils/aliases.js +1 -0
- package/dist/cli/utils/cli-ui.js +1 -1
- package/dist/cli/utils/provider.js +7 -3
- package/dist/cli/utils/wallet-shared.js +2 -1
- package/dist/cli/wallet-manager.js +15 -18
- package/dist/prompts/e2e-test-prompt.md +22 -0
- package/dist/prompts/skills/build-web3/plugin.json +11 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const axios = require('axios');
|
|
4
|
-
const { getAddress, id, parseUnits, formatUnits, Contract, MaxUint256 } = require('ethers');
|
|
4
|
+
const { getAddress, id, parseUnits, formatUnits, Contract, MaxUint256, ZeroAddress } = require('ethers');
|
|
5
5
|
const { decryptAesGcm, fromB64 } = require('../utils/aes');
|
|
6
6
|
const sdk = require('@sage-protocol/sdk');
|
|
7
7
|
const { formatJson } = require('../utils/format');
|
|
@@ -244,6 +244,42 @@ const PERSONAL_REGISTRY_QUERY = `
|
|
|
244
244
|
async function resolveOrCreatePersonalRegistry(wallet, opts = {}) {
|
|
245
245
|
const ownerAddress = getAddress(await wallet.getAddress());
|
|
246
246
|
const subgraphUrl = resolveSubgraphUrl(opts.subgraph);
|
|
247
|
+
const provider = wallet.provider || await getProvider();
|
|
248
|
+
|
|
249
|
+
const { resolveArtifact } = require('../utils/artifacts');
|
|
250
|
+
const PromptRegistryABI = resolveArtifact('contracts/PromptRegistry.sol/PromptRegistry.json').abi;
|
|
251
|
+
|
|
252
|
+
async function validateRegistryCandidate(registryAddr) {
|
|
253
|
+
try {
|
|
254
|
+
const code = await provider.getCode(registryAddr);
|
|
255
|
+
if (!code || code === '0x') {
|
|
256
|
+
return { ok: false, reason: 'no_code' };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const read = new Contract(registryAddr, PromptRegistryABI, provider);
|
|
260
|
+
|
|
261
|
+
// Ensure this registry is actually usable by the current wallet.
|
|
262
|
+
const govRole = await read.GOVERNANCE_ROLE().catch(() => null);
|
|
263
|
+
if (!govRole) return { ok: false, reason: 'missing_governance_role' };
|
|
264
|
+
|
|
265
|
+
const hasGovRole = await read.hasRole(govRole, ownerAddress).catch(() => null);
|
|
266
|
+
if (!hasGovRole) return { ok: false, reason: 'missing_governance_role_for_owner' };
|
|
267
|
+
|
|
268
|
+
// Personal registries should not be linked to a SubDAO.
|
|
269
|
+
const subdao = await read.subDAO().catch(() => null);
|
|
270
|
+
if (subdao && getAddress(subdao) !== getAddress(ZeroAddress)) {
|
|
271
|
+
return { ok: false, reason: 'linked_to_subdao', subdao: getAddress(subdao) };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// If paused, addPrompt will revert.
|
|
275
|
+
const paused = await read.paused().catch(() => false);
|
|
276
|
+
if (paused) return { ok: false, reason: 'paused' };
|
|
277
|
+
|
|
278
|
+
return { ok: true };
|
|
279
|
+
} catch (e) {
|
|
280
|
+
return { ok: false, reason: 'validation_error', error: e };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
247
283
|
|
|
248
284
|
// Try subgraph lookup first
|
|
249
285
|
if (subgraphUrl) {
|
|
@@ -254,7 +290,14 @@ async function resolveOrCreatePersonalRegistry(wallet, opts = {}) {
|
|
|
254
290
|
});
|
|
255
291
|
const registries = resp.data?.data?.personalRegistries || [];
|
|
256
292
|
if (registries.length > 0) {
|
|
257
|
-
|
|
293
|
+
const candidate = getAddress(registries[0].id);
|
|
294
|
+
const validation = await validateRegistryCandidate(candidate);
|
|
295
|
+
if (validation.ok) return candidate;
|
|
296
|
+
|
|
297
|
+
if (!opts.json) {
|
|
298
|
+
const extra = validation.subdao ? ` (subDAO=${validation.subdao})` : '';
|
|
299
|
+
ui.warn(`Resolved personal registry ${candidate} is not usable (${validation.reason}${extra}). Creating a fresh registry...`);
|
|
300
|
+
}
|
|
258
301
|
}
|
|
259
302
|
} catch (_) {
|
|
260
303
|
// Subgraph unavailable, fall through to create
|
|
@@ -309,6 +352,30 @@ async function addPromptToRegistry(wallet, registryAddr, promptData, opts = {})
|
|
|
309
352
|
const PromptRegistryABI = resolveArtifact('contracts/PromptRegistry.sol/PromptRegistry.json').abi;
|
|
310
353
|
const registry = new Contract(registryAddr, PromptRegistryABI, wallet);
|
|
311
354
|
|
|
355
|
+
// Proactive checks to surface actionable errors before spending gas.
|
|
356
|
+
try {
|
|
357
|
+
const ownerAddress = getAddress(await wallet.getAddress());
|
|
358
|
+
const govRole = await registry.GOVERNANCE_ROLE().catch(() => null);
|
|
359
|
+
if (!govRole) {
|
|
360
|
+
throw new Error(`Resolved registry ${registryAddr} does not expose GOVERNANCE_ROLE(); is this a valid PromptRegistry?`);
|
|
361
|
+
}
|
|
362
|
+
const hasGov = await registry.hasRole(govRole, ownerAddress).catch(() => null);
|
|
363
|
+
if (!hasGov) {
|
|
364
|
+
throw new Error(`Wallet ${ownerAddress} lacks GOVERNANCE_ROLE on registry ${registryAddr}. This usually means the registry is not your personal registry (or it was not initialized as personal).`);
|
|
365
|
+
}
|
|
366
|
+
const subdao = await registry.subDAO().catch(() => null);
|
|
367
|
+
if (subdao && getAddress(subdao) !== getAddress(ZeroAddress)) {
|
|
368
|
+
throw new Error(`Registry ${registryAddr} is linked to SubDAO ${getAddress(subdao)}; personal publish requires a personal registry (subDAO=0x0).`);
|
|
369
|
+
}
|
|
370
|
+
const paused = await registry.paused().catch(() => false);
|
|
371
|
+
if (paused) {
|
|
372
|
+
throw new Error(`Registry ${registryAddr} is paused; unpause it (DEFAULT_ADMIN_ROLE) or create a fresh personal registry.`);
|
|
373
|
+
}
|
|
374
|
+
} catch (e) {
|
|
375
|
+
// Re-throw with a cleaner message for JSON/non-JSON callers.
|
|
376
|
+
throw e;
|
|
377
|
+
}
|
|
378
|
+
|
|
312
379
|
const title = promptData.title || promptData.name || promptData.key || 'Untitled';
|
|
313
380
|
const tags = promptData.tags || [];
|
|
314
381
|
const contentCID = promptData.contentCID || '';
|
|
@@ -289,9 +289,10 @@ const Web3AuthWalletManager = require('../web3auth-wallet-manager');
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
// Device code flow via relay works with just appId (no secret needed)
|
|
292
|
-
//
|
|
292
|
+
// Always prefer relay unless --local is explicitly set
|
|
293
293
|
if (appId) {
|
|
294
294
|
if (options?.local) {
|
|
295
|
+
process.env.SAGE_PRIVY_LOGIN_MODE = 'local';
|
|
295
296
|
// Local OAuth requires appSecret for direct Privy SDK authentication
|
|
296
297
|
if (!appSecret) {
|
|
297
298
|
ui.error('Local OAuth mode requires PRIVY_APP_SECRET.');
|
|
@@ -300,15 +301,9 @@ const Web3AuthWalletManager = require('../web3auth-wallet-manager');
|
|
|
300
301
|
}
|
|
301
302
|
ui.info('Using local OAuth authentication...');
|
|
302
303
|
} else {
|
|
303
|
-
// Web
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
process.env.SAGE_PRIVY_LOGIN_MODE = 'device';
|
|
307
|
-
ui.info('Using device code authentication via web app relay...');
|
|
308
|
-
} else {
|
|
309
|
-
process.env.SAGE_PRIVY_LOGIN_MODE = 'web';
|
|
310
|
-
ui.info('Using web-based authentication...');
|
|
311
|
-
}
|
|
304
|
+
// Web relay flow - uses web app, no localhost callback
|
|
305
|
+
process.env.SAGE_PRIVY_LOGIN_MODE = 'web';
|
|
306
|
+
ui.info('Using web-based authentication via relay...');
|
|
312
307
|
}
|
|
313
308
|
|
|
314
309
|
const walletManager = new PrivyAuthWalletManager();
|
package/dist/cli/index.js
CHANGED
|
@@ -19,6 +19,11 @@ if (!process.env.CONTRACT_ARTIFACTS_ROOT) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// Always trust SAGE_RPC_URL over RPC_URL when provided
|
|
23
|
+
if (process.env.SAGE_RPC_URL) {
|
|
24
|
+
process.env.RPC_URL = process.env.SAGE_RPC_URL;
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
// Filter out deprecated Lit SDK warning immediately
|
|
23
28
|
const realStderrWrite = process.stderr.write.bind(process.stderr);
|
|
24
29
|
process.stderr.write = (chunk, encoding, callback) => {
|
|
@@ -47,8 +47,9 @@ class PrivyAuthWalletManager {
|
|
|
47
47
|
this.walletId = null;
|
|
48
48
|
this.accessToken = null;
|
|
49
49
|
|
|
50
|
-
// Privy app credentials from env
|
|
51
|
-
|
|
50
|
+
// Privy app credentials from env or defaults
|
|
51
|
+
const defaults = require('./defaults');
|
|
52
|
+
this.appId = process.env.PRIVY_APP_ID || process.env.NEXT_PUBLIC_PRIVY_APP_ID || defaults.PRIVY_APP_ID;
|
|
52
53
|
this.appSecret = process.env.PRIVY_APP_SECRET;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -79,14 +80,30 @@ class PrivyAuthWalletManager {
|
|
|
79
80
|
*/
|
|
80
81
|
async connect() {
|
|
81
82
|
try {
|
|
83
|
+
const relayEnabled = process.env.SAGE_DISABLE_PRIVY_RELAY !== '1';
|
|
84
|
+
const hasAppSecret = !!this.appSecret;
|
|
85
|
+
const mode = String(process.env.SAGE_PRIVY_LOGIN_MODE || '').trim().toLowerCase();
|
|
86
|
+
const forceRelay = relayEnabled && mode !== 'local' && mode !== 'oauth';
|
|
87
|
+
const preferSessionFlow = forceRelay;
|
|
88
|
+
|
|
82
89
|
// Try cached credentials first
|
|
83
90
|
const cached = readPrivyCredentials();
|
|
84
91
|
if (cached && cached.userId && cached.walletId && cached.walletAddress) {
|
|
85
92
|
// Verify the credentials are still valid
|
|
86
93
|
const valid = await this.verifyCredentials(cached);
|
|
87
94
|
if (valid) {
|
|
88
|
-
// Refresh canonical wallet info from Privy to get a valid wallet_id
|
|
89
95
|
this.accessToken = cached.accessToken;
|
|
96
|
+
if (preferSessionFlow) {
|
|
97
|
+
this.user = { id: cached.userId };
|
|
98
|
+
this.walletId = cached.walletId;
|
|
99
|
+
this.account = cached.walletAddress;
|
|
100
|
+
await this.setupProvider();
|
|
101
|
+
this.connected = true;
|
|
102
|
+
console.log(`Connected to Privy wallet: ${this.account}`);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Refresh canonical wallet info from Privy to get a valid wallet_id
|
|
90
107
|
const info = await this.getUserFromToken(this.accessToken);
|
|
91
108
|
|
|
92
109
|
// CRITICAL: Verify the JWT user matches the cached user
|
|
@@ -125,9 +142,8 @@ class PrivyAuthWalletManager {
|
|
|
125
142
|
|
|
126
143
|
// No valid cached credentials - need OAuth login
|
|
127
144
|
console.log('\nNo cached Privy credentials found.');
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const useDeviceCodeFlow = mode === 'device' || mode === 'device_code';
|
|
145
|
+
const useSessionFlow = preferSessionFlow || mode === 'web' || mode === 'session';
|
|
146
|
+
const useDeviceCodeFlow = !preferSessionFlow && (mode === 'device' || mode === 'device_code');
|
|
131
147
|
|
|
132
148
|
let authResult;
|
|
133
149
|
|
|
@@ -162,11 +178,22 @@ class PrivyAuthWalletManager {
|
|
|
162
178
|
|
|
163
179
|
this.accessToken = authResult.accessToken;
|
|
164
180
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
// If relay-only, avoid Privy API lookup and require session flow
|
|
182
|
+
if (preferSessionFlow && (!authResult.walletAddress || !authResult.walletId)) {
|
|
183
|
+
throw new Error('Privy relay login did not return wallet credentials. Re-run with `sage wallet connect`.');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (preferSessionFlow) {
|
|
187
|
+
this.user = { id: authResult.userId };
|
|
188
|
+
this.walletId = authResult.walletId;
|
|
189
|
+
this.account = authResult.walletAddress;
|
|
190
|
+
} else {
|
|
191
|
+
// Always resolve canonical wallet_id + address from Privy after OAuth
|
|
192
|
+
const info = await this.getUserFromToken(this.accessToken);
|
|
193
|
+
this.user = info.user;
|
|
194
|
+
this.walletId = info.walletId;
|
|
195
|
+
this.account = info.walletAddress;
|
|
196
|
+
}
|
|
170
197
|
|
|
171
198
|
// Cache credentials - store both walletId (for API) and walletAddress (for display)
|
|
172
199
|
writePrivyCredentials({
|
|
@@ -200,7 +227,12 @@ class PrivyAuthWalletManager {
|
|
|
200
227
|
* Verify cached credentials are still valid
|
|
201
228
|
*/
|
|
202
229
|
async verifyCredentials(cached) {
|
|
203
|
-
|
|
230
|
+
const relayEnabled = process.env.SAGE_DISABLE_PRIVY_RELAY !== '1';
|
|
231
|
+
if (!this.appId) return false;
|
|
232
|
+
if (!this.appSecret && relayEnabled) {
|
|
233
|
+
return !isTokenExpired(cached.accessToken);
|
|
234
|
+
}
|
|
235
|
+
if (!this.appSecret) return false;
|
|
204
236
|
|
|
205
237
|
// Check if access token is expired (Privy tokens expire after ~1 hour)
|
|
206
238
|
if (isTokenExpired(cached.accessToken)) {
|
|
@@ -212,7 +244,6 @@ class PrivyAuthWalletManager {
|
|
|
212
244
|
|
|
213
245
|
// If relay is enabled (default), the CLI only needs a valid user access token.
|
|
214
246
|
// Session keys are only required when the user disables the relay and signs directly via Privy.
|
|
215
|
-
const relayEnabled = process.env.SAGE_DISABLE_PRIVY_RELAY !== '1';
|
|
216
247
|
if (!relayEnabled) {
|
|
217
248
|
if (!getAuthorizationPrivateKey() && !cached.sessionPrivateKey) {
|
|
218
249
|
if (process.env.SAGE_VERBOSE === '1') {
|
|
@@ -236,6 +267,9 @@ class PrivyAuthWalletManager {
|
|
|
236
267
|
* Get API headers for Privy requests
|
|
237
268
|
*/
|
|
238
269
|
getApiHeaders() {
|
|
270
|
+
if (!this.appSecret) {
|
|
271
|
+
throw new Error('PRIVY_APP_SECRET required for direct Privy API calls. Use relay login (default) or set PRIVY_APP_SECRET.');
|
|
272
|
+
}
|
|
239
273
|
const { getPrivyHeaders } = require('./utils/wallet-shared');
|
|
240
274
|
return getPrivyHeaders(this.appId, this.appSecret);
|
|
241
275
|
}
|
|
@@ -58,7 +58,7 @@ const CHAIN_CONFIGS = {
|
|
|
58
58
|
* Used for resolution precedence.
|
|
59
59
|
*/
|
|
60
60
|
const ENV_VARS = {
|
|
61
|
-
rpcUrl: ['RPC_URL', 'BASE_SEPOLIA_RPC', 'BASE_RPC_URL'],
|
|
61
|
+
rpcUrl: ['SAGE_RPC_URL', 'RPC_URL', 'BASE_SEPOLIA_RPC', 'BASE_RPC_URL'],
|
|
62
62
|
chainId: ['CHAIN_ID', 'BASE_CHAIN_ID'],
|
|
63
63
|
subgraphUrl: ['SUBGRAPH_URL', 'SAGE_SUBGRAPH_URL', 'NEXT_PUBLIC_GRAPH_ENDPOINT', 'NEXT_PUBLIC_SUBGRAPH_URL']
|
|
64
64
|
};
|
|
@@ -106,8 +106,13 @@ class ConfigManager {
|
|
|
106
106
|
|
|
107
107
|
try {
|
|
108
108
|
const dotenv = require('dotenv');
|
|
109
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
110
|
+
const globalEnv = home ? path.join(home, '.sage', '.env') : null;
|
|
109
111
|
// Load from project .env
|
|
110
112
|
const projectEnv = path.join(this.projectDir, '.env');
|
|
113
|
+
if (globalEnv && fs.existsSync(globalEnv)) {
|
|
114
|
+
dotenv.config({ path: globalEnv });
|
|
115
|
+
}
|
|
111
116
|
if (fs.existsSync(projectEnv)) {
|
|
112
117
|
dotenv.config({ path: projectEnv });
|
|
113
118
|
}
|
|
@@ -116,6 +121,9 @@ class ConfigManager {
|
|
|
116
121
|
if (fs.existsSync(sageEnv)) {
|
|
117
122
|
dotenv.config({ path: sageEnv });
|
|
118
123
|
}
|
|
124
|
+
if (process.env.SAGE_RPC_URL) {
|
|
125
|
+
process.env.RPC_URL = process.env.SAGE_RPC_URL;
|
|
126
|
+
}
|
|
119
127
|
} catch (_) {
|
|
120
128
|
// dotenv not available, skip
|
|
121
129
|
}
|
|
@@ -105,6 +105,7 @@ const addressMap = z.record(
|
|
|
105
105
|
*/
|
|
106
106
|
const ipfsConfig = z.object({
|
|
107
107
|
gateway: z.string().url().optional(),
|
|
108
|
+
gateways: z.array(z.string()).optional(),
|
|
108
109
|
pinataApiKey: z.string().optional(),
|
|
109
110
|
pinataSecretKey: z.string().optional(),
|
|
110
111
|
pinataJwt: z.string().optional(),
|
|
@@ -2,6 +2,12 @@ const path = require('path');
|
|
|
2
2
|
const ui = require('../../utils/cli-ui');
|
|
3
3
|
|
|
4
4
|
const DEFAULT_GATEWAY = 'https://ipfs.dev.sageprotocol.io/ipfs';
|
|
5
|
+
const FALLBACK_GATEWAYS = [
|
|
6
|
+
'https://dweb.link/ipfs',
|
|
7
|
+
'https://nftstorage.link/ipfs',
|
|
8
|
+
'https://ipfs.io/ipfs',
|
|
9
|
+
'https://gateway.pinata.cloud/ipfs'
|
|
10
|
+
];
|
|
5
11
|
const DEFAULT_WORKER_BASE = 'https://api.sageprotocol.io';
|
|
6
12
|
const DEFAULT_WORKER_CHALLENGE_PATH = '/ipfs/auth/challenge';
|
|
7
13
|
const DEFAULT_WORKER_UPLOAD_PATH = '/ipfs/upload';
|
|
@@ -87,6 +93,7 @@ function createIpfsOnboarding({
|
|
|
87
93
|
|
|
88
94
|
if (!quiet) {
|
|
89
95
|
ui.header('Sage IPFS pinning setup', 'Configure your gateway and pinning providers so uploads work out of the box.');
|
|
96
|
+
ui.output(`Gateway fallbacks: ${FALLBACK_GATEWAYS.join(', ')}`);
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
let scope = initialScope && ['project', 'global'].includes(initialScope) ? initialScope : null;
|
|
@@ -332,6 +339,10 @@ function createIpfsOnboarding({
|
|
|
332
339
|
const payload = {};
|
|
333
340
|
if (gateway !== undefined) {
|
|
334
341
|
payload.gateway = ensureString(gateway) || null;
|
|
342
|
+
if (payload.gateway) {
|
|
343
|
+
const fallback = FALLBACK_GATEWAYS.filter((g) => g !== payload.gateway);
|
|
344
|
+
payload.gateways = fallback;
|
|
345
|
+
}
|
|
335
346
|
}
|
|
336
347
|
payload.warmGateway = !!warmGateway;
|
|
337
348
|
if (provider) payload.provider = provider;
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
const { loadSdk } = require('../../utils/sdk-resolver');
|
|
2
2
|
|
|
3
|
-
// Prefer
|
|
4
|
-
|
|
5
|
-
const DEFAULT_WALLET_TYPE = 'cast';
|
|
3
|
+
// Prefer Privy as the default wallet for npm CLI usage (no local keystore required).
|
|
4
|
+
const DEFAULT_WALLET_TYPE = 'privy';
|
|
6
5
|
|
|
7
6
|
function localResolveWalletType(configInstance, env = process.env) {
|
|
8
|
-
const envType = (env.WALLET_TYPE || '').trim();
|
|
9
|
-
if (envType.length) return envType;
|
|
7
|
+
const envType = (env.SAGE_WALLET_TYPE || env.WALLET_TYPE || '').trim();
|
|
10
8
|
|
|
11
9
|
try {
|
|
12
10
|
const cfg = configInstance?.getWalletConfig ? configInstance.getWalletConfig() : null;
|
|
13
|
-
const inferred = cfg?.type;
|
|
14
|
-
if (
|
|
15
|
-
|
|
11
|
+
const inferred = cfg?.type ? String(cfg.type).trim() : '';
|
|
12
|
+
if (envType.length) {
|
|
13
|
+
if (envType === DEFAULT_WALLET_TYPE && inferred && inferred !== DEFAULT_WALLET_TYPE) {
|
|
14
|
+
return inferred;
|
|
15
|
+
}
|
|
16
|
+
return envType;
|
|
16
17
|
}
|
|
18
|
+
if (inferred) return inferred;
|
|
17
19
|
} catch (_) {
|
|
18
20
|
// ignore config resolution errors
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
if (envType.length) return envType;
|
|
21
24
|
return DEFAULT_WALLET_TYPE;
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -59,6 +62,10 @@ function getSdkSession() {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
function resolveWalletType(configInstance) {
|
|
65
|
+
const envType = (process.env.SAGE_WALLET_TYPE || process.env.WALLET_TYPE || '').trim();
|
|
66
|
+
if (envType.length) {
|
|
67
|
+
return envType;
|
|
68
|
+
}
|
|
62
69
|
const session = getSdkSession();
|
|
63
70
|
if (session?.resolveWalletType) {
|
|
64
71
|
return session.resolveWalletType({ config: configInstance, env: process.env });
|
|
@@ -1127,7 +1127,7 @@ class SubDAOManager {
|
|
|
1127
1127
|
const signer = walletManager.getSigner();
|
|
1128
1128
|
const signerAddress = await signer.getAddress();
|
|
1129
1129
|
const walletLabel = (() => {
|
|
1130
|
-
const type = walletManager.getWalletType?.() || process.env.WALLET_TYPE || 'cast';
|
|
1130
|
+
const type = walletManager.getWalletType?.() || process.env.SAGE_WALLET_TYPE || process.env.WALLET_TYPE || 'cast';
|
|
1131
1131
|
if (type === 'privy') return 'Privy deterministic wallet';
|
|
1132
1132
|
if (type === 'web3auth') return 'Web3Auth wallet';
|
|
1133
1133
|
if (type === 'cdp') return 'CDP embedded wallet';
|
package/dist/cli/utils/cli-ui.js
CHANGED
|
@@ -402,7 +402,7 @@ function keyValue(data) {
|
|
|
402
402
|
* @param {boolean} [options.pretty=true] - Pretty print
|
|
403
403
|
*/
|
|
404
404
|
function json(data, options = {}) {
|
|
405
|
-
const { pretty = true } = options;
|
|
405
|
+
const { pretty = true } = options || {};
|
|
406
406
|
console.log(pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data));
|
|
407
407
|
}
|
|
408
408
|
|
|
@@ -14,11 +14,15 @@ async function getWallet(provider, options = {}) {
|
|
|
14
14
|
throw new Error('Signer unavailable. Connect a wallet capable of signing transactions.');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
let signer = session.signer;
|
|
18
|
+
|
|
19
|
+
// ethers v6 Signer#connect returns a NEW signer; it does not mutate.
|
|
20
|
+
// Ensure the returned signer is connected so read calls (eth_call) work.
|
|
21
|
+
if (!signer.provider && provider && typeof signer.connect === 'function') {
|
|
22
|
+
signer = signer.connect(provider);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
return
|
|
25
|
+
return signer;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
module.exports = {
|
|
@@ -102,7 +102,8 @@ function getUserIdFromToken(token) {
|
|
|
102
102
|
function getPrivyClient() {
|
|
103
103
|
if (_privyClient) return _privyClient;
|
|
104
104
|
|
|
105
|
-
const
|
|
105
|
+
const defaults = require('../defaults');
|
|
106
|
+
const appId = process.env.PRIVY_APP_ID || process.env.NEXT_PUBLIC_PRIVY_APP_ID || defaults.PRIVY_APP_ID;
|
|
106
107
|
const appSecret = process.env.PRIVY_APP_SECRET;
|
|
107
108
|
|
|
108
109
|
if (!appId || !appSecret) {
|
|
@@ -33,8 +33,9 @@ class WalletManager {
|
|
|
33
33
|
|
|
34
34
|
async connect() {
|
|
35
35
|
try {
|
|
36
|
-
//
|
|
37
|
-
|
|
36
|
+
// Explicit env override should win for CLI usage.
|
|
37
|
+
const envWalletType = (process.env.SAGE_WALLET_TYPE || process.env.WALLET_TYPE || '').trim();
|
|
38
|
+
let walletType = envWalletType || resolveWalletType(this.config) || this.walletType || 'cast';
|
|
38
39
|
// NEVER auto-switch to private key based on env alone; require explicit insecure opt-in
|
|
39
40
|
const insecurePk = shouldAllowInsecurePrivateKey();
|
|
40
41
|
if (!insecurePk && (process.env.SAGE_PRIVATE_KEY || process.env.PRIVATE_KEY)) {
|
|
@@ -132,19 +133,14 @@ class WalletManager {
|
|
|
132
133
|
process.env.SAGE_PRIVY_RELAY_URL = defaults.SAGE_PRIVY_RELAY_URL;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
//
|
|
136
|
-
// Use PrivyAuthWalletManager when appId is available
|
|
136
|
+
// Prefer relay-based web login by default (no localhost callback).
|
|
137
|
+
// Use PrivyAuthWalletManager when appId is available.
|
|
137
138
|
if (appId) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
} else {
|
|
145
|
-
if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') {
|
|
146
|
-
console.log('🔐 Using Privy OAuth authentication (same wallet as web app)');
|
|
147
|
-
}
|
|
139
|
+
if (!process.env.SAGE_PRIVY_LOGIN_MODE) {
|
|
140
|
+
process.env.SAGE_PRIVY_LOGIN_MODE = 'web';
|
|
141
|
+
}
|
|
142
|
+
if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') {
|
|
143
|
+
console.log('🔐 Using web relay authentication (same wallet as web app)');
|
|
148
144
|
}
|
|
149
145
|
this.wallet = new PrivyAuthWalletManager();
|
|
150
146
|
const ok = await this.wallet.initialize();
|
|
@@ -495,7 +491,7 @@ class WalletManager {
|
|
|
495
491
|
}
|
|
496
492
|
|
|
497
493
|
// Get current wallet type from environment or instance
|
|
498
|
-
const currentWalletType =
|
|
494
|
+
const currentWalletType = this.getWalletType();
|
|
499
495
|
|
|
500
496
|
if (['cast', 'cdp', 'web3auth', 'privy', 'metamask', 'walletconnect'].includes(currentWalletType) && this.wallet && typeof this.wallet.signTransaction === 'function') {
|
|
501
497
|
return await this.wallet.signTransaction(transaction);
|
|
@@ -530,7 +526,7 @@ class WalletManager {
|
|
|
530
526
|
}
|
|
531
527
|
|
|
532
528
|
// Get current wallet type from environment or instance
|
|
533
|
-
const currentWalletType =
|
|
529
|
+
const currentWalletType = this.getWalletType();
|
|
534
530
|
|
|
535
531
|
if (['cast', 'cdp', 'web3auth', 'privy', 'metamask', 'walletconnect'].includes(currentWalletType) && this.wallet && typeof this.wallet.signMessage === 'function') {
|
|
536
532
|
return await this.wallet.signMessage(message);
|
|
@@ -617,7 +613,7 @@ class WalletManager {
|
|
|
617
613
|
|
|
618
614
|
async disconnect() {
|
|
619
615
|
// Get current wallet type from environment or instance
|
|
620
|
-
const currentWalletType =
|
|
616
|
+
const currentWalletType = this.getWalletType();
|
|
621
617
|
|
|
622
618
|
if ((['cast', 'cdp', 'auth0', 'metamask', 'walletconnect', 'web3auth', 'privy'].includes(currentWalletType)) && this.wallet) {
|
|
623
619
|
await this.wallet.disconnect();
|
|
@@ -649,7 +645,8 @@ class WalletManager {
|
|
|
649
645
|
}
|
|
650
646
|
|
|
651
647
|
getWalletType() {
|
|
652
|
-
|
|
648
|
+
const envWalletType = (process.env.SAGE_WALLET_TYPE || process.env.WALLET_TYPE || '').trim();
|
|
649
|
+
return envWalletType || resolveWalletType(this.config) || this.walletType;
|
|
653
650
|
}
|
|
654
651
|
}
|
|
655
652
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: E2E Test Prompt
|
|
3
|
+
description: A test prompt for validating the /prompt/:cid endpoint
|
|
4
|
+
tags: [test, e2e, validation]
|
|
5
|
+
kind: "prompt"
|
|
6
|
+
publishable: true
|
|
7
|
+
targets: []
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# E2E Test Prompt
|
|
11
|
+
|
|
12
|
+
This is a test prompt created to validate the prompt allowlist flow.
|
|
13
|
+
|
|
14
|
+
## Purpose
|
|
15
|
+
- Test that prompts published to a DAO get added to the allowlist
|
|
16
|
+
- Verify that `/prompt/:cid` endpoint serves allowlisted prompts
|
|
17
|
+
- Confirm library sync properly indexes prompt CIDs
|
|
18
|
+
|
|
19
|
+
## Instructions
|
|
20
|
+
When this prompt is accessible via the API, the allowlist flow is working correctly.
|
|
21
|
+
|
|
22
|
+
Created: 2025-12-26
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "build-web3",
|
|
3
|
+
"description": "Build Web3 dApps and smart contracts from scratch through shipping. Full lifecycle for EVM/Base development with Solidity, Foundry, Next.js, and viem/wagmi. Covers contracts, frontend, testing, security analysis, and deployment.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "Sage Protocol",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": ["web3", "solidity", "foundry", "next.js", "viem", "wagmi", "smart-contracts", "dapp"],
|
|
8
|
+
"category": "development",
|
|
9
|
+
"homepage": "https://github.com/sage-protocol",
|
|
10
|
+
"repository": "https://github.com/sage-protocol/sage-protocol"
|
|
11
|
+
}
|