@namera-ai/cli 0.1.3 → 0.1.4
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/dist/commands/index.mjs +21 -0
- package/dist/commands/index.mjs.map +1 -0
- package/dist/commands/keystore/create.mjs +74 -0
- package/dist/commands/keystore/create.mjs.map +1 -0
- package/dist/commands/keystore/decrypt.mjs +71 -0
- package/dist/commands/keystore/decrypt.mjs.map +1 -0
- package/dist/commands/keystore/import.mjs +84 -0
- package/dist/commands/keystore/import.mjs.map +1 -0
- package/dist/commands/keystore/index.mjs +28 -0
- package/dist/commands/keystore/index.mjs.map +1 -0
- package/dist/commands/keystore/info.mjs +62 -0
- package/dist/commands/keystore/info.mjs.map +1 -0
- package/dist/commands/keystore/list.mjs +46 -0
- package/dist/commands/keystore/list.mjs.map +1 -0
- package/dist/commands/keystore/remove.mjs +51 -0
- package/dist/commands/keystore/remove.mjs.map +1 -0
- package/dist/commands/mcp/index.mjs +13 -0
- package/dist/commands/mcp/index.mjs.map +1 -0
- package/dist/commands/mcp/start.mjs +82 -0
- package/dist/commands/mcp/start.mjs.map +1 -0
- package/dist/commands/schema/index.mjs +53 -0
- package/dist/commands/schema/index.mjs.map +1 -0
- package/dist/commands/session-key/create/index.mjs +87 -0
- package/dist/commands/session-key/create/index.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/base.mjs +52 -0
- package/dist/commands/session-key/create/prompts/base.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/call.mjs +85 -0
- package/dist/commands/session-key/create/prompts/call.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/common.mjs +27 -0
- package/dist/commands/session-key/create/prompts/common.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/gas.mjs +19 -0
- package/dist/commands/session-key/create/prompts/gas.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/index.mjs +44 -0
- package/dist/commands/session-key/create/prompts/index.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/rate-limit.mjs +40 -0
- package/dist/commands/session-key/create/prompts/rate-limit.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/signature-caller.mjs +26 -0
- package/dist/commands/session-key/create/prompts/signature-caller.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/timestamp.mjs +32 -0
- package/dist/commands/session-key/create/prompts/timestamp.mjs.map +1 -0
- package/dist/commands/session-key/index.mjs +26 -0
- package/dist/commands/session-key/index.mjs.map +1 -0
- package/dist/commands/session-key/info.mjs +64 -0
- package/dist/commands/session-key/info.mjs.map +1 -0
- package/dist/commands/session-key/list.mjs +54 -0
- package/dist/commands/session-key/list.mjs.map +1 -0
- package/dist/commands/session-key/remove.mjs +51 -0
- package/dist/commands/session-key/remove.mjs.map +1 -0
- package/dist/commands/session-key/status.mjs +64 -0
- package/dist/commands/session-key/status.mjs.map +1 -0
- package/dist/commands/smart-account/create.mjs +94 -0
- package/dist/commands/smart-account/create.mjs.map +1 -0
- package/dist/commands/smart-account/import.mjs +45 -0
- package/dist/commands/smart-account/import.mjs.map +1 -0
- package/dist/commands/smart-account/index.mjs +30 -0
- package/dist/commands/smart-account/index.mjs.map +1 -0
- package/dist/commands/smart-account/info.mjs +66 -0
- package/dist/commands/smart-account/info.mjs.map +1 -0
- package/dist/commands/smart-account/list.mjs +48 -0
- package/dist/commands/smart-account/list.mjs.map +1 -0
- package/dist/commands/smart-account/remove.mjs +54 -0
- package/dist/commands/smart-account/remove.mjs.map +1 -0
- package/dist/commands/smart-account/status.mjs +63 -0
- package/dist/commands/smart-account/status.mjs.map +1 -0
- package/dist/dto/index.mjs +6 -0
- package/dist/dto/keystore.mjs +44 -0
- package/dist/dto/keystore.mjs.map +1 -0
- package/dist/dto/mcp.mjs +13 -0
- package/dist/dto/mcp.mjs.map +1 -0
- package/dist/dto/session-key.mjs +54 -0
- package/dist/dto/session-key.mjs.map +1 -0
- package/dist/dto/smart-account.mjs +45 -0
- package/dist/dto/smart-account.mjs.map +1 -0
- package/dist/flags/global.mjs +29 -0
- package/dist/flags/global.mjs.map +1 -0
- package/dist/flags/shared.mjs +11 -0
- package/dist/flags/shared.mjs.map +1 -0
- package/dist/helpers/humanize.mjs +153 -0
- package/dist/helpers/humanize.mjs.map +1 -0
- package/dist/helpers/paths.mjs +31 -0
- package/dist/helpers/paths.mjs.map +1 -0
- package/dist/helpers/policy.mjs +89 -0
- package/dist/helpers/policy.mjs.map +1 -0
- package/dist/helpers/pretty.mjs +71 -0
- package/dist/helpers/pretty.mjs.map +1 -0
- package/dist/index.mjs +15 -3264
- package/dist/index.mjs.map +1 -1
- package/dist/layers/config.mjs +124 -0
- package/dist/layers/config.mjs.map +1 -0
- package/dist/layers/index.mjs +11 -0
- package/dist/layers/keystore.mjs +225 -0
- package/dist/layers/keystore.mjs.map +1 -0
- package/dist/layers/mcp-context.mjs +8 -0
- package/dist/layers/mcp-context.mjs.map +1 -0
- package/dist/layers/mcp.mjs +31 -0
- package/dist/layers/mcp.mjs.map +1 -0
- package/dist/layers/output.mjs +21 -0
- package/dist/layers/output.mjs.map +1 -0
- package/dist/layers/prompt.mjs +78 -0
- package/dist/layers/prompt.mjs.map +1 -0
- package/dist/layers/session-key.mjs +218 -0
- package/dist/layers/session-key.mjs.map +1 -0
- package/dist/layers/smart-account.mjs +175 -0
- package/dist/layers/smart-account.mjs.map +1 -0
- package/dist/layers/web3.mjs +83 -0
- package/dist/layers/web3.mjs.map +1 -0
- package/dist/mcp/helpers/common.mjs +9 -0
- package/dist/mcp/helpers/common.mjs.map +1 -0
- package/dist/mcp/helpers/index.mjs +4 -0
- package/dist/mcp/helpers/session-key.mjs +129 -0
- package/dist/mcp/helpers/session-key.mjs.map +1 -0
- package/dist/mcp/index.mjs +43 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/mcp/mcp.mjs +15 -0
- package/dist/mcp/mcp.mjs.map +1 -0
- package/dist/mcp/tools/account/address.mjs +20 -0
- package/dist/mcp/tools/account/address.mjs.map +1 -0
- package/dist/mcp/tools/account/get-balance.mjs +54 -0
- package/dist/mcp/tools/account/get-balance.mjs.map +1 -0
- package/dist/mcp/tools/account/index.mjs +15 -0
- package/dist/mcp/tools/account/index.mjs.map +1 -0
- package/dist/mcp/tools/read/index.mjs +11 -0
- package/dist/mcp/tools/read/index.mjs.map +1 -0
- package/dist/mcp/tools/read/read-contract.mjs +49 -0
- package/dist/mcp/tools/read/read-contract.mjs.map +1 -0
- package/dist/mcp/tools/transaction/execute-transaction.mjs +38 -0
- package/dist/mcp/tools/transaction/execute-transaction.mjs.map +1 -0
- package/dist/mcp/tools/transaction/index.mjs +15 -0
- package/dist/mcp/tools/transaction/index.mjs.map +1 -0
- package/dist/mcp/tools/transaction/native-transfer.mjs +59 -0
- package/dist/mcp/tools/transaction/native-transfer.mjs.map +1 -0
- package/dist/schema/chain.mjs +124 -0
- package/dist/schema/chain.mjs.map +1 -0
- package/dist/schema/common.mjs +29 -0
- package/dist/schema/common.mjs.map +1 -0
- package/dist/schema/index.mjs +5 -0
- package/dist/schema/policy.mjs +114 -0
- package/dist/schema/policy.mjs.map +1 -0
- package/dist/schema/tx.mjs +19 -0
- package/dist/schema/tx.mjs.map +1 -0
- package/dist/types/index.mjs +11 -0
- package/dist/types/index.mjs.map +1 -0
- package/package.json +10 -11
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { chainIdToChainName } from "../schema/chain.mjs";
|
|
3
|
+
import "../schema/index.mjs";
|
|
4
|
+
import { SessionKey } from "../dto/session-key.mjs";
|
|
5
|
+
import "../dto/index.mjs";
|
|
6
|
+
import { ConfigManager } from "./config.mjs";
|
|
7
|
+
import { PromptManager } from "./prompt.mjs";
|
|
8
|
+
import { KeystoreManager } from "./keystore.mjs";
|
|
9
|
+
import { Web3Service } from "./web3.mjs";
|
|
10
|
+
import { policyParamsToPolicies } from "../helpers/policy.mjs";
|
|
11
|
+
import { SmartAccountManager } from "./smart-account.mjs";
|
|
12
|
+
import { Context, Data, Effect, Layer, Schema } from "effect";
|
|
13
|
+
import { Wallet } from "@ethereumjs/wallet";
|
|
14
|
+
import { hexToBytes, toHex } from "viem";
|
|
15
|
+
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
16
|
+
import { createSessionKey, isSessionKeyInstalled } from "@namera-ai/sdk/session-key";
|
|
17
|
+
//#region src/layers/session-key.ts
|
|
18
|
+
const SessionKeyManager = Context.Service("@namera-ai/cli/SessionKeyManager");
|
|
19
|
+
var SessionKeyManagerError = class extends Data.TaggedError("@namera-ai/cli/SessionKeyManagerError") {};
|
|
20
|
+
const layer = Layer.effect(SessionKeyManager, Effect.gen(function* () {
|
|
21
|
+
const configManager = yield* ConfigManager;
|
|
22
|
+
const promptManager = yield* PromptManager;
|
|
23
|
+
const keystoreManager = yield* KeystoreManager;
|
|
24
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
25
|
+
const web3Service = yield* Web3Service;
|
|
26
|
+
const createSessionKey$1 = (params) => Effect.gen(function* () {
|
|
27
|
+
if (yield* configManager.checkEntityExists({
|
|
28
|
+
alias: params.alias,
|
|
29
|
+
type: "session-key"
|
|
30
|
+
})) return yield* Effect.fail(new SessionKeyManagerError({
|
|
31
|
+
code: "SessionKeyAlreadyExists",
|
|
32
|
+
message: `Session key for alias ${params.alias} already exists`
|
|
33
|
+
}));
|
|
34
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
35
|
+
alias: params.alias,
|
|
36
|
+
type: "session-key"
|
|
37
|
+
});
|
|
38
|
+
const sa = yield* smartAccountManager.getSmartAccount({ alias: params.smartAccountAlias });
|
|
39
|
+
const ownerSigner = yield* keystoreManager.getSigner({
|
|
40
|
+
alias: sa.data.ownerAlias,
|
|
41
|
+
password: params.ownerKeystorePassword
|
|
42
|
+
});
|
|
43
|
+
const policies = policyParamsToPolicies(params.policyParams);
|
|
44
|
+
const clients = yield* Effect.all(params.chains.map((chain) => web3Service.getPublicClient({ chain })), { concurrency: "unbounded" });
|
|
45
|
+
const sessionPrivateKey = generatePrivateKey();
|
|
46
|
+
const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey);
|
|
47
|
+
const res = yield* Effect.promise(() => createSessionKey({
|
|
48
|
+
type: "ecdsa",
|
|
49
|
+
accountType: "ecdsa",
|
|
50
|
+
sessionPrivateKey,
|
|
51
|
+
clients,
|
|
52
|
+
entrypointVersion: sa.data.entryPointVersion,
|
|
53
|
+
kernelVersion: sa.data.kernelVersion,
|
|
54
|
+
index: sa.data.index,
|
|
55
|
+
signer: ownerSigner,
|
|
56
|
+
policies
|
|
57
|
+
}));
|
|
58
|
+
const encData = yield* Effect.tryPromise({
|
|
59
|
+
try: () => Wallet.fromPrivateKey(hexToBytes(sessionPrivateKey)).toV3(params.sessionKeyPassword),
|
|
60
|
+
catch: () => new SessionKeyManagerError({
|
|
61
|
+
code: "EncryptionError",
|
|
62
|
+
message: "Failed to encrypt session key"
|
|
63
|
+
})
|
|
64
|
+
});
|
|
65
|
+
const data = {
|
|
66
|
+
sessionKeyType: "ecdsa",
|
|
67
|
+
serializedAccounts: res.serializedAccounts.map((a) => ({
|
|
68
|
+
chain: chainIdToChainName(a.chainId),
|
|
69
|
+
serializedAccount: a.serializedAccount
|
|
70
|
+
})),
|
|
71
|
+
sessionKeyAddress: sessionKeyAccount.address,
|
|
72
|
+
smartAccountAlias: sa.alias,
|
|
73
|
+
...encData
|
|
74
|
+
};
|
|
75
|
+
yield* configManager.storeEntity({
|
|
76
|
+
alias: params.alias,
|
|
77
|
+
content: JSON.stringify(data),
|
|
78
|
+
path: entityPath,
|
|
79
|
+
type: "session-key"
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
alias: params.alias,
|
|
83
|
+
data,
|
|
84
|
+
path: entityPath
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
const getSessionKey = (params) => Effect.gen(function* () {
|
|
88
|
+
const res = yield* configManager.getEntity({
|
|
89
|
+
alias: params.alias,
|
|
90
|
+
type: "session-key"
|
|
91
|
+
});
|
|
92
|
+
const parsed = Schema.decodeUnknownOption(Schema.fromJsonString(SessionKey))(res.content);
|
|
93
|
+
if (parsed._tag === "None") return yield* Effect.fail(new SessionKeyManagerError({
|
|
94
|
+
code: "SessionKeyParseError",
|
|
95
|
+
message: "Unable to parse session key"
|
|
96
|
+
}));
|
|
97
|
+
return {
|
|
98
|
+
alias: res.alias,
|
|
99
|
+
data: parsed.value,
|
|
100
|
+
path: res.path
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
const listSessionKeys = (params) => Effect.gen(function* () {
|
|
104
|
+
const effects = (yield* configManager.getEntitiesForType("session-key")).map((entity) => Effect.gen(function* () {
|
|
105
|
+
const parsed = Schema.decodeUnknownOption(Schema.fromJsonString(SessionKey))(entity.content);
|
|
106
|
+
if (parsed._tag === "None") return yield* Effect.fail(new SessionKeyManagerError({
|
|
107
|
+
code: "SessionKeyParseError",
|
|
108
|
+
message: "Unable to parse session key"
|
|
109
|
+
}));
|
|
110
|
+
return {
|
|
111
|
+
alias: entity.alias,
|
|
112
|
+
data: parsed.value,
|
|
113
|
+
path: entity.path
|
|
114
|
+
};
|
|
115
|
+
}));
|
|
116
|
+
const allKeys = yield* Effect.all(effects, { concurrency: "unbounded" });
|
|
117
|
+
if (params.smartAccount) return allKeys.filter((k) => k.data.smartAccountAlias === params.smartAccount);
|
|
118
|
+
return allKeys;
|
|
119
|
+
});
|
|
120
|
+
const selectSessionKey = (params) => Effect.gen(function* () {
|
|
121
|
+
const sessionKeys = yield* listSessionKeys({});
|
|
122
|
+
return yield* promptManager.selectPrompt({
|
|
123
|
+
message: params.message,
|
|
124
|
+
choices: sessionKeys.map((sk) => ({
|
|
125
|
+
title: sk.alias,
|
|
126
|
+
value: sk,
|
|
127
|
+
description: `${sk.data.sessionKeyType === "ecdsa" ? sk.data.sessionKeyAddress : sk.data.passKeyName} (${sk.data.smartAccountAlias})`
|
|
128
|
+
}))
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
const getSessionKeyStatus = (params) => Effect.gen(function* () {
|
|
132
|
+
const sessionKey = yield* getSessionKey({ alias: params.alias });
|
|
133
|
+
const sa = yield* smartAccountManager.getSmartAccount({ alias: sessionKey.data.smartAccountAlias });
|
|
134
|
+
const publicClient = yield* web3Service.getPublicClient({
|
|
135
|
+
chain: params.chain,
|
|
136
|
+
rpcUrl: params.rpcUrl
|
|
137
|
+
});
|
|
138
|
+
if (sessionKey.data.sessionKeyType !== "ecdsa") return yield* Effect.fail(new SessionKeyManagerError({
|
|
139
|
+
code: "SessionKeyParseError",
|
|
140
|
+
message: "Only ECDSA session keys have an on-chain status"
|
|
141
|
+
}));
|
|
142
|
+
const sessionKeyAddress = sessionKey.data.sessionKeyAddress;
|
|
143
|
+
return yield* Effect.tryPromise({
|
|
144
|
+
try: () => isSessionKeyInstalled(publicClient, {
|
|
145
|
+
accountAddress: sa.data.smartAccountAddress,
|
|
146
|
+
sessionKeyAddress
|
|
147
|
+
}),
|
|
148
|
+
catch: () => false
|
|
149
|
+
}).pipe(Effect.catch(() => Effect.succeed(false)));
|
|
150
|
+
});
|
|
151
|
+
const multiSelectSessionKeys = (params) => Effect.gen(function* () {
|
|
152
|
+
const allKeys = yield* listSessionKeys({ smartAccount: params.smartAccount });
|
|
153
|
+
return yield* promptManager.multiSelectPrompt({
|
|
154
|
+
message: params.message,
|
|
155
|
+
choices: allKeys.map((k) => ({
|
|
156
|
+
title: k.alias,
|
|
157
|
+
value: k,
|
|
158
|
+
description: `${k.data.sessionKeyType === "ecdsa" ? k.data.sessionKeyAddress : k.data.passKeyName} (${k.data.smartAccountAlias})`
|
|
159
|
+
})),
|
|
160
|
+
min: 1
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
const getSessionKeyPassword = (params) => Effect.gen(function* () {
|
|
164
|
+
const key = yield* getSessionKey({ alias: params.alias });
|
|
165
|
+
return yield* promptManager.passwordPrompt({
|
|
166
|
+
message: params.message,
|
|
167
|
+
validate: (v) => Effect.gen(function* () {
|
|
168
|
+
if (v.trim() === "") return yield* Effect.fail("Password cannot be empty");
|
|
169
|
+
yield* Effect.tryPromise({
|
|
170
|
+
try: () => Wallet.fromV3(key.data, v),
|
|
171
|
+
catch: () => new SessionKeyManagerError({
|
|
172
|
+
code: "DecryptError",
|
|
173
|
+
message: `Invalid password for session key ${params.alias}`
|
|
174
|
+
})
|
|
175
|
+
}).pipe(Effect.catch(() => Effect.fail("Invalid password")));
|
|
176
|
+
return v;
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
const getSessionKeySigner = (params) => Effect.gen(function* () {
|
|
181
|
+
const key = yield* getSessionKey({ alias: params.alias });
|
|
182
|
+
const signer = yield* Effect.tryPromise({
|
|
183
|
+
try: async () => {
|
|
184
|
+
return privateKeyToAccount(toHex((await Wallet.fromV3(key.data, params.password)).getPrivateKey()));
|
|
185
|
+
},
|
|
186
|
+
catch: () => new SessionKeyManagerError({
|
|
187
|
+
code: "DecryptError",
|
|
188
|
+
message: `Invalid password for session key ${params.alias}`
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
...key,
|
|
193
|
+
signer
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
const removeSessionKey = (params) => Effect.gen(function* () {
|
|
197
|
+
const key = yield* getSessionKey({ alias: params.alias });
|
|
198
|
+
return yield* configManager.removeEntity({
|
|
199
|
+
alias: key.alias,
|
|
200
|
+
type: "session-key"
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
return SessionKeyManager.of({
|
|
204
|
+
createSessionKey: createSessionKey$1,
|
|
205
|
+
getSessionKey,
|
|
206
|
+
listSessionKeys,
|
|
207
|
+
selectSessionKey,
|
|
208
|
+
getSessionKeyStatus,
|
|
209
|
+
multiSelectSessionKeys,
|
|
210
|
+
getSessionKeyPassword,
|
|
211
|
+
getSessionKeySigner,
|
|
212
|
+
removeSessionKey
|
|
213
|
+
});
|
|
214
|
+
}));
|
|
215
|
+
//#endregion
|
|
216
|
+
export { SessionKeyManager, layer };
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=session-key.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-key.mjs","names":["createSessionKey","createNameraSessionKey"],"sources":["../../src/layers/session-key.ts"],"sourcesContent":["import { Wallet } from \"@ethereumjs/wallet\";\nimport {\n createSessionKey as createNameraSessionKey,\n isSessionKeyInstalled,\n} from \"@namera-ai/sdk/session-key\";\nimport { Context, Data, Effect, Layer, type Redacted, Schema } from \"effect\";\nimport type { QuitError } from \"effect/Terminal\";\nimport type { Prompt } from \"effect/unstable/cli\";\nimport { hexToBytes, toHex } from \"viem\";\nimport {\n generatePrivateKey,\n type LocalAccount,\n privateKeyToAccount,\n} from \"viem/accounts\";\n\nimport {\n type CreateSessionKeyParams,\n type Keystore,\n SessionKey,\n type SessionKeyData,\n} from \"@/dto\";\nimport { policyParamsToPolicies } from \"@/helpers/policy\";\nimport { chainIdToChainName } from \"@/schema\";\n\nimport type {\n GetSessionKeyParams,\n GetSessionKeyStatusParams,\n ListSessionKeysParams,\n RemoveSessionKeyParams,\n} from \"../dto/session-key\";\nimport { ConfigManager, type ConfigManagerError } from \"./config\";\nimport { KeystoreManager, type KeystoreManagerError } from \"./keystore\";\nimport { PromptManager } from \"./prompt\";\nimport { SmartAccountManager } from \"./smart-account\";\nimport { Web3Service } from \"./web3\";\n\nexport type SessionKeyManager = {\n createSessionKey: (\n params: CreateSessionKeyParams,\n ) => Effect.Effect<\n SessionKeyData,\n SessionKeyManagerError | ConfigManagerError | KeystoreManagerError\n >;\n getSessionKey: (\n params: GetSessionKeyParams,\n ) => Effect.Effect<\n SessionKeyData,\n SessionKeyManagerError | ConfigManagerError\n >;\n listSessionKeys: (\n params: ListSessionKeysParams,\n ) => Effect.Effect<\n SessionKeyData[],\n ConfigManagerError | SessionKeyManagerError\n >;\n selectSessionKey: (params: {\n message: string;\n }) => Effect.Effect<\n SessionKeyData,\n ConfigManagerError | SessionKeyManagerError | QuitError,\n Prompt.Environment\n >;\n getSessionKeyStatus: (\n params: GetSessionKeyStatusParams,\n ) => Effect.Effect<\n boolean,\n ConfigManagerError | SessionKeyManagerError,\n never\n >;\n multiSelectSessionKeys: (params: {\n message: string;\n smartAccount?: string;\n }) => Effect.Effect<\n SessionKeyData[],\n ConfigManagerError | SessionKeyManagerError | QuitError,\n Prompt.Environment\n >;\n getSessionKeyPassword: (params: {\n alias: string;\n message: string;\n }) => Effect.Effect<\n Redacted.Redacted<string>,\n SessionKeyManagerError | ConfigManagerError | QuitError,\n Prompt.Environment\n >;\n getSessionKeySigner: (params: {\n alias: string;\n password: string;\n }) => Effect.Effect<\n SessionKeyData & { signer: LocalAccount },\n SessionKeyManagerError | ConfigManagerError | QuitError,\n never\n >;\n removeSessionKey: (params: {\n readonly alias: string;\n }) => Effect.Effect<void, SessionKeyManagerError | ConfigManagerError, never>;\n};\nexport const SessionKeyManager = Context.Service<SessionKeyManager>(\n \"@namera-ai/cli/SessionKeyManager\",\n);\n\nexport class SessionKeyManagerError extends Data.TaggedError(\n \"@namera-ai/cli/SessionKeyManagerError\",\n)<{\n code:\n | \"EncryptionError\"\n | \"SessionKeyAlreadyExists\"\n | \"SessionKeyParseError\"\n | \"DecryptError\";\n message: string;\n}> {}\n\nexport const layer = Layer.effect(\n SessionKeyManager,\n Effect.gen(function* () {\n const configManager = yield* ConfigManager;\n const promptManager = yield* PromptManager;\n const keystoreManager = yield* KeystoreManager;\n const smartAccountManager = yield* SmartAccountManager;\n const web3Service = yield* Web3Service;\n\n const createSessionKey = (params: CreateSessionKeyParams) =>\n Effect.gen(function* () {\n const exists = yield* configManager.checkEntityExists({\n alias: params.alias,\n type: \"session-key\",\n });\n\n if (exists) {\n return yield* Effect.fail(\n new SessionKeyManagerError({\n code: \"SessionKeyAlreadyExists\",\n message: `Session key for alias ${params.alias} already exists`,\n }),\n );\n }\n\n const entityPath = yield* configManager.getEntityPath({\n alias: params.alias,\n type: \"session-key\",\n });\n\n const sa = yield* smartAccountManager.getSmartAccount({\n alias: params.smartAccountAlias,\n });\n\n const ownerSigner = yield* keystoreManager.getSigner({\n alias: sa.data.ownerAlias,\n password: params.ownerKeystorePassword,\n });\n const policies = policyParamsToPolicies(params.policyParams);\n\n const clients = yield* Effect.all(\n params.chains.map((chain) => web3Service.getPublicClient({ chain })),\n { concurrency: \"unbounded\" },\n );\n\n const sessionPrivateKey = generatePrivateKey();\n const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey);\n\n const res = yield* Effect.promise(() =>\n createNameraSessionKey({\n type: \"ecdsa\",\n accountType: \"ecdsa\",\n sessionPrivateKey,\n clients,\n entrypointVersion: sa.data.entryPointVersion,\n kernelVersion: sa.data.kernelVersion,\n index: sa.data.index,\n signer: ownerSigner,\n policies,\n }),\n );\n\n // Encrypt Session Key\n const encData = (yield* Effect.tryPromise({\n try: () =>\n Wallet.fromPrivateKey(hexToBytes(sessionPrivateKey)).toV3(\n params.sessionKeyPassword,\n ),\n catch: () =>\n new SessionKeyManagerError({\n code: \"EncryptionError\",\n message: \"Failed to encrypt session key\",\n }),\n })) as Keystore;\n\n const data: SessionKey = {\n sessionKeyType: \"ecdsa\",\n serializedAccounts: res.serializedAccounts.map((a) => ({\n chain: chainIdToChainName(a.chainId),\n serializedAccount: a.serializedAccount,\n })),\n sessionKeyAddress: sessionKeyAccount.address,\n smartAccountAlias: sa.alias,\n ...encData,\n };\n\n yield* configManager.storeEntity({\n alias: params.alias,\n content: JSON.stringify(data),\n path: entityPath,\n type: \"session-key\",\n });\n\n return {\n alias: params.alias,\n data,\n path: entityPath,\n } satisfies SessionKeyData;\n });\n\n const getSessionKey = (params: GetSessionKeyParams) =>\n Effect.gen(function* () {\n const res = yield* configManager.getEntity({\n alias: params.alias,\n type: \"session-key\",\n });\n\n const parsed = Schema.decodeUnknownOption(\n Schema.fromJsonString(SessionKey),\n )(res.content);\n\n if (parsed._tag === \"None\") {\n return yield* Effect.fail(\n new SessionKeyManagerError({\n code: \"SessionKeyParseError\",\n message: \"Unable to parse session key\",\n }),\n );\n }\n\n const data: SessionKeyData = {\n alias: res.alias,\n data: parsed.value,\n path: res.path,\n };\n\n return data;\n });\n\n const listSessionKeys = (params: ListSessionKeysParams) =>\n Effect.gen(function* () {\n const res = yield* configManager.getEntitiesForType(\"session-key\");\n\n const effects = res.map((entity) =>\n Effect.gen(function* () {\n const parsed = Schema.decodeUnknownOption(\n Schema.fromJsonString(SessionKey),\n )(entity.content);\n\n if (parsed._tag === \"None\") {\n return yield* Effect.fail(\n new SessionKeyManagerError({\n code: \"SessionKeyParseError\",\n message: \"Unable to parse session key\",\n }),\n );\n }\n\n const data: SessionKeyData = {\n alias: entity.alias,\n data: parsed.value,\n path: entity.path,\n };\n\n return data;\n }),\n );\n\n const allKeys = yield* Effect.all(effects, {\n concurrency: \"unbounded\",\n });\n // .map((d) => (d._op === \"Success\" ? d.success : undefined))\n // .filter(Boolean) as SessionKeyData[];\n\n if (params.smartAccount) {\n return allKeys.filter(\n (k) => k.data.smartAccountAlias === params.smartAccount,\n );\n }\n\n return allKeys;\n });\n\n const selectSessionKey = (params: { message: string }) =>\n Effect.gen(function* () {\n const sessionKeys = yield* listSessionKeys({});\n\n const res = yield* promptManager.selectPrompt({\n message: params.message,\n choices: sessionKeys.map((sk) => ({\n title: sk.alias,\n value: sk,\n description: `${sk.data.sessionKeyType === \"ecdsa\" ? sk.data.sessionKeyAddress : sk.data.passKeyName} (${sk.data.smartAccountAlias})`,\n })) satisfies Prompt.SelectChoice<SessionKeyData>[],\n });\n\n return res;\n });\n\n const getSessionKeyStatus = (params: GetSessionKeyStatusParams) =>\n Effect.gen(function* () {\n const sessionKey = yield* getSessionKey({ alias: params.alias });\n const sa = yield* smartAccountManager.getSmartAccount({\n alias: sessionKey.data.smartAccountAlias,\n });\n\n const publicClient = yield* web3Service.getPublicClient({\n chain: params.chain,\n rpcUrl: params.rpcUrl,\n });\n\n if (sessionKey.data.sessionKeyType !== \"ecdsa\") {\n return yield* Effect.fail(\n new SessionKeyManagerError({\n code: \"SessionKeyParseError\",\n message: \"Only ECDSA session keys have an on-chain status\",\n }),\n );\n }\n const sessionKeyAddress = sessionKey.data.sessionKeyAddress;\n\n const res = yield* Effect.tryPromise({\n try: () =>\n isSessionKeyInstalled(publicClient, {\n accountAddress: sa.data.smartAccountAddress,\n sessionKeyAddress,\n }),\n catch: () => false,\n }).pipe(Effect.catch(() => Effect.succeed(false)));\n\n return res;\n });\n\n const multiSelectSessionKeys = (params: {\n message: string;\n smartAccount?: string;\n }) =>\n Effect.gen(function* () {\n const allKeys = yield* listSessionKeys({\n smartAccount: params.smartAccount,\n });\n\n const res = yield* promptManager.multiSelectPrompt({\n message: params.message,\n choices: allKeys.map((k) => ({\n title: k.alias,\n value: k,\n description: `${k.data.sessionKeyType === \"ecdsa\" ? k.data.sessionKeyAddress : k.data.passKeyName} (${k.data.smartAccountAlias})`,\n })) satisfies Prompt.SelectChoice<SessionKeyData>[],\n min: 1,\n });\n\n return res;\n });\n\n const getSessionKeyPassword = (params: {\n alias: string;\n message: string;\n }) =>\n Effect.gen(function* () {\n const key = yield* getSessionKey({ alias: params.alias });\n\n const password = yield* promptManager.passwordPrompt({\n message: params.message,\n validate: (v) =>\n Effect.gen(function* () {\n if (v.trim() === \"\") {\n return yield* Effect.fail(\"Password cannot be empty\");\n }\n\n yield* Effect.tryPromise({\n try: () => Wallet.fromV3(key.data, v),\n catch: () =>\n new SessionKeyManagerError({\n code: \"DecryptError\",\n message: `Invalid password for session key ${params.alias}`,\n }),\n }).pipe(Effect.catch(() => Effect.fail(\"Invalid password\")));\n\n return v;\n }),\n });\n\n return password;\n });\n\n const getSessionKeySigner = (params: { alias: string; password: string }) =>\n Effect.gen(function* () {\n const key = yield* getSessionKey({ alias: params.alias });\n\n const signer = yield* Effect.tryPromise({\n try: async () => {\n const wallet = await Wallet.fromV3(key.data, params.password);\n return privateKeyToAccount(\n toHex(wallet.getPrivateKey()),\n ) as LocalAccount;\n },\n catch: () =>\n new SessionKeyManagerError({\n code: \"DecryptError\",\n message: `Invalid password for session key ${params.alias}`,\n }),\n });\n\n return { ...key, signer };\n });\n\n const removeSessionKey = (params: RemoveSessionKeyParams) =>\n Effect.gen(function* () {\n const key = yield* getSessionKey({ alias: params.alias });\n\n return yield* configManager.removeEntity({\n alias: key.alias,\n type: \"session-key\",\n });\n });\n\n return SessionKeyManager.of({\n createSessionKey,\n getSessionKey,\n listSessionKeys,\n selectSessionKey,\n getSessionKeyStatus,\n multiSelectSessionKeys,\n getSessionKeyPassword,\n getSessionKeySigner,\n removeSessionKey,\n });\n }),\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { LocalSmartAccount } from "../dto/smart-account.mjs";
|
|
3
|
+
import "../dto/index.mjs";
|
|
4
|
+
import { ConfigManager } from "./config.mjs";
|
|
5
|
+
import { PromptManager } from "./prompt.mjs";
|
|
6
|
+
import { KeystoreManager } from "./keystore.mjs";
|
|
7
|
+
import { Web3Service } from "./web3.mjs";
|
|
8
|
+
import { Context, Data, Effect, Layer, Schema } from "effect";
|
|
9
|
+
import { mainnet } from "viem/chains";
|
|
10
|
+
import { createPublicClient, http } from "viem";
|
|
11
|
+
import { createAccountClient } from "@namera-ai/sdk/account";
|
|
12
|
+
//#region src/layers/smart-account.ts
|
|
13
|
+
/**
|
|
14
|
+
* Service tag for resolving {@link SmartAccountManager} from the Effect context.
|
|
15
|
+
*/
|
|
16
|
+
const SmartAccountManager = Context.Service("@namera-ai/cli/SmartAccountManager");
|
|
17
|
+
/**
|
|
18
|
+
* Domain error for smart account lifecycle operations.
|
|
19
|
+
*/
|
|
20
|
+
var SmartAccountManagerError = class extends Data.TaggedError("@namera-ai/cli/SmartAccountManagerError") {};
|
|
21
|
+
/**
|
|
22
|
+
* Live layer that manages smart account storage and derivation.
|
|
23
|
+
*/
|
|
24
|
+
const layer = Layer.effect(SmartAccountManager, Effect.gen(function* () {
|
|
25
|
+
const configManager = yield* ConfigManager;
|
|
26
|
+
const keystoreManager = yield* KeystoreManager;
|
|
27
|
+
const promptManager = yield* PromptManager;
|
|
28
|
+
const web3Service = yield* Web3Service;
|
|
29
|
+
const getSmartAccount = (params) => Effect.gen(function* () {
|
|
30
|
+
const res = yield* configManager.getEntity({
|
|
31
|
+
alias: params.alias,
|
|
32
|
+
type: "smart-account"
|
|
33
|
+
});
|
|
34
|
+
const parsedSmartAccount = Schema.decodeUnknownSync(Schema.fromJsonString(LocalSmartAccount))(res.content);
|
|
35
|
+
return {
|
|
36
|
+
alias: res.alias,
|
|
37
|
+
data: parsedSmartAccount,
|
|
38
|
+
path: res.path
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
const listSmartAccounts = () => Effect.gen(function* () {
|
|
42
|
+
const effects = (yield* configManager.getEntitiesForType("smart-account")).map((entity) => Effect.gen(function* () {
|
|
43
|
+
const parsedSmartAccount = Schema.decodeUnknownSync(Schema.fromJsonString(LocalSmartAccount))(entity.content);
|
|
44
|
+
return {
|
|
45
|
+
alias: entity.alias,
|
|
46
|
+
data: parsedSmartAccount,
|
|
47
|
+
path: entity.path
|
|
48
|
+
};
|
|
49
|
+
}));
|
|
50
|
+
return yield* Effect.all(effects, { concurrency: "unbounded" });
|
|
51
|
+
});
|
|
52
|
+
const createSmartAccount = (params) => Effect.gen(function* () {
|
|
53
|
+
const existingAccounts = yield* listSmartAccounts();
|
|
54
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
55
|
+
alias: params.alias,
|
|
56
|
+
type: "smart-account"
|
|
57
|
+
});
|
|
58
|
+
const ownerKeystore = yield* keystoreManager.getKeystore({ alias: params.ownerAlias });
|
|
59
|
+
const ownerSigner = yield* keystoreManager.getSigner({
|
|
60
|
+
alias: params.ownerAlias,
|
|
61
|
+
password: params.ownerPassword
|
|
62
|
+
});
|
|
63
|
+
const publicClient = createPublicClient({
|
|
64
|
+
chain: mainnet,
|
|
65
|
+
transport: http()
|
|
66
|
+
});
|
|
67
|
+
const entryPointVersion = "0.7";
|
|
68
|
+
const kernelVersion = "0.3.2";
|
|
69
|
+
const index = BigInt(params.index ?? 0);
|
|
70
|
+
const ownerType = "ecdsa";
|
|
71
|
+
const saAddress = (yield* Effect.tryPromise({
|
|
72
|
+
try: () => createAccountClient({
|
|
73
|
+
type: "ecdsa",
|
|
74
|
+
bundlerTransport: http(),
|
|
75
|
+
chain: mainnet,
|
|
76
|
+
client: publicClient,
|
|
77
|
+
entrypointVersion: entryPointVersion,
|
|
78
|
+
kernelVersion,
|
|
79
|
+
signer: ownerSigner
|
|
80
|
+
}),
|
|
81
|
+
catch: () => new SmartAccountManagerError({
|
|
82
|
+
code: "KernelAddressGenerationError",
|
|
83
|
+
message: `Unable to compute smart account address for Address: ${ownerKeystore.data.address} and Index: ${params.index}`
|
|
84
|
+
})
|
|
85
|
+
})).account.address;
|
|
86
|
+
if (existingAccounts.find((d) => d.data.smartAccountAddress === saAddress)) return yield* Effect.fail(new SmartAccountManagerError({
|
|
87
|
+
code: "SmartAccountAlreadyExists",
|
|
88
|
+
message: `Smart account for owner: ${ownerKeystore.alias} and index: ${params.index} already exists`
|
|
89
|
+
}));
|
|
90
|
+
const saData = {
|
|
91
|
+
entryPointVersion,
|
|
92
|
+
kernelVersion,
|
|
93
|
+
ownerAlias: ownerKeystore.alias,
|
|
94
|
+
ownerType,
|
|
95
|
+
smartAccountAddress: saAddress,
|
|
96
|
+
index
|
|
97
|
+
};
|
|
98
|
+
const data = {
|
|
99
|
+
data: saData,
|
|
100
|
+
path: entityPath,
|
|
101
|
+
alias: params.alias
|
|
102
|
+
};
|
|
103
|
+
const encoded = Schema.encodeSync(LocalSmartAccount)(saData);
|
|
104
|
+
yield* configManager.storeEntity({
|
|
105
|
+
alias: params.alias,
|
|
106
|
+
content: JSON.stringify(encoded),
|
|
107
|
+
path: entityPath,
|
|
108
|
+
type: "smart-account"
|
|
109
|
+
});
|
|
110
|
+
return data;
|
|
111
|
+
});
|
|
112
|
+
const selectSmartAccount = (params) => Effect.gen(function* () {
|
|
113
|
+
const smartAccounts = yield* listSmartAccounts();
|
|
114
|
+
return yield* promptManager.selectPrompt({
|
|
115
|
+
message: params.message,
|
|
116
|
+
choices: smartAccounts.map((a) => ({
|
|
117
|
+
title: a.alias,
|
|
118
|
+
value: a,
|
|
119
|
+
description: a.data.smartAccountAddress
|
|
120
|
+
}))
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
const removeSmartAccount = (params) => Effect.gen(function* () {
|
|
124
|
+
const sa = yield* getSmartAccount({ alias: params.alias });
|
|
125
|
+
return yield* configManager.removeEntity({
|
|
126
|
+
alias: sa.alias,
|
|
127
|
+
type: "smart-account"
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
const getSmartAccountStatus = (params) => Effect.gen(function* () {
|
|
131
|
+
const sa = yield* getSmartAccount({ alias: params.alias });
|
|
132
|
+
const publicClient = yield* web3Service.getPublicClient({
|
|
133
|
+
chain: params.chain,
|
|
134
|
+
rpcUrl: params.rpcUrl
|
|
135
|
+
});
|
|
136
|
+
const code = yield* Effect.promise(() => publicClient.getCode({ address: sa.data.smartAccountAddress }));
|
|
137
|
+
return Boolean(code);
|
|
138
|
+
});
|
|
139
|
+
const importSmartAccount = (params) => Effect.gen(function* () {
|
|
140
|
+
const { alias, ...rest } = params;
|
|
141
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
142
|
+
alias: params.alias,
|
|
143
|
+
type: "smart-account"
|
|
144
|
+
});
|
|
145
|
+
const ownerKeystore = yield* keystoreManager.getKeystore({ alias: params.ownerAlias });
|
|
146
|
+
if ((yield* listSmartAccounts()).find((d) => d.data.smartAccountAddress === rest.smartAccountAddress)) return yield* Effect.fail(new SmartAccountManagerError({
|
|
147
|
+
code: "SmartAccountAlreadyExists",
|
|
148
|
+
message: `Smart account for owner: ${ownerKeystore.alias} and address: ${rest.smartAccountAddress} already exists`
|
|
149
|
+
}));
|
|
150
|
+
yield* configManager.storeEntity({
|
|
151
|
+
alias: params.alias,
|
|
152
|
+
content: JSON.stringify(Schema.encodeSync(LocalSmartAccount)(rest)),
|
|
153
|
+
path: entityPath,
|
|
154
|
+
type: "smart-account"
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
alias: params.alias,
|
|
158
|
+
data: rest,
|
|
159
|
+
path: entityPath
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
return SmartAccountManager.of({
|
|
163
|
+
createSmartAccount,
|
|
164
|
+
getSmartAccount,
|
|
165
|
+
listSmartAccounts,
|
|
166
|
+
selectSmartAccount,
|
|
167
|
+
removeSmartAccount,
|
|
168
|
+
getSmartAccountStatus,
|
|
169
|
+
importSmartAccount
|
|
170
|
+
});
|
|
171
|
+
}));
|
|
172
|
+
//#endregion
|
|
173
|
+
export { SmartAccountManager, SmartAccountManagerError, layer };
|
|
174
|
+
|
|
175
|
+
//# sourceMappingURL=smart-account.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smart-account.mjs","names":[],"sources":["../../src/layers/smart-account.ts"],"sourcesContent":["import { createAccountClient } from \"@namera-ai/sdk/account\";\nimport { Context, Data, Effect, Layer, Schema } from \"effect\";\nimport type { QuitError } from \"effect/Terminal\";\nimport type { Prompt } from \"effect/unstable/cli\";\nimport type { Environment } from \"effect/unstable/cli/Prompt\";\nimport { createPublicClient, http, type PublicClient } from \"viem\";\nimport { mainnet } from \"viem/chains\";\n\nimport {\n type CreateSmartAccountParams,\n type GetSmartAccountParams,\n type GetSmartAccountStatusParams,\n type ImportSmartAccountParams,\n LocalSmartAccount,\n type LocalSmartAccountData,\n type RemoveSmartAccountParams,\n} from \"@/dto\";\n\nimport { ConfigManager, type ConfigManagerError } from \"./config\";\nimport { KeystoreManager, type KeystoreManagerError } from \"./keystore\";\nimport { PromptManager } from \"./prompt\";\nimport { Web3Service } from \"./web3\";\n\nexport type SmartAccountManager = {\n /**\n * Loads a smart account by alias and parses the stored JSON payload.\n *\n * @param params Alias of the smart account to load.\n * @returns The parsed smart account data.\n */\n getSmartAccount: (\n params: GetSmartAccountParams,\n ) => Effect.Effect<LocalSmartAccountData, ConfigManagerError, never>;\n /**\n * Lists all locally stored smart accounts.\n *\n * @returns The list of smart account data.\n */\n listSmartAccounts: () => Effect.Effect<\n LocalSmartAccountData[],\n ConfigManagerError,\n never\n >;\n /**\n * Prompts the user to select a smart account from local storage.\n *\n * @param params - Prompt message to display.\n * @returns The selected smart account data.\n */\n selectSmartAccount: (params: {\n message: string;\n }) => Effect.Effect<\n LocalSmartAccountData,\n ConfigManagerError | KeystoreManagerError | QuitError,\n Environment\n >;\n /**\n * Creates a new smart account derived from a local keystore and index.\n *\n * @param params - Alias, owner alias, and optional index for derivation.\n * @returns The created smart account data.\n */\n createSmartAccount: (\n params: CreateSmartAccountParams,\n ) => Effect.Effect<\n LocalSmartAccountData,\n SmartAccountManagerError | ConfigManagerError | KeystoreManagerError,\n never\n >;\n /**\n * Removes a locally stored smart account by alias.\n *\n * @param params - Alias of the smart account to remove.\n */\n removeSmartAccount: (\n params: RemoveSmartAccountParams,\n ) => Effect.Effect<\n void,\n ConfigManagerError | SmartAccountManagerError,\n never\n >;\n /**\n * Checks if a smart account is deployed on the specified chain.\n *\n * @param params - Alias plus chain and optional RPC URL.\n * @returns Whether the smart account is deployed.\n */\n getSmartAccountStatus: (\n params: GetSmartAccountStatusParams,\n ) => Effect.Effect<boolean, ConfigManagerError, never>;\n /**\n * Imports a smart account with precomputed metadata and address.\n *\n * @param params - Smart account metadata and alias to store.\n * @returns The imported smart account data.\n */\n importSmartAccount: (\n params: ImportSmartAccountParams,\n ) => Effect.Effect<\n LocalSmartAccountData,\n SmartAccountManagerError | ConfigManagerError | KeystoreManagerError,\n never\n >;\n};\n\n/**\n * Service tag for resolving {@link SmartAccountManager} from the Effect context.\n */\nexport const SmartAccountManager = Context.Service<SmartAccountManager>(\n \"@namera-ai/cli/SmartAccountManager\",\n);\n\n/**\n * Domain error for smart account lifecycle operations.\n */\nexport class SmartAccountManagerError extends Data.TaggedError(\n \"@namera-ai/cli/SmartAccountManagerError\",\n)<{\n code:\n | \"KernelAddressGenerationError\"\n | \"SmartAccountAlreadyExists\"\n | \"SmartAccountNotFound\"\n | \"SmartAccountImportError\";\n message: string;\n}> {}\n\n/**\n * Live layer that manages smart account storage and derivation.\n */\nexport const layer = Layer.effect(\n SmartAccountManager,\n Effect.gen(function* () {\n const configManager = yield* ConfigManager;\n const keystoreManager = yield* KeystoreManager;\n const promptManager = yield* PromptManager;\n const web3Service = yield* Web3Service;\n\n const getSmartAccount = (params: GetSmartAccountParams) =>\n Effect.gen(function* () {\n const res = yield* configManager.getEntity({\n alias: params.alias,\n type: \"smart-account\",\n });\n\n const parsedSmartAccount = Schema.decodeUnknownSync(\n Schema.fromJsonString(LocalSmartAccount),\n )(res.content);\n\n return {\n alias: res.alias,\n data: parsedSmartAccount,\n path: res.path,\n } satisfies LocalSmartAccountData;\n });\n\n const listSmartAccounts = () =>\n Effect.gen(function* () {\n const res = yield* configManager.getEntitiesForType(\"smart-account\");\n\n const effects = res.map((entity) =>\n Effect.gen(function* () {\n const parsedSmartAccount = Schema.decodeUnknownSync(\n Schema.fromJsonString(LocalSmartAccount),\n )(entity.content);\n\n return {\n alias: entity.alias,\n data: parsedSmartAccount,\n path: entity.path,\n } satisfies LocalSmartAccountData;\n }),\n );\n\n return yield* Effect.all(effects, { concurrency: \"unbounded\" });\n });\n\n const createSmartAccount = (params: CreateSmartAccountParams) =>\n Effect.gen(function* () {\n const existingAccounts = yield* listSmartAccounts();\n\n const entityPath = yield* configManager.getEntityPath({\n alias: params.alias,\n type: \"smart-account\",\n });\n\n const ownerKeystore = yield* keystoreManager.getKeystore({\n alias: params.ownerAlias,\n });\n\n const ownerSigner = yield* keystoreManager.getSigner({\n alias: params.ownerAlias,\n password: params.ownerPassword,\n });\n\n // Doesn't matter because public client is only needed for 0.6 entrypoints\n const publicClient = createPublicClient({\n chain: mainnet,\n transport: http(),\n });\n\n const entryPointVersion = \"0.7\";\n const kernelVersion = \"0.3.2\";\n const index = BigInt(params.index ?? 0);\n const ownerType = \"ecdsa\";\n\n const res = yield* Effect.tryPromise({\n try: () =>\n createAccountClient({\n type: \"ecdsa\",\n bundlerTransport: http(),\n chain: mainnet,\n client: publicClient,\n entrypointVersion: entryPointVersion,\n kernelVersion,\n signer: ownerSigner,\n }),\n catch: () =>\n new SmartAccountManagerError({\n code: \"KernelAddressGenerationError\",\n message: `Unable to compute smart account address for Address: ${ownerKeystore.data.address} and Index: ${params.index}`,\n }),\n });\n\n const saAddress = res.account.address;\n\n const exists = existingAccounts.find(\n (d) => d.data.smartAccountAddress === saAddress,\n );\n\n if (exists) {\n return yield* Effect.fail(\n new SmartAccountManagerError({\n code: \"SmartAccountAlreadyExists\",\n message: `Smart account for owner: ${ownerKeystore.alias} and index: ${params.index} already exists`,\n }),\n );\n }\n\n const saData: LocalSmartAccount = {\n entryPointVersion,\n kernelVersion,\n ownerAlias: ownerKeystore.alias,\n ownerType,\n smartAccountAddress: saAddress,\n index,\n };\n\n const data: LocalSmartAccountData = {\n data: saData,\n path: entityPath,\n alias: params.alias,\n };\n\n const encoded = Schema.encodeSync(LocalSmartAccount)(saData);\n\n yield* configManager.storeEntity({\n alias: params.alias,\n content: JSON.stringify(encoded),\n path: entityPath,\n type: \"smart-account\",\n });\n\n return data;\n });\n\n const selectSmartAccount = (params: { message: string }) =>\n Effect.gen(function* () {\n const smartAccounts = yield* listSmartAccounts();\n\n const res = yield* promptManager.selectPrompt({\n message: params.message,\n choices: smartAccounts.map((a) => ({\n title: a.alias,\n value: a,\n description: a.data.smartAccountAddress,\n })) satisfies Prompt.SelectChoice<LocalSmartAccountData>[],\n });\n\n return res;\n });\n\n const removeSmartAccount = (params: RemoveSmartAccountParams) =>\n Effect.gen(function* () {\n const sa = yield* getSmartAccount({ alias: params.alias });\n\n return yield* configManager.removeEntity({\n alias: sa.alias,\n type: \"smart-account\",\n });\n });\n\n const getSmartAccountStatus = (params: GetSmartAccountStatusParams) =>\n Effect.gen(function* () {\n const sa = yield* getSmartAccount({ alias: params.alias });\n\n const publicClient = (yield* web3Service.getPublicClient({\n chain: params.chain,\n rpcUrl: params.rpcUrl,\n })) as PublicClient;\n\n const code = yield* Effect.promise(() =>\n publicClient.getCode({\n address: sa.data.smartAccountAddress,\n }),\n );\n\n return Boolean(code);\n });\n\n const importSmartAccount = (params: ImportSmartAccountParams) =>\n Effect.gen(function* () {\n const { alias, ...rest } = params;\n const entityPath = yield* configManager.getEntityPath({\n alias: params.alias,\n type: \"smart-account\",\n });\n\n const ownerKeystore = yield* keystoreManager.getKeystore({\n alias: params.ownerAlias,\n });\n\n const allAccounts = yield* listSmartAccounts();\n\n const exists = allAccounts.find(\n (d) => d.data.smartAccountAddress === rest.smartAccountAddress,\n );\n\n if (exists) {\n return yield* Effect.fail(\n new SmartAccountManagerError({\n code: \"SmartAccountAlreadyExists\",\n message: `Smart account for owner: ${ownerKeystore.alias} and address: ${rest.smartAccountAddress} already exists`,\n }),\n );\n }\n\n yield* configManager.storeEntity({\n alias: params.alias,\n content: JSON.stringify(Schema.encodeSync(LocalSmartAccount)(rest)),\n path: entityPath,\n type: \"smart-account\",\n });\n\n return {\n alias: params.alias,\n data: rest,\n path: entityPath,\n };\n });\n\n return SmartAccountManager.of({\n createSmartAccount,\n getSmartAccount,\n listSmartAccounts,\n selectSmartAccount,\n removeSmartAccount,\n getSmartAccountStatus,\n importSmartAccount,\n });\n }),\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getChain, supportedChains } from "../schema/chain.mjs";
|
|
3
|
+
import "../schema/index.mjs";
|
|
4
|
+
import { PromptManager } from "./prompt.mjs";
|
|
5
|
+
import { Config, Context, Effect, Layer, Redacted } from "effect";
|
|
6
|
+
import { createPublicClient, http } from "viem";
|
|
7
|
+
//#region src/layers/web3.ts
|
|
8
|
+
/**
|
|
9
|
+
* Service tag for resolving {@link Web3Service} from the Effect context.
|
|
10
|
+
*/
|
|
11
|
+
const Web3Service = Context.Service("@namera-ai/cli/Web3Service");
|
|
12
|
+
/**
|
|
13
|
+
* Live layer that wires web3 clients and chain selection prompts.
|
|
14
|
+
*/
|
|
15
|
+
const layer = Layer.effect(Web3Service, Effect.gen(function* () {
|
|
16
|
+
const promptManager = yield* PromptManager;
|
|
17
|
+
const chainNameToEnvVar = (chain, suffix) => {
|
|
18
|
+
return `${chain.replaceAll("-", "_").toUpperCase()}_${suffix}`;
|
|
19
|
+
};
|
|
20
|
+
const getRpcUrl = (params) => Effect.gen(function* () {
|
|
21
|
+
let rpcUrl;
|
|
22
|
+
if (params.rpcUrl) rpcUrl = params.rpcUrl;
|
|
23
|
+
else {
|
|
24
|
+
const envVarName = chainNameToEnvVar(params.chain, "RPC_URL");
|
|
25
|
+
const envRpcUrl = yield* Config.option(Config.redacted(envVarName));
|
|
26
|
+
if (envRpcUrl._tag === "Some") rpcUrl = Redacted.value(envRpcUrl.value);
|
|
27
|
+
else rpcUrl = void 0;
|
|
28
|
+
}
|
|
29
|
+
return rpcUrl;
|
|
30
|
+
}).pipe(Effect.orDie);
|
|
31
|
+
const getBundlerUrl = (params) => Effect.gen(function* () {
|
|
32
|
+
let bundlerUrl;
|
|
33
|
+
if (params.bundlerUrl) bundlerUrl = params.bundlerUrl;
|
|
34
|
+
else {
|
|
35
|
+
const envVarName = chainNameToEnvVar(params.chain, "BUNDLER_URL");
|
|
36
|
+
const envRpcUrl = yield* Config.option(Config.redacted(envVarName));
|
|
37
|
+
if (envRpcUrl._tag === "Some") bundlerUrl = Redacted.value(envRpcUrl.value);
|
|
38
|
+
else bundlerUrl = `https://public.pimlico.io/v2/${getChain(params.chain).id}/rpc`;
|
|
39
|
+
}
|
|
40
|
+
return bundlerUrl;
|
|
41
|
+
}).pipe(Effect.orDie);
|
|
42
|
+
const getPublicClient = (params) => Effect.gen(function* () {
|
|
43
|
+
const rpcUrl = yield* getRpcUrl(params);
|
|
44
|
+
return createPublicClient({
|
|
45
|
+
chain: getChain(params.chain),
|
|
46
|
+
transport: http(rpcUrl)
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
const getBundlerTransport = (params) => Effect.gen(function* () {
|
|
50
|
+
return http(yield* getBundlerUrl(params));
|
|
51
|
+
});
|
|
52
|
+
const selectChain = (params) => Effect.gen(function* () {
|
|
53
|
+
const chains = Object.values(supportedChains);
|
|
54
|
+
return yield* promptManager.selectPrompt({
|
|
55
|
+
message: params.message,
|
|
56
|
+
choices: chains.map((c) => ({
|
|
57
|
+
title: c.name,
|
|
58
|
+
value: c.key
|
|
59
|
+
}))
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
const multiSelectChain = (params) => Effect.gen(function* () {
|
|
63
|
+
const chains = Object.values(supportedChains);
|
|
64
|
+
return yield* promptManager.multiSelectPrompt({
|
|
65
|
+
message: params.message,
|
|
66
|
+
choices: chains.map((c) => ({
|
|
67
|
+
title: c.name,
|
|
68
|
+
value: c.key
|
|
69
|
+
})),
|
|
70
|
+
min: 1
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return Web3Service.of({
|
|
74
|
+
getPublicClient,
|
|
75
|
+
selectChain,
|
|
76
|
+
multiSelectChain,
|
|
77
|
+
getBundlerTransport
|
|
78
|
+
});
|
|
79
|
+
}));
|
|
80
|
+
//#endregion
|
|
81
|
+
export { Web3Service, layer };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=web3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web3.mjs","names":[],"sources":["../../src/layers/web3.ts"],"sourcesContent":["import { Config, Context, Effect, Layer, Redacted } from \"effect\";\nimport type { QuitError } from \"effect/Terminal\";\nimport type { Prompt } from \"effect/unstable/cli\";\nimport {\n type Chain,\n type Client,\n createPublicClient,\n type HttpTransport,\n http,\n} from \"viem\";\n\nimport {\n type ChainWithMetadata,\n getChain,\n type SupportedChain,\n supportedChains,\n} from \"@/schema\";\n\nimport { PromptManager } from \"./prompt\";\n\nexport type Web3Service = {\n /**\n * Creates a viem public client for the requested chain.\n *\n * Falls back to an environment-derived RPC URL when not explicitly provided.\n *\n * @param params - Chain key and optional RPC URL override.\n */\n getPublicClient: (\n params: GetPublicClientParams,\n ) => Effect.Effect<Client<HttpTransport, Chain, undefined>>;\n /**\n * Prompts the user to select a single supported chain.\n *\n * @param params - Prompt message to display.\n */\n selectChain: (params: {\n message: string;\n }) => Effect.Effect<SupportedChain, QuitError, Prompt.Environment>;\n /**\n * Prompts the user to select one or more supported chains.\n *\n * @param params - Prompt message to display.\n */\n multiSelectChain: (params: {\n message: string;\n }) => Effect.Effect<SupportedChain[], QuitError, Prompt.Environment>;\n /**\n * Creates a viem bundler transport for the requested chain.\n *\n * Falls back to an environment-derived RPC URL when not explicitly provided.\n *\n * @param params - Chain key and optional RPC URL override.\n */\n getBundlerTransport: (\n params: GetBundlerClientParams,\n ) => Effect.Effect<HttpTransport<undefined, false>, never, never>;\n};\n\n/**\n * Parameters for constructing a public client.\n */\ntype GetPublicClientParams = {\n /**\n * Target chain identifier key.\n */\n chain: SupportedChain;\n /**\n * Optional RPC URL override for the chain.\n */\n rpcUrl?: string;\n};\n\nexport type GetBundlerClientParams = {\n /**\n * Target chain identifier key.\n */\n chain: SupportedChain;\n /**\n * Optional RPC URL override for the chain.\n *\n */\n bundlerUrl?: string;\n};\n\n/**\n * Service tag for resolving {@link Web3Service} from the Effect context.\n */\nexport const Web3Service = Context.Service<Web3Service>(\n \"@namera-ai/cli/Web3Service\",\n);\n\n/**\n * Live layer that wires web3 clients and chain selection prompts.\n */\nexport const layer = Layer.effect(\n Web3Service,\n Effect.gen(function* () {\n const promptManager = yield* PromptManager;\n\n const chainNameToEnvVar = (chain: SupportedChain, suffix: string) => {\n return `${chain.replaceAll(\"-\", \"_\").toUpperCase()}_${suffix}`;\n };\n\n const getRpcUrl = (params: GetPublicClientParams) =>\n Effect.gen(function* () {\n let rpcUrl: string | undefined;\n\n if (params.rpcUrl) {\n rpcUrl = params.rpcUrl;\n } else {\n // check env\n const envVarName = chainNameToEnvVar(params.chain, \"RPC_URL\");\n const envRpcUrl = yield* Config.option(Config.redacted(envVarName));\n\n if (envRpcUrl._tag === \"Some\") {\n rpcUrl = Redacted.value(envRpcUrl.value);\n } else {\n rpcUrl = undefined;\n }\n }\n\n return rpcUrl;\n }).pipe(Effect.orDie);\n\n const getBundlerUrl = (params: GetBundlerClientParams) =>\n Effect.gen(function* () {\n let bundlerUrl: string | undefined;\n\n if (params.bundlerUrl) {\n bundlerUrl = params.bundlerUrl;\n } else {\n // check env\n const envVarName = chainNameToEnvVar(params.chain, \"BUNDLER_URL\");\n const envRpcUrl = yield* Config.option(Config.redacted(envVarName));\n\n if (envRpcUrl._tag === \"Some\") {\n bundlerUrl = Redacted.value(envRpcUrl.value);\n } else {\n const chain = getChain(params.chain);\n // Defaults to Pimlico Public RPC\n bundlerUrl = `https://public.pimlico.io/v2/${chain.id}/rpc`;\n }\n }\n\n return bundlerUrl;\n }).pipe(Effect.orDie);\n\n const getPublicClient = (params: GetPublicClientParams) =>\n Effect.gen(function* () {\n const rpcUrl = yield* getRpcUrl(params);\n const chain = getChain(params.chain);\n const client = createPublicClient({\n chain,\n transport: http(rpcUrl),\n });\n\n return client;\n });\n\n const getBundlerTransport = (params: GetBundlerClientParams) =>\n Effect.gen(function* () {\n const bundlerUrl = yield* getBundlerUrl(params);\n return http(bundlerUrl);\n });\n\n const selectChain = (params: { message: string }) =>\n Effect.gen(function* () {\n const chains = Object.values(supportedChains) as ChainWithMetadata[];\n\n const res = yield* promptManager.selectPrompt({\n message: params.message,\n choices: chains.map((c) => ({\n title: c.name,\n value: c.key as SupportedChain,\n })) satisfies Prompt.SelectChoice<SupportedChain>[],\n });\n\n return res;\n });\n\n const multiSelectChain = (params: { message: string }) =>\n Effect.gen(function* () {\n const chains = Object.values(supportedChains) as ChainWithMetadata[];\n\n const res = yield* promptManager.multiSelectPrompt({\n message: params.message,\n choices: chains.map((c) => ({\n title: c.name,\n value: c.key as SupportedChain,\n })) satisfies Prompt.SelectChoice<SupportedChain>[],\n min: 1,\n });\n\n return res;\n });\n\n return Web3Service.of({\n getPublicClient,\n selectChain,\n multiSelectChain,\n getBundlerTransport,\n });\n }),\n);\n"],"mappings":";;;;;;;;;;AAwFA,MAAa,cAAc,QAAQ,QACjC,6BACD;;;;AAKD,MAAa,QAAQ,MAAM,OACzB,aACA,OAAO,IAAI,aAAa;CACtB,MAAM,gBAAgB,OAAO;CAE7B,MAAM,qBAAqB,OAAuB,WAAmB;AACnE,SAAO,GAAG,MAAM,WAAW,KAAK,IAAI,CAAC,aAAa,CAAC,GAAG;;CAGxD,MAAM,aAAa,WACjB,OAAO,IAAI,aAAa;EACtB,IAAI;AAEJ,MAAI,OAAO,OACT,UAAS,OAAO;OACX;GAEL,MAAM,aAAa,kBAAkB,OAAO,OAAO,UAAU;GAC7D,MAAM,YAAY,OAAO,OAAO,OAAO,OAAO,SAAS,WAAW,CAAC;AAEnE,OAAI,UAAU,SAAS,OACrB,UAAS,SAAS,MAAM,UAAU,MAAM;OAExC,UAAS;;AAIb,SAAO;GACP,CAAC,KAAK,OAAO,MAAM;CAEvB,MAAM,iBAAiB,WACrB,OAAO,IAAI,aAAa;EACtB,IAAI;AAEJ,MAAI,OAAO,WACT,cAAa,OAAO;OACf;GAEL,MAAM,aAAa,kBAAkB,OAAO,OAAO,cAAc;GACjE,MAAM,YAAY,OAAO,OAAO,OAAO,OAAO,SAAS,WAAW,CAAC;AAEnE,OAAI,UAAU,SAAS,OACrB,cAAa,SAAS,MAAM,UAAU,MAAM;OAI5C,cAAa,gCAFC,SAAS,OAAO,MAEe,CAAM,GAAG;;AAI1D,SAAO;GACP,CAAC,KAAK,OAAO,MAAM;CAEvB,MAAM,mBAAmB,WACvB,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO,UAAU,OAAO;AAOvC,SALe,mBAAmB;GAChC,OAFY,SAAS,OAAO,MAE5B;GACA,WAAW,KAAK,OAAO;GACxB,CAEM;GACP;CAEJ,MAAM,uBAAuB,WAC3B,OAAO,IAAI,aAAa;AAEtB,SAAO,KAAK,OADc,cAAc,OAAO,CACxB;GACvB;CAEJ,MAAM,eAAe,WACnB,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO,OAAO,gBAAgB;AAU7C,SAAO,OARY,cAAc,aAAa;GAC5C,SAAS,OAAO;GAChB,SAAS,OAAO,KAAK,OAAO;IAC1B,OAAO,EAAE;IACT,OAAO,EAAE;IACV,EAAE;GACJ,CAAC;GAGF;CAEJ,MAAM,oBAAoB,WACxB,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO,OAAO,gBAAgB;AAW7C,SAAO,OATY,cAAc,kBAAkB;GACjD,SAAS,OAAO;GAChB,SAAS,OAAO,KAAK,OAAO;IAC1B,OAAO,EAAE;IACT,OAAO,EAAE;IACV,EAAE;GACH,KAAK;GACN,CAAC;GAGF;AAEJ,QAAO,YAAY,GAAG;EACpB;EACA;EACA;EACA;EACD,CAAC;EACF,CACH"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
//#region src/mcp/helpers/common.ts
|
|
4
|
+
const EmptyArgs = Schema.Record(Schema.String, Schema.Unknown);
|
|
5
|
+
var InsufficientPermissions = class extends Schema.TaggedErrorClass()("InsufficientPermissions", {}) {};
|
|
6
|
+
//#endregion
|
|
7
|
+
export { EmptyArgs, InsufficientPermissions };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=common.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.mjs","names":[],"sources":["../../../src/mcp/helpers/common.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\nexport const EmptyArgs = Schema.Record(Schema.String, Schema.Unknown);\n\nexport class InsufficientPermissions extends Schema.TaggedErrorClass<InsufficientPermissions>()(\n \"InsufficientPermissions\",\n {},\n) {}\n"],"mappings":";;;AAEA,MAAa,YAAY,OAAO,OAAO,OAAO,QAAQ,OAAO,QAAQ;AAErE,IAAa,0BAAb,cAA6C,OAAO,kBAA2C,CAC7F,2BACA,EAAE,CACH,CAAC"}
|