@namera-ai/cli 0.0.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 +268 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +3270 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { NodeHttpServer, NodeRuntime, NodeServices, NodeStdio } from "@effect/platform-node";
|
|
4
|
+
import { Config, ConfigProvider, Console, Data, Duration, Effect, FileSystem, Layer, Logger, Option, Path, Redacted, Schema, SchemaTransformation, ServiceMap, Struct } from "effect";
|
|
5
|
+
import { Argument, Command, Flag, GlobalFlag, Prompt } from "effect/unstable/cli";
|
|
6
|
+
import { arbitrum, arbitrumSepolia, arcTestnet, avalanche, avalancheFuji, base, baseSepolia, celo, celoSepolia, mainnet, monad, monadTestnet, optimism, optimismSepolia, polygon, polygonAmoy, scroll, scrollSepolia, sepolia, tempoModerato, unichain, unichainSepolia, zora, zoraSepolia } from "viem/chains";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import { Wallet } from "@ethereumjs/wallet";
|
|
9
|
+
import { createPublicClient, formatEther, formatUnits, hexToBytes, http, isHex, parseEther, parseUnits, toFunctionSelector, toHex, zeroAddress } from "viem";
|
|
10
|
+
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
import { McpServer, Tool, Toolkit } from "effect/unstable/ai";
|
|
13
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
14
|
+
import { getBalance, readContract } from "viem/actions";
|
|
15
|
+
import { createSessionKey, createSessionKeyClient, deserializePermissionAccountParams, isSessionKeyInstalled } from "@namera-ai/sdk/session-key";
|
|
16
|
+
import { executeTransaction } from "@namera-ai/sdk/transaction";
|
|
17
|
+
import { CallPolicyVersion, CallType, ParamCondition, toCallPolicy, toGasPolicy, toRateLimitPolicy, toSignatureCallerPolicy, toSudoPolicy, toTimestampPolicy } from "@namera-ai/sdk/policy";
|
|
18
|
+
import { createAccountClient } from "@namera-ai/sdk/account";
|
|
19
|
+
import { NdJson } from "json-nd";
|
|
20
|
+
//#region src/dto/keystore.ts
|
|
21
|
+
const Keystore = Schema.Struct({
|
|
22
|
+
version: Schema.Number,
|
|
23
|
+
id: Schema.String,
|
|
24
|
+
address: Schema.String,
|
|
25
|
+
crypto: Schema.Any
|
|
26
|
+
});
|
|
27
|
+
Schema.Struct({
|
|
28
|
+
path: Schema.String,
|
|
29
|
+
alias: Schema.String,
|
|
30
|
+
data: Keystore
|
|
31
|
+
});
|
|
32
|
+
const GetKeystoreParams = Schema.Struct({ alias: Schema.String.annotate({ description: "The alias of the wallet to retrieve" }) });
|
|
33
|
+
const ListKeystoreParams = Schema.Void;
|
|
34
|
+
const CreateKeystoreParams = Schema.Struct({
|
|
35
|
+
alias: Schema.String.annotate({ description: "The alias of the wallet to create" }),
|
|
36
|
+
password: Schema.redact(Schema.String).annotate({ description: "The password to encrypt the keystore with" })
|
|
37
|
+
});
|
|
38
|
+
const DecryptKeystoreParams = Schema.Struct({
|
|
39
|
+
alias: Schema.String.annotate({ description: "The alias of the keystore to decrypt" }),
|
|
40
|
+
password: Schema.redact(Schema.String).annotate({ description: "The password to decrypt the keystore with" })
|
|
41
|
+
});
|
|
42
|
+
Schema.Struct({
|
|
43
|
+
address: Schema.String.annotate({ description: "The address of the keystore" }),
|
|
44
|
+
alias: Schema.String.annotate({ description: "The alias of the keystore" }),
|
|
45
|
+
privateKey: Schema.Redacted(Schema.String).annotate({ description: "The private key of the keystore" }),
|
|
46
|
+
publicKey: Schema.String.annotate({ description: "The public key of the keystore" })
|
|
47
|
+
});
|
|
48
|
+
const ImportKeystoreParams = Schema.Struct({
|
|
49
|
+
alias: Schema.String.annotate({ description: "The alias of the keystore to import" }),
|
|
50
|
+
privateKey: Schema.String.annotate({ description: "The private key of the keystore to import" }),
|
|
51
|
+
password: Schema.redact(Schema.String).annotate({ description: "The password to encrypt the keystore with" })
|
|
52
|
+
});
|
|
53
|
+
const RemoveKeystoreParams = Schema.Struct({ alias: Schema.String.annotate({ description: "The alias of the keystore to remove" }) });
|
|
54
|
+
Schema.Struct({
|
|
55
|
+
alias: Schema.String.annotate({ description: "The alias of the keystore to get the signer for" }),
|
|
56
|
+
password: Schema.redact(Schema.String).annotate({ description: "The password to decrypt the keystore with" })
|
|
57
|
+
});
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/dto/mcp.ts
|
|
60
|
+
const StartMcpServerParams = Schema.Struct({
|
|
61
|
+
smartAccountAlias: Schema.String.annotate({ description: "The alias of the smart account to use for the MCP server" }),
|
|
62
|
+
transport: Schema.Literals(["http", "stdio"]).annotate({ description: "The transport to use for the MCP server" }),
|
|
63
|
+
port: Schema.optional(Schema.Int.check(Schema.makeFilter((v) => v > 0 && v < 65536))).annotate({ description: "The port to use for the MCP server when using http transport" }),
|
|
64
|
+
sessionKeys: Schema.mutableKey(Schema.Record(Schema.String, Schema.String))
|
|
65
|
+
});
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/schema/chain.ts
|
|
68
|
+
const supportedMainnetChains = {
|
|
69
|
+
"eth-mainnet": {
|
|
70
|
+
...mainnet,
|
|
71
|
+
key: "eth-mainnet"
|
|
72
|
+
},
|
|
73
|
+
"opt-mainnet": {
|
|
74
|
+
...optimism,
|
|
75
|
+
key: "opt-mainnet"
|
|
76
|
+
},
|
|
77
|
+
"polygon-mainnet": {
|
|
78
|
+
...polygon,
|
|
79
|
+
key: "polygon-mainnet"
|
|
80
|
+
},
|
|
81
|
+
"arb-mainnet": {
|
|
82
|
+
...arbitrum,
|
|
83
|
+
key: "arb-mainnet"
|
|
84
|
+
},
|
|
85
|
+
"zora-mainnet": {
|
|
86
|
+
...zora,
|
|
87
|
+
key: "zora-mainnet"
|
|
88
|
+
},
|
|
89
|
+
"base-mainnet": {
|
|
90
|
+
...base,
|
|
91
|
+
key: "base-mainnet"
|
|
92
|
+
},
|
|
93
|
+
"avax-mainnet": {
|
|
94
|
+
...avalanche,
|
|
95
|
+
key: "avax-mainnet"
|
|
96
|
+
},
|
|
97
|
+
"unichain-mainnet": {
|
|
98
|
+
...unichain,
|
|
99
|
+
key: "unichain-mainnet"
|
|
100
|
+
},
|
|
101
|
+
"celo-mainnet": {
|
|
102
|
+
...celo,
|
|
103
|
+
key: "celo-mainnet"
|
|
104
|
+
},
|
|
105
|
+
"scroll-mainnet": {
|
|
106
|
+
...scroll,
|
|
107
|
+
key: "scroll-mainnet"
|
|
108
|
+
},
|
|
109
|
+
"monad-mainnet": {
|
|
110
|
+
...monad,
|
|
111
|
+
key: "monad-mainnet"
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const supportedTestnetChains = {
|
|
115
|
+
"eth-sepolia": {
|
|
116
|
+
...sepolia,
|
|
117
|
+
key: "eth-sepolia"
|
|
118
|
+
},
|
|
119
|
+
"opt-sepolia": {
|
|
120
|
+
...optimismSepolia,
|
|
121
|
+
key: "opt-sepolia"
|
|
122
|
+
},
|
|
123
|
+
"polygon-amoy": {
|
|
124
|
+
...polygonAmoy,
|
|
125
|
+
key: "polygon-amoy"
|
|
126
|
+
},
|
|
127
|
+
"arb-sepolia": {
|
|
128
|
+
...arbitrumSepolia,
|
|
129
|
+
key: "arb-sepolia"
|
|
130
|
+
},
|
|
131
|
+
"zora-sepolia": {
|
|
132
|
+
...zoraSepolia,
|
|
133
|
+
key: "zora-sepolia"
|
|
134
|
+
},
|
|
135
|
+
"base-sepolia": {
|
|
136
|
+
...baseSepolia,
|
|
137
|
+
key: "base-sepolia"
|
|
138
|
+
},
|
|
139
|
+
"tempo-moderato": {
|
|
140
|
+
...tempoModerato,
|
|
141
|
+
key: "tempo-moderato"
|
|
142
|
+
},
|
|
143
|
+
"avax-fuji": {
|
|
144
|
+
...avalancheFuji,
|
|
145
|
+
key: "avax-fuji"
|
|
146
|
+
},
|
|
147
|
+
"unichain-sepolia": {
|
|
148
|
+
...unichainSepolia,
|
|
149
|
+
key: "unichain-sepolia"
|
|
150
|
+
},
|
|
151
|
+
"monad-testnet": {
|
|
152
|
+
...monadTestnet,
|
|
153
|
+
key: "monad-testnet"
|
|
154
|
+
},
|
|
155
|
+
"celo-sepolia": {
|
|
156
|
+
...celoSepolia,
|
|
157
|
+
key: "celo-sepolia"
|
|
158
|
+
},
|
|
159
|
+
"scroll-sepolia": {
|
|
160
|
+
...scrollSepolia,
|
|
161
|
+
key: "scroll-sepolia"
|
|
162
|
+
},
|
|
163
|
+
"arc-testnet": {
|
|
164
|
+
...arcTestnet,
|
|
165
|
+
key: "arc-testnet"
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const SupportedChain = Schema.Literals([...Object.keys(supportedMainnetChains), ...Object.keys(supportedTestnetChains)]);
|
|
169
|
+
const supportedChains = {
|
|
170
|
+
...supportedMainnetChains,
|
|
171
|
+
...supportedTestnetChains
|
|
172
|
+
};
|
|
173
|
+
const getChain = (chain) => {
|
|
174
|
+
return supportedChains[chain];
|
|
175
|
+
};
|
|
176
|
+
const getChainFromId = (chainId) => {
|
|
177
|
+
return Object.values(supportedChains).find((c) => c.id === chainId);
|
|
178
|
+
};
|
|
179
|
+
const chainIdToChainName = (chainId) => {
|
|
180
|
+
const chain = getChainFromId(chainId);
|
|
181
|
+
if (!chain) throw new Error(`Chain with id ${chainId} not found`);
|
|
182
|
+
return chain.key;
|
|
183
|
+
};
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/schema/common.ts
|
|
186
|
+
const BigIntFromString = Schema.String.pipe(Schema.decodeTo(Schema.BigInt, SchemaTransformation.transform({
|
|
187
|
+
encode: (v) => v.toString(),
|
|
188
|
+
decode: (v) => BigInt(v)
|
|
189
|
+
})));
|
|
190
|
+
const EthereumAddress = Schema.TemplateLiteral(["0x", Schema.String.check(Schema.isPattern(/^[0-9a-fA-F]{40}$/))]);
|
|
191
|
+
const Hex = Schema.TemplateLiteral(["0x", Schema.String.check(Schema.isPattern(/^[0-9a-fA-F]*$/))]);
|
|
192
|
+
const EntrypointVersion = Schema.Literals([
|
|
193
|
+
"0.7",
|
|
194
|
+
"0.8",
|
|
195
|
+
"0.9"
|
|
196
|
+
]);
|
|
197
|
+
const KernelVersion = Schema.Literals([
|
|
198
|
+
"0.3.0",
|
|
199
|
+
"0.3.1",
|
|
200
|
+
"0.3.2",
|
|
201
|
+
"0.3.3"
|
|
202
|
+
]);
|
|
203
|
+
const OwnerType = Schema.Literals([
|
|
204
|
+
"ecdsa",
|
|
205
|
+
"passkey",
|
|
206
|
+
"multisig"
|
|
207
|
+
]);
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/schema/policy.ts
|
|
210
|
+
const SudoPolicyParams = Schema.Struct({ type: Schema.Literal("sudo") });
|
|
211
|
+
const TimestampPolicyParams = Schema.Struct({
|
|
212
|
+
type: Schema.Literal("timestamp"),
|
|
213
|
+
validAfter: Schema.optional(Schema.Number).pipe(Schema.withDecodingDefault(() => 0)).annotate({ description: "The timestamp in seconds after which signer is valid. If not provided, the signer is valid immediately." }),
|
|
214
|
+
validUntil: Schema.optional(Schema.Number).pipe(Schema.withDecodingDefault(() => 0)).annotate({ description: "The timestamp in seconds until which signer is valid. If not provided, the signer is valid indefinitely." })
|
|
215
|
+
});
|
|
216
|
+
const SignatureCallerPolicyParams = Schema.Struct({
|
|
217
|
+
type: Schema.Literal("signature-caller"),
|
|
218
|
+
allowedCallers: Schema.mutable(Schema.Array(EthereumAddress)).annotate({ description: "List of addresses that are allowed to validate messages signed by the signer." })
|
|
219
|
+
});
|
|
220
|
+
const RateLimitPolicyParams = Schema.Struct({
|
|
221
|
+
type: Schema.Literal("rate-limit"),
|
|
222
|
+
interval: Schema.optional(Schema.Number).annotate({ description: "Length of interval in seconds" }),
|
|
223
|
+
count: Schema.Number.annotate({ description: "The number of calls allowed within the interval" }),
|
|
224
|
+
startAt: Schema.optional(Schema.Number).annotate({ description: "The timestamp in seconds at which the rate limit starts. Before this signer cannot sign any UserOperations" })
|
|
225
|
+
});
|
|
226
|
+
const GasPolicyParams = Schema.Struct({
|
|
227
|
+
type: Schema.Literal("gas"),
|
|
228
|
+
amount: Schema.optional(BigIntFromString).annotate({ description: "Amount, in wei that the signer can spend on gas, in total across all UserOps it sends." }),
|
|
229
|
+
enforcePaymaster: Schema.optional(Schema.Boolean).annotate({
|
|
230
|
+
description: "If set to true, enforce that a paymaster must be used.",
|
|
231
|
+
default: false
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
const CallPolicyVersion$1 = Schema.Literals([
|
|
235
|
+
"0.0.1",
|
|
236
|
+
"0.0.2",
|
|
237
|
+
"0.0.3",
|
|
238
|
+
"0.0.4",
|
|
239
|
+
"0.0.5"
|
|
240
|
+
]);
|
|
241
|
+
const CallType$1 = Schema.Literals([
|
|
242
|
+
"call",
|
|
243
|
+
"delegatecall",
|
|
244
|
+
"batch-call"
|
|
245
|
+
]);
|
|
246
|
+
const ParamCondition$1 = Schema.Literals([
|
|
247
|
+
"EQUAL",
|
|
248
|
+
"GREATER_THAN",
|
|
249
|
+
"LESS_THAN",
|
|
250
|
+
"GREATER_THAN_OR_EQUAL",
|
|
251
|
+
"LESS_THAN_OR_EQUAL",
|
|
252
|
+
"NOT_EQUAL",
|
|
253
|
+
"ONE_OF",
|
|
254
|
+
"SLICE_EQUAL"
|
|
255
|
+
]);
|
|
256
|
+
const ConditionValue = Schema.Union([
|
|
257
|
+
Schema.Struct({
|
|
258
|
+
condition: ParamCondition$1.pick(["ONE_OF"]),
|
|
259
|
+
value: Schema.mutable(Schema.Array(Schema.Any)).annotate({ description: "The value of the argument to use with the operator." })
|
|
260
|
+
}),
|
|
261
|
+
Schema.Struct({
|
|
262
|
+
condition: ParamCondition$1.pick(["SLICE_EQUAL"]),
|
|
263
|
+
value: Schema.Any.annotate({ description: "The value of the argument to use with the operator." }),
|
|
264
|
+
start: Schema.Number,
|
|
265
|
+
length: Schema.Number
|
|
266
|
+
}),
|
|
267
|
+
Schema.Struct({
|
|
268
|
+
condition: ParamCondition$1.pick([
|
|
269
|
+
"EQUAL",
|
|
270
|
+
"GREATER_THAN",
|
|
271
|
+
"LESS_THAN",
|
|
272
|
+
"GREATER_THAN_OR_EQUAL",
|
|
273
|
+
"LESS_THAN_OR_EQUAL",
|
|
274
|
+
"NOT_EQUAL",
|
|
275
|
+
"SLICE_EQUAL"
|
|
276
|
+
]),
|
|
277
|
+
value: Schema.Any.annotate({ description: "The value of the argument to use with the operator." })
|
|
278
|
+
}),
|
|
279
|
+
Schema.Null
|
|
280
|
+
]);
|
|
281
|
+
const ParamRule = Schema.Struct({
|
|
282
|
+
condition: ParamCondition$1,
|
|
283
|
+
offset: Schema.Number,
|
|
284
|
+
params: Schema.Union([Hex, Schema.mutable(Schema.Array(Hex))])
|
|
285
|
+
});
|
|
286
|
+
const PermissionCore = Schema.Struct({
|
|
287
|
+
callType: Schema.optional(CallType$1).pipe(Schema.withDecodingDefault(() => "call")).annotate({
|
|
288
|
+
description: "The type of call to make",
|
|
289
|
+
default: "call"
|
|
290
|
+
}),
|
|
291
|
+
target: EthereumAddress.annotate({ description: "The target contract to call or address to send ETH to. If this is zeroAddress, then the target can be any contract as long as the ABI matches (or it can be any address if no ABI is specified)" }),
|
|
292
|
+
selector: Schema.optional(Hex).annotate({ description: "The function selector if the target is a contract" }),
|
|
293
|
+
valueLimit: Schema.optional(BigIntFromString).annotate({ description: "The maximum value in wei that can be sent to the target" }),
|
|
294
|
+
rules: Schema.optional(Schema.Array(ParamRule))
|
|
295
|
+
});
|
|
296
|
+
const PermissionManual = PermissionCore;
|
|
297
|
+
const PermissionWithABI = PermissionCore.mapFields(Struct.omit(["rules"])).mapFields(Struct.assign({
|
|
298
|
+
abi: Schema.mutable(Schema.Array(Schema.Any)).annotate({ description: "The ABI of the target contract" }),
|
|
299
|
+
functionName: Schema.String.annotate({ description: "The function name" }),
|
|
300
|
+
args: Schema.optional(Schema.Array(ConditionValue)).annotate({ description: "An array of conditions, each corresponding to an argument, in the order that the arguments are laid out. use null to skip an argument." })
|
|
301
|
+
}));
|
|
302
|
+
const Permission = Schema.Union([PermissionManual, PermissionWithABI]);
|
|
303
|
+
const CallPolicyParams = Schema.Struct({
|
|
304
|
+
type: Schema.Literal("call"),
|
|
305
|
+
policyVersion: CallPolicyVersion$1,
|
|
306
|
+
permissions: Schema.optional(Schema.Array(Permission))
|
|
307
|
+
});
|
|
308
|
+
const PolicyParams = Schema.Union([
|
|
309
|
+
SudoPolicyParams,
|
|
310
|
+
TimestampPolicyParams,
|
|
311
|
+
SignatureCallerPolicyParams,
|
|
312
|
+
RateLimitPolicyParams,
|
|
313
|
+
GasPolicyParams,
|
|
314
|
+
CallPolicyParams
|
|
315
|
+
]);
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/dto/session-key.ts
|
|
318
|
+
const BaseSessionKey = Schema.Struct({
|
|
319
|
+
smartAccountAlias: Schema.String,
|
|
320
|
+
serializedAccounts: Schema.Array(Schema.Struct({
|
|
321
|
+
chain: SupportedChain,
|
|
322
|
+
serializedAccount: Schema.String
|
|
323
|
+
}))
|
|
324
|
+
}).mapFields(Struct.assign(Keystore.fields));
|
|
325
|
+
const EcdsaSessionKey = Schema.Struct({
|
|
326
|
+
sessionKeyType: Schema.Literal("ecdsa"),
|
|
327
|
+
sessionKeyAddress: EthereumAddress,
|
|
328
|
+
smartAccountAlias: Schema.String
|
|
329
|
+
}).mapFields(Struct.assign(BaseSessionKey.fields));
|
|
330
|
+
const PasskeySessionKey = Schema.Struct({
|
|
331
|
+
sessionKeyType: Schema.Literal("passkey"),
|
|
332
|
+
passKeyName: Schema.String
|
|
333
|
+
}).mapFields(Struct.assign(BaseSessionKey.fields));
|
|
334
|
+
const SessionKey = Schema.Union([EcdsaSessionKey, PasskeySessionKey]);
|
|
335
|
+
Schema.Struct({
|
|
336
|
+
alias: Schema.String,
|
|
337
|
+
data: SessionKey,
|
|
338
|
+
path: Schema.String
|
|
339
|
+
});
|
|
340
|
+
const CreateSessionKeyParams = Schema.Struct({
|
|
341
|
+
alias: Schema.String.annotate({ description: "The alias of the session key to create" }),
|
|
342
|
+
chains: Schema.mutable(Schema.Array(SupportedChain)).annotate({ description: "The chains to create the session key for" }),
|
|
343
|
+
policyParams: Schema.mutable(Schema.Array(PolicyParams)),
|
|
344
|
+
smartAccountAlias: Schema.String.annotate({ description: "The alias of the smart account to create the session key for" }),
|
|
345
|
+
ownerKeystorePassword: Schema.redact(Schema.String).annotate({ description: "The password to decrypt the owner keystore with" }),
|
|
346
|
+
sessionKeyPassword: Schema.redact(Schema.String).annotate({ description: "The password to encrypt the session key with" })
|
|
347
|
+
});
|
|
348
|
+
Schema.Struct({ alias: Schema.String.annotate({ description: "The alias of the session key to retrieve" }) });
|
|
349
|
+
const ListSessionKeysParams = Schema.Struct({ smartAccount: Schema.optional(Schema.String).annotate({ description: "The alias of the smart account to list session keys for" }) });
|
|
350
|
+
const GetSessionKeyInfoParams = Schema.Struct({ alias: Schema.String.annotate({ description: "The alias of the session key to retrieve" }) });
|
|
351
|
+
const GetSessionKeyStatusParams = Schema.Struct({
|
|
352
|
+
alias: Schema.String.annotate({ description: "The alias of the session key to retrieve" }),
|
|
353
|
+
chain: SupportedChain.annotate({ description: "The chain to retrieve the session key status for" }),
|
|
354
|
+
rpcUrl: Schema.redact(Schema.String.pipe(Schema.optional)).annotate({
|
|
355
|
+
description: "The RPC URL to use for the chain",
|
|
356
|
+
default: "Public RPC URL"
|
|
357
|
+
})
|
|
358
|
+
});
|
|
359
|
+
const RemoveSessionKeyParams = Schema.Struct({ alias: Schema.String.annotate({ description: "The alias of the session key to remove" }) });
|
|
360
|
+
//#endregion
|
|
361
|
+
//#region src/dto/smart-account.ts
|
|
362
|
+
const LocalSmartAccount = Schema.Struct({
|
|
363
|
+
entryPointVersion: EntrypointVersion,
|
|
364
|
+
index: BigIntFromString,
|
|
365
|
+
kernelVersion: KernelVersion,
|
|
366
|
+
ownerAlias: Schema.String,
|
|
367
|
+
ownerType: OwnerType,
|
|
368
|
+
smartAccountAddress: EthereumAddress
|
|
369
|
+
});
|
|
370
|
+
Schema.Struct({
|
|
371
|
+
data: LocalSmartAccount,
|
|
372
|
+
path: Schema.String,
|
|
373
|
+
alias: Schema.String
|
|
374
|
+
});
|
|
375
|
+
const CreateSmartAccountParams = Schema.Struct({
|
|
376
|
+
alias: Schema.String.annotate({ description: "The alias of the smart account to create" }),
|
|
377
|
+
ownerAlias: Schema.String.annotate({ description: "The alias of the owner keystore" }),
|
|
378
|
+
ownerPassword: Schema.redact(Schema.String).annotate({ description: "The password of the owner keystore" }),
|
|
379
|
+
index: Schema.optional(BigIntFromString.check(Schema.makeFilter((v) => v >= 0n))).annotate({
|
|
380
|
+
description: "The index of the smart account",
|
|
381
|
+
default: 0n
|
|
382
|
+
})
|
|
383
|
+
});
|
|
384
|
+
Schema.Struct({ alias: Schema.String.annotate({ description: "The alias of the smart account to retrieve" }) });
|
|
385
|
+
const ListSmartAccountParams = Schema.Void;
|
|
386
|
+
const GetSmartAccountInfoParams = Schema.Struct({ alias: Schema.String.annotate({ description: "The alias of the smart account to retrieve" }) });
|
|
387
|
+
const RemoveSmartAccountParams = Schema.Struct({ alias: Schema.String.annotate({ description: "The alias of the smart account to remove" }) });
|
|
388
|
+
const GetSmartAccountStatusParams = Schema.Struct({
|
|
389
|
+
alias: Schema.String.annotate({ description: "The alias of the smart account" }),
|
|
390
|
+
chain: SupportedChain.annotate({ description: "The chain to get the status for" }),
|
|
391
|
+
rpcUrl: Schema.redact(Schema.String.pipe(Schema.optional)).annotate({
|
|
392
|
+
description: "The RPC URL to use for the chain",
|
|
393
|
+
default: "Public RPC URL"
|
|
394
|
+
})
|
|
395
|
+
});
|
|
396
|
+
const ImportSmartAccountParams = LocalSmartAccount.pipe(Schema.fieldsAssign({ alias: Schema.String.annotate({ description: "The alias of the smart account to import" }) }));
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/flags/global.ts
|
|
399
|
+
const globalOutput = GlobalFlag.setting("output")({ flag: Flag.choice("output", [
|
|
400
|
+
"pretty",
|
|
401
|
+
"json",
|
|
402
|
+
"ndjson"
|
|
403
|
+
]).pipe(Flag.withAlias("o"), Flag.withDefault("pretty"), Flag.withDescription("Output format (pretty, json, ndjson)")) });
|
|
404
|
+
const globalQuite = GlobalFlag.setting("quite")({ flag: Flag.boolean("quite").pipe(Flag.withAlias("q"), Flag.withDefault(false), Flag.withDescription("Do not print output")) });
|
|
405
|
+
const globalParams = GlobalFlag.setting("params")({ flag: Flag.string("params").pipe(Flag.optional, Flag.withDescription("JSON Parameters to pass to the command")) });
|
|
406
|
+
const getGlobalFlags = () => Effect.gen(function* () {
|
|
407
|
+
const out = yield* globalOutput;
|
|
408
|
+
const quite = yield* globalQuite;
|
|
409
|
+
return {
|
|
410
|
+
out,
|
|
411
|
+
params: yield* globalParams,
|
|
412
|
+
quite
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
const globalFlags = [
|
|
416
|
+
globalOutput,
|
|
417
|
+
globalQuite,
|
|
418
|
+
globalParams
|
|
419
|
+
];
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region src/types/index.ts
|
|
422
|
+
const entityName = {
|
|
423
|
+
keystore: "Keystore",
|
|
424
|
+
"session-key": "Session Key",
|
|
425
|
+
"smart-account": "Smart Account"
|
|
426
|
+
};
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/layers/config.ts
|
|
429
|
+
/**
|
|
430
|
+
* Domain error for configuration and entity storage operations.
|
|
431
|
+
*/
|
|
432
|
+
var ConfigManagerError = class extends Data.TaggedError("@namera-ai/cli/ConfigManagerError") {};
|
|
433
|
+
/**
|
|
434
|
+
* Service tag for resolving {@link ConfigManager} from the Effect context.
|
|
435
|
+
*/
|
|
436
|
+
const ConfigManager = ServiceMap.Service("@namera-ai/cli/ConfigManager");
|
|
437
|
+
/**
|
|
438
|
+
* Live layer that persists CLI entities in the user's config directory.
|
|
439
|
+
*/
|
|
440
|
+
const layer$7 = Layer.effect(ConfigManager, Effect.gen(function* () {
|
|
441
|
+
const fs = yield* FileSystem.FileSystem;
|
|
442
|
+
const path = yield* Path.Path;
|
|
443
|
+
const getConfigDirPath = () => Effect.gen(function* () {
|
|
444
|
+
const homeDir = yield* Effect.sync(() => os.homedir());
|
|
445
|
+
return path.join(homeDir, ".namera");
|
|
446
|
+
});
|
|
447
|
+
const ensureConfigDirExists = () => Effect.gen(function* () {
|
|
448
|
+
const baseDir = yield* getConfigDirPath();
|
|
449
|
+
const directoriesToCreate = [
|
|
450
|
+
"smart-accounts",
|
|
451
|
+
"session-keys",
|
|
452
|
+
"keystores"
|
|
453
|
+
].map((dir) => path.join(baseDir, dir));
|
|
454
|
+
yield* Effect.forEach(directoriesToCreate, (dirPath) => fs.makeDirectory(dirPath, { recursive: true }).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
455
|
+
code: "InitializationError",
|
|
456
|
+
message: e.message
|
|
457
|
+
})))), { concurrency: "unbounded" });
|
|
458
|
+
});
|
|
459
|
+
const getEntityPath = (entity) => Effect.gen(function* () {
|
|
460
|
+
const baseDir = yield* getConfigDirPath();
|
|
461
|
+
return path.join(baseDir, `${entity.type}s`, entity.alias);
|
|
462
|
+
});
|
|
463
|
+
const checkEntityExists = (entity) => Effect.gen(function* () {
|
|
464
|
+
const entityPath = yield* getEntityPath(entity);
|
|
465
|
+
return yield* fs.exists(entityPath).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
466
|
+
code: e.reason._tag,
|
|
467
|
+
message: e.message
|
|
468
|
+
}))));
|
|
469
|
+
});
|
|
470
|
+
const getEntity = (entity) => Effect.gen(function* () {
|
|
471
|
+
const entityPath = yield* getEntityPath(entity);
|
|
472
|
+
if (yield* checkEntityExists(entity)) {
|
|
473
|
+
const content = yield* fs.readFileString(entityPath);
|
|
474
|
+
return {
|
|
475
|
+
alias: entity.alias,
|
|
476
|
+
content,
|
|
477
|
+
path: entityPath,
|
|
478
|
+
type: entity.type
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return yield* Effect.fail(new ConfigManagerError({
|
|
482
|
+
code: "EntityNotFound",
|
|
483
|
+
message: `${entityName[entity.type]} with alias ${entity.alias} does not exist`
|
|
484
|
+
}));
|
|
485
|
+
}).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
486
|
+
code: e.reason._tag,
|
|
487
|
+
message: e.message
|
|
488
|
+
}))));
|
|
489
|
+
const getEntitiesForType = (type) => Effect.gen(function* () {
|
|
490
|
+
const baseDir = yield* getConfigDirPath();
|
|
491
|
+
const entitiesDir = path.join(baseDir, `${type}s`);
|
|
492
|
+
const effects = (yield* fs.readDirectory(entitiesDir)).map((entityName) => Effect.gen(function* () {
|
|
493
|
+
const entityPath = path.join(entitiesDir, entityName);
|
|
494
|
+
return {
|
|
495
|
+
alias: entityName,
|
|
496
|
+
content: yield* fs.readFileString(entityPath),
|
|
497
|
+
path: entityPath,
|
|
498
|
+
type
|
|
499
|
+
};
|
|
500
|
+
}));
|
|
501
|
+
return yield* Effect.all(effects, { concurrency: "unbounded" });
|
|
502
|
+
}).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
503
|
+
code: e.reason._tag,
|
|
504
|
+
message: e.message
|
|
505
|
+
}))));
|
|
506
|
+
const storeEntity = (entity) => Effect.gen(function* () {
|
|
507
|
+
const entityPath = yield* getEntityPath(entity);
|
|
508
|
+
if (yield* checkEntityExists({
|
|
509
|
+
alias: entity.alias,
|
|
510
|
+
type: entity.type
|
|
511
|
+
})) return yield* Effect.fail(new ConfigManagerError({
|
|
512
|
+
code: "EntityAlreadyExists",
|
|
513
|
+
message: `Entity ${entity.alias} already exists`
|
|
514
|
+
}));
|
|
515
|
+
yield* fs.writeFileString(entityPath, entity.content);
|
|
516
|
+
return {
|
|
517
|
+
alias: entity.alias,
|
|
518
|
+
content: entity.content,
|
|
519
|
+
path: entityPath,
|
|
520
|
+
type: entity.type
|
|
521
|
+
};
|
|
522
|
+
}).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
523
|
+
code: e.reason._tag,
|
|
524
|
+
message: e.message
|
|
525
|
+
}))));
|
|
526
|
+
const removeEntity = (entity) => Effect.gen(function* () {
|
|
527
|
+
const entityPath = yield* getEntityPath(entity);
|
|
528
|
+
yield* fs.remove(entityPath).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
529
|
+
code: e.reason._tag,
|
|
530
|
+
message: e.message
|
|
531
|
+
}))));
|
|
532
|
+
});
|
|
533
|
+
return ConfigManager.of({
|
|
534
|
+
checkEntityExists,
|
|
535
|
+
ensureConfigDirExists,
|
|
536
|
+
getConfigDirPath,
|
|
537
|
+
getEntitiesForType,
|
|
538
|
+
getEntity,
|
|
539
|
+
getEntityPath,
|
|
540
|
+
storeEntity,
|
|
541
|
+
removeEntity
|
|
542
|
+
});
|
|
543
|
+
}));
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/layers/prompt.ts
|
|
546
|
+
/**
|
|
547
|
+
* Service tag for resolving {@link PromptManager} from the Effect context.
|
|
548
|
+
*/
|
|
549
|
+
const PromptManager = ServiceMap.Service("@namera-ai/cli/PromptManager");
|
|
550
|
+
/**
|
|
551
|
+
* Live layer that validates and returns interactive prompt input.
|
|
552
|
+
*/
|
|
553
|
+
const layer$6 = Layer.effect(PromptManager, Effect.gen(function* () {
|
|
554
|
+
const configManager = yield* ConfigManager;
|
|
555
|
+
const aliasPrompt = (params) => Effect.gen(function* () {
|
|
556
|
+
return yield* Prompt.text({
|
|
557
|
+
message: params.message,
|
|
558
|
+
validate: (v) => Effect.gen(function* () {
|
|
559
|
+
if (v.trim() === "") return yield* Effect.fail("Alias cannot be empty");
|
|
560
|
+
const exists = yield* configManager.checkEntityExists({
|
|
561
|
+
alias: v,
|
|
562
|
+
type: params.type
|
|
563
|
+
}).pipe(Effect.catchTag("@namera-ai/cli/ConfigManagerError", (e) => Effect.fail(e.message)));
|
|
564
|
+
if (params.aliasType === "new" && exists) return yield* Effect.fail(`${entityName[params.type]} with alias ${v} already exists`);
|
|
565
|
+
if (params.aliasType === "existing" && !exists) return yield* Effect.fail(`${entityName[params.type]} with alias ${v} does not exist`);
|
|
566
|
+
return v;
|
|
567
|
+
})
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
const passwordPrompt = (params) => Effect.gen(function* () {
|
|
571
|
+
return yield* Prompt.password({
|
|
572
|
+
message: params.message,
|
|
573
|
+
validate: (v) => Effect.gen(function* () {
|
|
574
|
+
if (v.trim() === "") return yield* Effect.fail("Password cannot be empty");
|
|
575
|
+
if (params.validate) return yield* params.validate(v);
|
|
576
|
+
return v;
|
|
577
|
+
})
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
const selectPrompt = (params) => Effect.gen(function* () {
|
|
581
|
+
return yield* Prompt.select(params);
|
|
582
|
+
});
|
|
583
|
+
const multiSelectPrompt = (params) => Effect.gen(function* () {
|
|
584
|
+
return yield* Prompt.multiSelect(params);
|
|
585
|
+
});
|
|
586
|
+
function hexPrompt(params) {
|
|
587
|
+
return Effect.gen(function* () {
|
|
588
|
+
const validate = (v) => Effect.gen(function* () {
|
|
589
|
+
if (v.trim() === "") return yield* Effect.fail("Hex cannot be empty");
|
|
590
|
+
if (!isHex(v)) return yield* Effect.fail("Invalid hex value");
|
|
591
|
+
if (hexToBytes(v).length !== params.length) return yield* Effect.fail(`Hex value must be ${params.length} bytes`);
|
|
592
|
+
return v;
|
|
593
|
+
});
|
|
594
|
+
if (params.redacted) return yield* Prompt.password({
|
|
595
|
+
message: params.message,
|
|
596
|
+
validate
|
|
597
|
+
});
|
|
598
|
+
return yield* Prompt.text({
|
|
599
|
+
message: params.message,
|
|
600
|
+
validate
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return PromptManager.of({
|
|
605
|
+
aliasPrompt,
|
|
606
|
+
passwordPrompt,
|
|
607
|
+
selectPrompt,
|
|
608
|
+
hexPrompt,
|
|
609
|
+
multiSelectPrompt
|
|
610
|
+
});
|
|
611
|
+
}));
|
|
612
|
+
//#endregion
|
|
613
|
+
//#region src/layers/keystore.ts
|
|
614
|
+
/**
|
|
615
|
+
* Service tag for resolving {@link KeystoreManager} from the Effect context.
|
|
616
|
+
*/
|
|
617
|
+
const KeystoreManager = ServiceMap.Service("@namera-ai/cli/KeystoreManager");
|
|
618
|
+
/**
|
|
619
|
+
* Domain error for keystore management operations.
|
|
620
|
+
*/
|
|
621
|
+
var KeystoreManagerError = class extends Data.TaggedError("@namera-ai/cli/KeystoreManagerError") {};
|
|
622
|
+
/**
|
|
623
|
+
* Live layer wiring the keystore manager with configuration and prompts.
|
|
624
|
+
*/
|
|
625
|
+
const layer$5 = Layer.effect(KeystoreManager, Effect.gen(function* () {
|
|
626
|
+
const configManager = yield* ConfigManager;
|
|
627
|
+
const promptManager = yield* PromptManager;
|
|
628
|
+
const getKeystore = (params) => Effect.gen(function* () {
|
|
629
|
+
const res = yield* configManager.getEntity({
|
|
630
|
+
alias: params.alias,
|
|
631
|
+
type: "keystore"
|
|
632
|
+
});
|
|
633
|
+
const parsedKeystore = yield* Effect.try({
|
|
634
|
+
catch: () => new KeystoreManagerError({
|
|
635
|
+
code: "KeystoreParseError",
|
|
636
|
+
message: "Unable to parse keystore"
|
|
637
|
+
}),
|
|
638
|
+
try: () => JSON.parse(res.content)
|
|
639
|
+
});
|
|
640
|
+
return {
|
|
641
|
+
alias: res.alias,
|
|
642
|
+
data: {
|
|
643
|
+
...parsedKeystore,
|
|
644
|
+
address: `0x${parsedKeystore.address}`
|
|
645
|
+
},
|
|
646
|
+
path: res.path
|
|
647
|
+
};
|
|
648
|
+
});
|
|
649
|
+
const listKeystores = () => Effect.gen(function* () {
|
|
650
|
+
const effects = (yield* configManager.getEntitiesForType("keystore")).map((entity) => Effect.gen(function* () {
|
|
651
|
+
const parsedKeystore = yield* Effect.try({
|
|
652
|
+
catch: () => new KeystoreManagerError({
|
|
653
|
+
code: "KeystoreParseError",
|
|
654
|
+
message: "Unable to parse keystore"
|
|
655
|
+
}),
|
|
656
|
+
try: () => JSON.parse(entity.content)
|
|
657
|
+
});
|
|
658
|
+
return {
|
|
659
|
+
alias: entity.alias,
|
|
660
|
+
data: {
|
|
661
|
+
...parsedKeystore,
|
|
662
|
+
address: `0x${parsedKeystore.address}`
|
|
663
|
+
},
|
|
664
|
+
path: entity.path
|
|
665
|
+
};
|
|
666
|
+
}));
|
|
667
|
+
return yield* Effect.all(effects, { concurrency: "unbounded" });
|
|
668
|
+
});
|
|
669
|
+
const createKeystore = (params) => Effect.gen(function* () {
|
|
670
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
671
|
+
alias: params.alias,
|
|
672
|
+
type: "keystore"
|
|
673
|
+
});
|
|
674
|
+
if (yield* configManager.checkEntityExists({
|
|
675
|
+
alias: params.alias,
|
|
676
|
+
type: "keystore"
|
|
677
|
+
})) return yield* Effect.fail(new KeystoreManagerError({
|
|
678
|
+
code: "KeystoreAlreadyExists",
|
|
679
|
+
message: `Keystore with alias ${params.alias} already exists`
|
|
680
|
+
}));
|
|
681
|
+
const keystoreString = yield* Effect.tryPromise({
|
|
682
|
+
catch: () => new KeystoreManagerError({
|
|
683
|
+
code: "KeystoreCreationFailed",
|
|
684
|
+
message: "Failed to create keystore"
|
|
685
|
+
}),
|
|
686
|
+
try: () => Wallet.generate().toV3String(params.password)
|
|
687
|
+
});
|
|
688
|
+
yield* configManager.storeEntity({
|
|
689
|
+
alias: params.alias,
|
|
690
|
+
content: keystoreString,
|
|
691
|
+
path: entityPath,
|
|
692
|
+
type: "keystore"
|
|
693
|
+
});
|
|
694
|
+
const keystore = yield* Effect.try({
|
|
695
|
+
catch: () => new KeystoreManagerError({
|
|
696
|
+
code: "KeystoreParseError",
|
|
697
|
+
message: "Unable to parse keystore"
|
|
698
|
+
}),
|
|
699
|
+
try: () => JSON.parse(keystoreString)
|
|
700
|
+
});
|
|
701
|
+
return {
|
|
702
|
+
alias: params.alias,
|
|
703
|
+
data: {
|
|
704
|
+
...keystore,
|
|
705
|
+
address: `0x${keystore.address}`
|
|
706
|
+
},
|
|
707
|
+
path: entityPath
|
|
708
|
+
};
|
|
709
|
+
});
|
|
710
|
+
const decryptKeystore = (params) => Effect.gen(function* () {
|
|
711
|
+
const keystore = yield* getKeystore({ alias: params.alias });
|
|
712
|
+
const wallet = yield* Effect.tryPromise({
|
|
713
|
+
catch: () => new KeystoreManagerError({
|
|
714
|
+
code: "KeystoreDecryptionFailed",
|
|
715
|
+
message: "Failed to decrypt keystore"
|
|
716
|
+
}),
|
|
717
|
+
try: () => Wallet.fromV3(keystore.data, params.password)
|
|
718
|
+
});
|
|
719
|
+
return {
|
|
720
|
+
address: wallet.getChecksumAddressString(),
|
|
721
|
+
alias: params.alias,
|
|
722
|
+
privateKey: Redacted.make(wallet.getPrivateKeyString()),
|
|
723
|
+
publicKey: wallet.getPublicKeyString()
|
|
724
|
+
};
|
|
725
|
+
});
|
|
726
|
+
const selectKeystore = (params) => Effect.gen(function* () {
|
|
727
|
+
const keystores = yield* listKeystores();
|
|
728
|
+
return yield* promptManager.selectPrompt({
|
|
729
|
+
message: params.message,
|
|
730
|
+
choices: keystores.map((k) => ({
|
|
731
|
+
title: k.alias,
|
|
732
|
+
value: k,
|
|
733
|
+
description: k.data.address
|
|
734
|
+
}))
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
const importKeystore = (params) => Effect.gen(function* () {
|
|
738
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
739
|
+
alias: params.alias,
|
|
740
|
+
type: "keystore"
|
|
741
|
+
});
|
|
742
|
+
if (yield* configManager.checkEntityExists({
|
|
743
|
+
alias: params.alias,
|
|
744
|
+
type: "keystore"
|
|
745
|
+
})) return yield* Effect.fail(new KeystoreManagerError({
|
|
746
|
+
code: "KeystoreAlreadyExists",
|
|
747
|
+
message: `Keystore with alias ${params.alias} already exists`
|
|
748
|
+
}));
|
|
749
|
+
const keystoreString = yield* Effect.tryPromise({
|
|
750
|
+
catch: () => new KeystoreManagerError({
|
|
751
|
+
code: "KeystoreCreationFailed",
|
|
752
|
+
message: "Failed to create keystore"
|
|
753
|
+
}),
|
|
754
|
+
try: () => Wallet.fromPrivateKey(hexToBytes(params.privateKey)).toV3String(params.password)
|
|
755
|
+
});
|
|
756
|
+
yield* configManager.storeEntity({
|
|
757
|
+
alias: params.alias,
|
|
758
|
+
content: keystoreString,
|
|
759
|
+
path: entityPath,
|
|
760
|
+
type: "keystore"
|
|
761
|
+
});
|
|
762
|
+
const keystore = yield* Effect.try({
|
|
763
|
+
catch: () => new KeystoreManagerError({
|
|
764
|
+
code: "KeystoreParseError",
|
|
765
|
+
message: "Unable to parse keystore"
|
|
766
|
+
}),
|
|
767
|
+
try: () => JSON.parse(keystoreString)
|
|
768
|
+
});
|
|
769
|
+
return {
|
|
770
|
+
alias: params.alias,
|
|
771
|
+
data: keystore,
|
|
772
|
+
path: entityPath
|
|
773
|
+
};
|
|
774
|
+
});
|
|
775
|
+
const removeKeystore = (params) => Effect.gen(function* () {
|
|
776
|
+
if (!(yield* configManager.checkEntityExists({
|
|
777
|
+
alias: params.alias,
|
|
778
|
+
type: "keystore"
|
|
779
|
+
}))) return yield* Effect.fail(new KeystoreManagerError({
|
|
780
|
+
code: "KeystoreNotFound",
|
|
781
|
+
message: `Keystore with alias ${params.alias} does not exist`
|
|
782
|
+
}));
|
|
783
|
+
return yield* configManager.removeEntity({
|
|
784
|
+
alias: params.alias,
|
|
785
|
+
type: "keystore"
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
const getSigner = (params) => Effect.gen(function* () {
|
|
789
|
+
const keystore = yield* getKeystore({ alias: params.alias });
|
|
790
|
+
return privateKeyToAccount(toHex((yield* Effect.tryPromise({
|
|
791
|
+
catch: () => new KeystoreManagerError({
|
|
792
|
+
code: "KeystoreDecryptionFailed",
|
|
793
|
+
message: "Failed to decrypt keystore"
|
|
794
|
+
}),
|
|
795
|
+
try: () => Wallet.fromV3(keystore.data, params.password)
|
|
796
|
+
})).getPrivateKey()));
|
|
797
|
+
});
|
|
798
|
+
const getKeystorePassword = (params) => Effect.gen(function* () {
|
|
799
|
+
const keystore = yield* getKeystore({ alias: params.alias });
|
|
800
|
+
return yield* promptManager.passwordPrompt({
|
|
801
|
+
message: params.message,
|
|
802
|
+
validate: (v) => Effect.gen(function* () {
|
|
803
|
+
if (v.trim() === "") return yield* Effect.fail("Password cannot be empty");
|
|
804
|
+
yield* Effect.tryPromise({
|
|
805
|
+
try: () => Wallet.fromV3(keystore.data, v),
|
|
806
|
+
catch: () => new KeystoreManagerError({
|
|
807
|
+
code: "DecryptError",
|
|
808
|
+
message: `Invalid password for keystore ${params.alias}`
|
|
809
|
+
})
|
|
810
|
+
}).pipe(Effect.catch(() => Effect.fail("Invalid password")));
|
|
811
|
+
return v;
|
|
812
|
+
})
|
|
813
|
+
});
|
|
814
|
+
});
|
|
815
|
+
return KeystoreManager.of({
|
|
816
|
+
createKeystore,
|
|
817
|
+
decryptKeystore,
|
|
818
|
+
getKeystore,
|
|
819
|
+
listKeystores,
|
|
820
|
+
selectKeystore,
|
|
821
|
+
importKeystore,
|
|
822
|
+
removeKeystore,
|
|
823
|
+
getSigner,
|
|
824
|
+
getKeystorePassword
|
|
825
|
+
});
|
|
826
|
+
}));
|
|
827
|
+
//#endregion
|
|
828
|
+
//#region src/layers/mcp-context.ts
|
|
829
|
+
const McpContext = ServiceMap.Service("@namera-ai/cli/McpContext");
|
|
830
|
+
//#endregion
|
|
831
|
+
//#region src/mcp/helpers/common.ts
|
|
832
|
+
const EmptyArgs = Schema.Record(Schema.String, Schema.Unknown);
|
|
833
|
+
var InsufficientPermissions = class extends Schema.TaggedErrorClass()("InsufficientPermissions", {}) {};
|
|
834
|
+
//#endregion
|
|
835
|
+
//#region src/mcp/tools/account/address.ts
|
|
836
|
+
const GetAddressTool = Tool.make("get_wallet_address", {
|
|
837
|
+
dependencies: [McpContext],
|
|
838
|
+
description: "Get the address of the wallet",
|
|
839
|
+
failure: Schema.Never,
|
|
840
|
+
parameters: EmptyArgs,
|
|
841
|
+
success: Schema.String
|
|
842
|
+
});
|
|
843
|
+
const getAddressToolHandler = () => Effect.gen(function* () {
|
|
844
|
+
return (yield* McpContext).smartAccount.smartAccountAddress;
|
|
845
|
+
});
|
|
846
|
+
//#endregion
|
|
847
|
+
//#region src/layers/web3.ts
|
|
848
|
+
/**
|
|
849
|
+
* Service tag for resolving {@link Web3Service} from the Effect context.
|
|
850
|
+
*/
|
|
851
|
+
const Web3Service = ServiceMap.Service("@namera-ai/cli/Web3Service");
|
|
852
|
+
/**
|
|
853
|
+
* Live layer that wires web3 clients and chain selection prompts.
|
|
854
|
+
*/
|
|
855
|
+
const layer$4 = Layer.effect(Web3Service, Effect.gen(function* () {
|
|
856
|
+
const promptManager = yield* PromptManager;
|
|
857
|
+
const chainNameToEnvVar = (chain, suffix) => {
|
|
858
|
+
return `${chain.replaceAll("-", "_").toUpperCase()}_${suffix}`;
|
|
859
|
+
};
|
|
860
|
+
const getRpcUrl = (params) => Effect.gen(function* () {
|
|
861
|
+
let rpcUrl;
|
|
862
|
+
if (params.rpcUrl) rpcUrl = params.rpcUrl;
|
|
863
|
+
else {
|
|
864
|
+
const envVarName = chainNameToEnvVar(params.chain, "RPC_URL");
|
|
865
|
+
const envRpcUrl = yield* Config.option(Config.redacted(envVarName));
|
|
866
|
+
if (envRpcUrl._tag === "Some") rpcUrl = Redacted.value(envRpcUrl.value);
|
|
867
|
+
else rpcUrl = void 0;
|
|
868
|
+
}
|
|
869
|
+
return rpcUrl;
|
|
870
|
+
}).pipe(Effect.orDie);
|
|
871
|
+
const getBundlerUrl = (params) => Effect.gen(function* () {
|
|
872
|
+
let bundlerUrl;
|
|
873
|
+
if (params.bundlerUrl) bundlerUrl = params.bundlerUrl;
|
|
874
|
+
else {
|
|
875
|
+
const envVarName = chainNameToEnvVar(params.chain, "BUNDLER_URL");
|
|
876
|
+
const envRpcUrl = yield* Config.option(Config.redacted(envVarName));
|
|
877
|
+
if (envRpcUrl._tag === "Some") bundlerUrl = Redacted.value(envRpcUrl.value);
|
|
878
|
+
else bundlerUrl = `https://public.pimlico.io/v2/${getChain(params.chain).id}/rpc`;
|
|
879
|
+
}
|
|
880
|
+
return bundlerUrl;
|
|
881
|
+
}).pipe(Effect.orDie);
|
|
882
|
+
const getPublicClient = (params) => Effect.gen(function* () {
|
|
883
|
+
const rpcUrl = yield* getRpcUrl(params);
|
|
884
|
+
return createPublicClient({
|
|
885
|
+
chain: getChain(params.chain),
|
|
886
|
+
transport: http(rpcUrl)
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
const getBundlerTransport = (params) => Effect.gen(function* () {
|
|
890
|
+
return http(yield* getBundlerUrl(params));
|
|
891
|
+
});
|
|
892
|
+
const selectChain = (params) => Effect.gen(function* () {
|
|
893
|
+
const chains = Object.values(supportedChains);
|
|
894
|
+
return yield* promptManager.selectPrompt({
|
|
895
|
+
message: params.message,
|
|
896
|
+
choices: chains.map((c) => ({
|
|
897
|
+
title: c.name,
|
|
898
|
+
value: c.key
|
|
899
|
+
}))
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
const multiSelectChain = (params) => Effect.gen(function* () {
|
|
903
|
+
const chains = Object.values(supportedChains);
|
|
904
|
+
return yield* promptManager.multiSelectPrompt({
|
|
905
|
+
message: params.message,
|
|
906
|
+
choices: chains.map((c) => ({
|
|
907
|
+
title: c.name,
|
|
908
|
+
value: c.key
|
|
909
|
+
})),
|
|
910
|
+
min: 1
|
|
911
|
+
});
|
|
912
|
+
});
|
|
913
|
+
return Web3Service.of({
|
|
914
|
+
getPublicClient,
|
|
915
|
+
selectChain,
|
|
916
|
+
multiSelectChain,
|
|
917
|
+
getBundlerTransport
|
|
918
|
+
});
|
|
919
|
+
}));
|
|
920
|
+
//#endregion
|
|
921
|
+
//#region src/mcp/helpers/session-key.ts
|
|
922
|
+
const toPolicyMap = (policies) => {
|
|
923
|
+
const map = {};
|
|
924
|
+
for (const p of policies) {
|
|
925
|
+
if (p.type === "timestamp") {
|
|
926
|
+
if (map.timestamp) return null;
|
|
927
|
+
map.timestamp = p;
|
|
928
|
+
}
|
|
929
|
+
if (p.type === "call") {
|
|
930
|
+
if (map.call) return null;
|
|
931
|
+
map.call = p;
|
|
932
|
+
}
|
|
933
|
+
if (p.type === "signature-caller") {
|
|
934
|
+
if (map.signatureCaller) return null;
|
|
935
|
+
map.signatureCaller = p;
|
|
936
|
+
}
|
|
937
|
+
if (p.type === "gas") {
|
|
938
|
+
if (map.gas) return null;
|
|
939
|
+
map.gas = p;
|
|
940
|
+
}
|
|
941
|
+
if (p.type === "rate-limit") {
|
|
942
|
+
if (map.rateLimit) return null;
|
|
943
|
+
map.rateLimit = p;
|
|
944
|
+
}
|
|
945
|
+
if (p.type === "sudo") {
|
|
946
|
+
if (map.sudo) return null;
|
|
947
|
+
map.sudo = p;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return map;
|
|
951
|
+
};
|
|
952
|
+
function evaluateTimestamp(policy, _chainIds, _operation) {
|
|
953
|
+
const now = Date.now();
|
|
954
|
+
if (policy.validAfter !== void 0 && now < policy.validAfter * 1e3) return false;
|
|
955
|
+
if (policy.validUntil !== void 0 && now >= policy.validUntil * 1e3) return false;
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
function evaluateSignature(policy, chainIds, operation) {
|
|
959
|
+
const callers = operation.allowedCallers;
|
|
960
|
+
if (!callers || callers.length === 0) return false;
|
|
961
|
+
if (!chainIds.includes(operation.chainId)) return false;
|
|
962
|
+
return callers.every((caller) => policy.allowedCallers.includes(caller));
|
|
963
|
+
}
|
|
964
|
+
function evaluateCall(policy, chainIds, operation) {
|
|
965
|
+
const permissions = policy.permissions ?? [];
|
|
966
|
+
if (permissions.length === 0) return false;
|
|
967
|
+
const { batches } = operation;
|
|
968
|
+
if (batches.length === 0) return false;
|
|
969
|
+
return batches.every((batch) => {
|
|
970
|
+
const { calls, chainId } = batch;
|
|
971
|
+
return calls.every((call) => {
|
|
972
|
+
const { to, value = 0n, data } = call;
|
|
973
|
+
if (!chainIds.includes(chainId)) return false;
|
|
974
|
+
return permissions.some((p) => {
|
|
975
|
+
if (p.target !== to) return false;
|
|
976
|
+
if (value > (p.valueLimit ?? 0n)) return false;
|
|
977
|
+
const isContractCall = data.length >= 10;
|
|
978
|
+
if ("functionName" in p) {
|
|
979
|
+
if (!isContractCall) return false;
|
|
980
|
+
if (!p.selector) return false;
|
|
981
|
+
if (data.slice(0, 10) !== p.selector) return false;
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
return true;
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
const evaluateSessionKey = (sessionKey, operation) => {
|
|
990
|
+
if (operation.intent === "read") return true;
|
|
991
|
+
const accountParams = deserializePermissionAccountParams(sessionKey.data.serializedAccounts[0]?.serializedAccount ?? "");
|
|
992
|
+
const chainIds = sessionKey.data.serializedAccounts.map((a) => getChain(a.chain).id);
|
|
993
|
+
const p = toPolicyMap((accountParams.permissionParams.policies ?? []).map((x) => x.policyParams));
|
|
994
|
+
if (!p) return false;
|
|
995
|
+
const hasSudo = Boolean(p.sudo);
|
|
996
|
+
if (hasSudo && (p.call || p.signatureCaller)) return false;
|
|
997
|
+
if (p.timestamp && !evaluateTimestamp(p.timestamp, chainIds, operation)) return false;
|
|
998
|
+
if (!hasSudo) {
|
|
999
|
+
if (operation.intent === "transaction") {
|
|
1000
|
+
if (!p.call) return false;
|
|
1001
|
+
if (!evaluateCall(p.call, chainIds, operation)) return false;
|
|
1002
|
+
}
|
|
1003
|
+
if (operation.intent === "sign") {
|
|
1004
|
+
if (!p.signatureCaller) return false;
|
|
1005
|
+
if (!evaluateSignature(p.signatureCaller, chainIds, operation)) return false;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return true;
|
|
1009
|
+
};
|
|
1010
|
+
const getSessionKeyClient = (params) => Effect.gen(function* () {
|
|
1011
|
+
const { sessionKeys, smartAccount } = yield* McpContext;
|
|
1012
|
+
const key = sessionKeys.filter((sk) => {
|
|
1013
|
+
return evaluateSessionKey(sk, params.operation);
|
|
1014
|
+
})[0];
|
|
1015
|
+
if (!key) return yield* Effect.fail(new InsufficientPermissions());
|
|
1016
|
+
const web3Service = yield* Web3Service;
|
|
1017
|
+
let chainId;
|
|
1018
|
+
if (params.operation.intent === "read") chainId = params.operation.chainId;
|
|
1019
|
+
else if (params.operation.intent === "sign") chainId = params.operation.chainId;
|
|
1020
|
+
else chainId = params.operation.batches[0]?.chainId ?? 1;
|
|
1021
|
+
const chainName = chainIdToChainName(chainId);
|
|
1022
|
+
const chain = getChain(chainName);
|
|
1023
|
+
const publicClient = yield* web3Service.getPublicClient({ chain: chainName });
|
|
1024
|
+
const bundlerTransport = yield* web3Service.getBundlerTransport({ chain: chainName });
|
|
1025
|
+
const serializedAccount = key.data.serializedAccounts.find((a) => a.chain === chainName)?.serializedAccount ?? "";
|
|
1026
|
+
return yield* Effect.promise(() => createSessionKeyClient({
|
|
1027
|
+
type: "ecdsa",
|
|
1028
|
+
bundlerTransport,
|
|
1029
|
+
chain,
|
|
1030
|
+
client: publicClient,
|
|
1031
|
+
entrypointVersion: smartAccount.entryPointVersion,
|
|
1032
|
+
kernelVersion: smartAccount.kernelVersion,
|
|
1033
|
+
serializedAccount,
|
|
1034
|
+
sessionKeySigner: key.signer
|
|
1035
|
+
}));
|
|
1036
|
+
});
|
|
1037
|
+
//#endregion
|
|
1038
|
+
//#region src/mcp/tools/account/get-balance.ts
|
|
1039
|
+
const GetBalanceToolParams = Schema.Struct({
|
|
1040
|
+
address: EthereumAddress.annotate({ description: "The address to get the balance for" }),
|
|
1041
|
+
chain: SupportedChain.annotate({ description: "The chain to get the balance for" })
|
|
1042
|
+
});
|
|
1043
|
+
const GetBalanceToolResult = Schema.Struct({
|
|
1044
|
+
nativeCurrency: Schema.Struct({
|
|
1045
|
+
name: Schema.String.annotate({ description: "The name of the currency" }),
|
|
1046
|
+
symbol: Schema.String.annotate({ description: "The symbol of the currency" }),
|
|
1047
|
+
decimals: Schema.Number.annotate({ description: "The number of decimals the currency has" })
|
|
1048
|
+
}).annotate({ description: "The native currency of the chain" }),
|
|
1049
|
+
balance: Schema.Struct({
|
|
1050
|
+
amount: Schema.String.annotate({ description: "Balance in atomic units" }),
|
|
1051
|
+
formatted: Schema.String.annotate({ description: "Balance in native currency units" })
|
|
1052
|
+
})
|
|
1053
|
+
});
|
|
1054
|
+
const GetBalanceTool = Tool.make("get_balance", {
|
|
1055
|
+
dependencies: [McpContext, Web3Service],
|
|
1056
|
+
description: "Get the address of the wallet",
|
|
1057
|
+
failure: InsufficientPermissions,
|
|
1058
|
+
parameters: GetBalanceToolParams,
|
|
1059
|
+
success: GetBalanceToolResult
|
|
1060
|
+
});
|
|
1061
|
+
const getBalanceToolHandler = (params) => Effect.gen(function* () {
|
|
1062
|
+
const sessionKeyClient = yield* getSessionKeyClient({ operation: {
|
|
1063
|
+
intent: "read",
|
|
1064
|
+
chainId: getChain(params.chain).id
|
|
1065
|
+
} });
|
|
1066
|
+
const res = yield* Effect.promise(() => getBalance(sessionKeyClient.client, { address: params.address }));
|
|
1067
|
+
return {
|
|
1068
|
+
nativeCurrency: sessionKeyClient.chain.nativeCurrency,
|
|
1069
|
+
balance: {
|
|
1070
|
+
amount: res.toString(),
|
|
1071
|
+
formatted: formatUnits(res, sessionKeyClient.chain.nativeCurrency.decimals)
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
});
|
|
1075
|
+
//#endregion
|
|
1076
|
+
//#region src/mcp/tools/account/index.ts
|
|
1077
|
+
const AccountTools = Toolkit.make(GetAddressTool, GetBalanceTool);
|
|
1078
|
+
const AccountToolsHandlers = AccountTools.toLayer(Effect.succeed({
|
|
1079
|
+
get_wallet_address: getAddressToolHandler,
|
|
1080
|
+
get_balance: getBalanceToolHandler
|
|
1081
|
+
}));
|
|
1082
|
+
//#endregion
|
|
1083
|
+
//#region src/mcp/tools/read/read-contract.ts
|
|
1084
|
+
const ReadContractToolParams = Schema.Struct({
|
|
1085
|
+
chain: SupportedChain.annotate({ description: "The chain to use for the transfer" }),
|
|
1086
|
+
contractAddress: EthereumAddress.annotate({ description: "The ethereum address of the contract to read" }),
|
|
1087
|
+
abi: Schema.Array(Schema.Any).annotate({ description: "The ABI of the contract to read" }),
|
|
1088
|
+
functionName: Schema.String.annotate({ description: "The name of the function to call on the contract" }),
|
|
1089
|
+
args: Schema.Array(Schema.Any).annotate({ description: "The arguments to pass to the function" })
|
|
1090
|
+
});
|
|
1091
|
+
const ReadContractTool = Tool.make("read_contract", {
|
|
1092
|
+
dependencies: [McpContext, Web3Service],
|
|
1093
|
+
description: "Read data from a specified contract.",
|
|
1094
|
+
failure: InsufficientPermissions,
|
|
1095
|
+
parameters: ReadContractToolParams,
|
|
1096
|
+
success: Schema.Any.annotate({ description: "The data returned by the contract" })
|
|
1097
|
+
});
|
|
1098
|
+
const readContractToolHandler = (params) => Effect.gen(function* () {
|
|
1099
|
+
const client = yield* getSessionKeyClient({ operation: {
|
|
1100
|
+
intent: "read",
|
|
1101
|
+
chainId: getChain(params.chain).id
|
|
1102
|
+
} });
|
|
1103
|
+
readContract(client, {
|
|
1104
|
+
address: params.contractAddress,
|
|
1105
|
+
abi: params.abi,
|
|
1106
|
+
functionName: params.functionName,
|
|
1107
|
+
args: params.args
|
|
1108
|
+
});
|
|
1109
|
+
return yield* Effect.promise(() => readContract(client, {
|
|
1110
|
+
address: params.contractAddress,
|
|
1111
|
+
abi: params.abi,
|
|
1112
|
+
functionName: params.functionName,
|
|
1113
|
+
args: params.args
|
|
1114
|
+
}));
|
|
1115
|
+
});
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/mcp/tools/read/index.ts
|
|
1118
|
+
const ReadTools = Toolkit.make(ReadContractTool);
|
|
1119
|
+
const ReadToolsHandlers = ReadTools.toLayer(Effect.succeed({ read_contract: (params) => readContractToolHandler(params) }));
|
|
1120
|
+
//#endregion
|
|
1121
|
+
//#region src/schema/tx.ts
|
|
1122
|
+
const Call = Schema.Struct({
|
|
1123
|
+
to: EthereumAddress.annotate({ description: "The target address to call" }),
|
|
1124
|
+
data: Hex.annotate({ description: "The data to send with the call" }),
|
|
1125
|
+
value: Schema.optional(BigIntFromString).annotate({ description: "The value to send with the call" })
|
|
1126
|
+
});
|
|
1127
|
+
const Batch = Schema.Struct({
|
|
1128
|
+
chainId: Schema.Int.annotate({ description: "The chain ID to execute the transaction on" }),
|
|
1129
|
+
nonceKey: Schema.optional(Schema.String).annotate({ description: "The nonce key to use, for 2D Parallel transactions." }),
|
|
1130
|
+
calls: Schema.mutable(Schema.Array(Call))
|
|
1131
|
+
});
|
|
1132
|
+
Schema.Struct({ batches: Schema.mutable(Schema.Array(Batch)) });
|
|
1133
|
+
//#endregion
|
|
1134
|
+
//#region src/mcp/tools/transaction/execute-transaction.ts
|
|
1135
|
+
const ExecuteTransactionToolParams = Batch;
|
|
1136
|
+
const ExecuteTransactionTool = Tool.make("execute_transaction", {
|
|
1137
|
+
dependencies: [McpContext, Web3Service],
|
|
1138
|
+
description: "Send transactions.",
|
|
1139
|
+
failure: InsufficientPermissions,
|
|
1140
|
+
parameters: ExecuteTransactionToolParams,
|
|
1141
|
+
success: Schema.String.annotate({ description: "The transaction hash for the executed transaction." })
|
|
1142
|
+
});
|
|
1143
|
+
const executeTransactionToolHandler = (params) => Effect.gen(function* () {
|
|
1144
|
+
const batches = [{
|
|
1145
|
+
chainId: params.chainId,
|
|
1146
|
+
nonceKey: params.nonceKey,
|
|
1147
|
+
calls: params.calls
|
|
1148
|
+
}];
|
|
1149
|
+
const client = yield* getSessionKeyClient({ operation: {
|
|
1150
|
+
intent: "transaction",
|
|
1151
|
+
batches
|
|
1152
|
+
} });
|
|
1153
|
+
return (yield* Effect.promise(() => executeTransaction({
|
|
1154
|
+
clients: [client],
|
|
1155
|
+
batches
|
|
1156
|
+
})))[0]?.receipt.transactionHash;
|
|
1157
|
+
});
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/mcp/tools/transaction/native-transfer.ts
|
|
1160
|
+
const NativeTransferToolParams = Schema.Struct({
|
|
1161
|
+
chain: SupportedChain.annotate({ description: "The chain to use for the transfer" }),
|
|
1162
|
+
address: EthereumAddress.annotate({ description: "The ethereum address to transfer to" }),
|
|
1163
|
+
amount: Schema.String.annotate({ description: "The amount of native tokens to transfer" }),
|
|
1164
|
+
unit: Schema.Literals([
|
|
1165
|
+
"wei",
|
|
1166
|
+
"gwei",
|
|
1167
|
+
"ether"
|
|
1168
|
+
])
|
|
1169
|
+
});
|
|
1170
|
+
const NativeTransferTool = Tool.make("native_transfer", {
|
|
1171
|
+
dependencies: [McpContext, Web3Service],
|
|
1172
|
+
description: "Transfer Native Tokens to a specified address.",
|
|
1173
|
+
failure: InsufficientPermissions,
|
|
1174
|
+
parameters: NativeTransferToolParams,
|
|
1175
|
+
success: Schema.String.annotate({ description: "The transaction hash of the transfer" })
|
|
1176
|
+
});
|
|
1177
|
+
const nativeTransferToolHandler = (params) => Effect.gen(function* () {
|
|
1178
|
+
const decimals = () => {
|
|
1179
|
+
if (params.unit === "wei") return 1;
|
|
1180
|
+
if (params.unit === "gwei") return 9;
|
|
1181
|
+
return 18;
|
|
1182
|
+
};
|
|
1183
|
+
const amount = parseUnits(params.amount.toString(), decimals());
|
|
1184
|
+
const batches = [{
|
|
1185
|
+
chainId: getChain(params.chain).id,
|
|
1186
|
+
calls: [{
|
|
1187
|
+
to: params.address,
|
|
1188
|
+
value: amount,
|
|
1189
|
+
data: "0x"
|
|
1190
|
+
}]
|
|
1191
|
+
}];
|
|
1192
|
+
const client = yield* getSessionKeyClient({ operation: {
|
|
1193
|
+
intent: "transaction",
|
|
1194
|
+
batches
|
|
1195
|
+
} });
|
|
1196
|
+
return (yield* Effect.promise(() => executeTransaction({
|
|
1197
|
+
clients: [client],
|
|
1198
|
+
batches
|
|
1199
|
+
})))[0]?.receipt.transactionHash;
|
|
1200
|
+
});
|
|
1201
|
+
//#endregion
|
|
1202
|
+
//#region src/mcp/tools/transaction/index.ts
|
|
1203
|
+
const TransactionTools = Toolkit.make(NativeTransferTool, ExecuteTransactionTool);
|
|
1204
|
+
const TransactionToolsHandlers = TransactionTools.toLayer(Effect.succeed({
|
|
1205
|
+
native_transfer: (params) => nativeTransferToolHandler(params),
|
|
1206
|
+
execute_transaction: (params) => executeTransactionToolHandler(params)
|
|
1207
|
+
}));
|
|
1208
|
+
//#endregion
|
|
1209
|
+
//#region src/mcp/mcp.ts
|
|
1210
|
+
const Account = Layer.effectDiscard(McpServer.registerToolkit(AccountTools)).pipe(Layer.provideMerge(AccountToolsHandlers));
|
|
1211
|
+
const Transfer = Layer.effectDiscard(McpServer.registerToolkit(TransactionTools)).pipe(Layer.provideMerge(TransactionToolsHandlers));
|
|
1212
|
+
const Read = Layer.effectDiscard(McpServer.registerToolkit(ReadTools)).pipe(Layer.provideMerge(ReadToolsHandlers));
|
|
1213
|
+
const McpLive = Layer.mergeAll(Account, Transfer, Read);
|
|
1214
|
+
//#endregion
|
|
1215
|
+
//#region src/mcp/index.ts
|
|
1216
|
+
const MCP_SERVER_NAME = "Namera MCP Server";
|
|
1217
|
+
const MCP_SERVER_VERSION = "0.0.1";
|
|
1218
|
+
const McpRouter = McpLive.pipe(Layer.provideMerge(McpServer.layerHttp({
|
|
1219
|
+
name: MCP_SERVER_NAME,
|
|
1220
|
+
path: "/mcp",
|
|
1221
|
+
version: MCP_SERVER_VERSION
|
|
1222
|
+
})), Layer.provideMerge(HttpRouter.cors({
|
|
1223
|
+
allowedHeaders: [
|
|
1224
|
+
"Content-Type",
|
|
1225
|
+
"Authorization",
|
|
1226
|
+
"mcp-session-id",
|
|
1227
|
+
"mcp-protocol-version"
|
|
1228
|
+
],
|
|
1229
|
+
allowedMethods: [
|
|
1230
|
+
"GET",
|
|
1231
|
+
"POST",
|
|
1232
|
+
"PUT",
|
|
1233
|
+
"DELETE",
|
|
1234
|
+
"PATCH",
|
|
1235
|
+
"OPTIONS"
|
|
1236
|
+
],
|
|
1237
|
+
allowedOrigins: ["*"],
|
|
1238
|
+
credentials: false,
|
|
1239
|
+
exposedHeaders: ["mcp-session-id", "mcp-protocol-version"]
|
|
1240
|
+
})));
|
|
1241
|
+
const McpStdio = McpLive.pipe(Layer.provideMerge(McpServer.layerStdio({
|
|
1242
|
+
name: MCP_SERVER_NAME,
|
|
1243
|
+
version: MCP_SERVER_VERSION
|
|
1244
|
+
})), Layer.provide(NodeStdio.layer), Layer.provide(Layer.succeed(Logger.LogToStderr)(true)));
|
|
1245
|
+
const startMcpHttpServer = (port) => Layer.launch(HttpRouter.serve(McpRouter).pipe(Layer.provideMerge(NodeHttpServer.layer(createServer, { port }))));
|
|
1246
|
+
const startMcpStdioServer = () => Layer.launch(McpStdio);
|
|
1247
|
+
//#endregion
|
|
1248
|
+
//#region src/helpers/policy.ts
|
|
1249
|
+
/** biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: safe */
|
|
1250
|
+
const toCallPolicyVersion = (version) => {
|
|
1251
|
+
switch (version) {
|
|
1252
|
+
case "0.0.1": return CallPolicyVersion.V0_0_1;
|
|
1253
|
+
case "0.0.2": return CallPolicyVersion.V0_0_2;
|
|
1254
|
+
case "0.0.3": return CallPolicyVersion.V0_0_3;
|
|
1255
|
+
case "0.0.4": return CallPolicyVersion.V0_0_4;
|
|
1256
|
+
case "0.0.5": return CallPolicyVersion.V0_0_5;
|
|
1257
|
+
default: throw new Error("Invalid call policy version");
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
const conditionToParamCondition = (condition) => {
|
|
1261
|
+
if (condition === "EQUAL") return ParamCondition.EQUAL;
|
|
1262
|
+
if (condition === "GREATER_THAN") return ParamCondition.GREATER_THAN;
|
|
1263
|
+
if (condition === "LESS_THAN") return ParamCondition.LESS_THAN;
|
|
1264
|
+
if (condition === "GREATER_THAN_OR_EQUAL") return ParamCondition.GREATER_THAN_OR_EQUAL;
|
|
1265
|
+
if (condition === "LESS_THAN_OR_EQUAL") return ParamCondition.LESS_THAN_OR_EQUAL;
|
|
1266
|
+
if (condition === "NOT_EQUAL") return ParamCondition.NOT_EQUAL;
|
|
1267
|
+
if (condition === "ONE_OF") return ParamCondition.ONE_OF;
|
|
1268
|
+
if (condition === "SLICE_EQUAL") return ParamCondition.SLICE_EQUAL;
|
|
1269
|
+
return ParamCondition.EQUAL;
|
|
1270
|
+
};
|
|
1271
|
+
const policyParamsToPolicies = (params) => {
|
|
1272
|
+
const policies = [];
|
|
1273
|
+
for (const param of params) if (param.type === "sudo") policies.push(toSudoPolicy({}));
|
|
1274
|
+
else if (param.type === "timestamp") policies.push(toTimestampPolicy(param));
|
|
1275
|
+
else if (param.type === "gas") policies.push(toGasPolicy(param));
|
|
1276
|
+
else if (param.type === "rate-limit") policies.push(toRateLimitPolicy(param));
|
|
1277
|
+
else if (param.type === "signature-caller") policies.push(toSignatureCallerPolicy(param));
|
|
1278
|
+
else if (param.type === "call") policies.push(toCallPolicy({
|
|
1279
|
+
policyVersion: toCallPolicyVersion(param.policyVersion),
|
|
1280
|
+
permissions: !param.permissions ? void 0 : param.permissions.map((p) => {
|
|
1281
|
+
const callType = (() => {
|
|
1282
|
+
if (!p.callType) return CallType.CALL;
|
|
1283
|
+
if (p.callType === "call") return CallType.CALL;
|
|
1284
|
+
if (p.callType === "delegatecall") return CallType.DELEGATE_CALL;
|
|
1285
|
+
return CallType.BATCH_CALL;
|
|
1286
|
+
})();
|
|
1287
|
+
if ("abi" in p) return {
|
|
1288
|
+
callType,
|
|
1289
|
+
target: p.target,
|
|
1290
|
+
valueLimit: p.valueLimit,
|
|
1291
|
+
abi: p.abi,
|
|
1292
|
+
functionName: p.functionName,
|
|
1293
|
+
selector: p.selector,
|
|
1294
|
+
args: !p.args ? void 0 : p.args.map((a) => {
|
|
1295
|
+
if (a === null) return null;
|
|
1296
|
+
const c = a.condition;
|
|
1297
|
+
if (c === "ONE_OF") return {
|
|
1298
|
+
condition: ParamCondition.ONE_OF,
|
|
1299
|
+
value: a.value
|
|
1300
|
+
};
|
|
1301
|
+
if (c === "SLICE_EQUAL" && "start" in a) return {
|
|
1302
|
+
condition: ParamCondition.SLICE_EQUAL,
|
|
1303
|
+
value: a.value,
|
|
1304
|
+
start: a.start,
|
|
1305
|
+
length: a.length
|
|
1306
|
+
};
|
|
1307
|
+
return {
|
|
1308
|
+
condition: conditionToParamCondition(c),
|
|
1309
|
+
value: a.value
|
|
1310
|
+
};
|
|
1311
|
+
})
|
|
1312
|
+
};
|
|
1313
|
+
return {
|
|
1314
|
+
callType,
|
|
1315
|
+
target: p.target,
|
|
1316
|
+
selector: p.selector,
|
|
1317
|
+
valueLimit: p.valueLimit,
|
|
1318
|
+
rules: p.rules ? p.rules.map((r) => {
|
|
1319
|
+
return {
|
|
1320
|
+
offset: r.offset,
|
|
1321
|
+
params: r.params,
|
|
1322
|
+
condition: conditionToParamCondition(r.condition)
|
|
1323
|
+
};
|
|
1324
|
+
}) : void 0
|
|
1325
|
+
};
|
|
1326
|
+
})
|
|
1327
|
+
}));
|
|
1328
|
+
else throw new Error("Invalid policy type");
|
|
1329
|
+
return policies;
|
|
1330
|
+
};
|
|
1331
|
+
//#endregion
|
|
1332
|
+
//#region src/layers/smart-account.ts
|
|
1333
|
+
/**
|
|
1334
|
+
* Service tag for resolving {@link SmartAccountManager} from the Effect context.
|
|
1335
|
+
*/
|
|
1336
|
+
const SmartAccountManager = ServiceMap.Service("@namera-ai/cli/SmartAccountManager");
|
|
1337
|
+
/**
|
|
1338
|
+
* Domain error for smart account lifecycle operations.
|
|
1339
|
+
*/
|
|
1340
|
+
var SmartAccountManagerError = class extends Data.TaggedError("@namera-ai/cli/SmartAccountManagerError") {};
|
|
1341
|
+
/**
|
|
1342
|
+
* Live layer that manages smart account storage and derivation.
|
|
1343
|
+
*/
|
|
1344
|
+
const layer$3 = Layer.effect(SmartAccountManager, Effect.gen(function* () {
|
|
1345
|
+
const configManager = yield* ConfigManager;
|
|
1346
|
+
const keystoreManager = yield* KeystoreManager;
|
|
1347
|
+
const promptManager = yield* PromptManager;
|
|
1348
|
+
const web3Service = yield* Web3Service;
|
|
1349
|
+
const getSmartAccount = (params) => Effect.gen(function* () {
|
|
1350
|
+
const res = yield* configManager.getEntity({
|
|
1351
|
+
alias: params.alias,
|
|
1352
|
+
type: "smart-account"
|
|
1353
|
+
});
|
|
1354
|
+
const parsedSmartAccount = Schema.decodeUnknownSync(Schema.fromJsonString(LocalSmartAccount))(res.content);
|
|
1355
|
+
return {
|
|
1356
|
+
alias: res.alias,
|
|
1357
|
+
data: parsedSmartAccount,
|
|
1358
|
+
path: res.path
|
|
1359
|
+
};
|
|
1360
|
+
});
|
|
1361
|
+
const listSmartAccounts = () => Effect.gen(function* () {
|
|
1362
|
+
const effects = (yield* configManager.getEntitiesForType("smart-account")).map((entity) => Effect.gen(function* () {
|
|
1363
|
+
const parsedSmartAccount = Schema.decodeUnknownSync(Schema.fromJsonString(LocalSmartAccount))(entity.content);
|
|
1364
|
+
return {
|
|
1365
|
+
alias: entity.alias,
|
|
1366
|
+
data: parsedSmartAccount,
|
|
1367
|
+
path: entity.path
|
|
1368
|
+
};
|
|
1369
|
+
}));
|
|
1370
|
+
return yield* Effect.all(effects, { concurrency: "unbounded" });
|
|
1371
|
+
});
|
|
1372
|
+
const createSmartAccount = (params) => Effect.gen(function* () {
|
|
1373
|
+
const existingAccounts = yield* listSmartAccounts();
|
|
1374
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
1375
|
+
alias: params.alias,
|
|
1376
|
+
type: "smart-account"
|
|
1377
|
+
});
|
|
1378
|
+
const ownerKeystore = yield* keystoreManager.getKeystore({ alias: params.ownerAlias });
|
|
1379
|
+
const ownerSigner = yield* keystoreManager.getSigner({
|
|
1380
|
+
alias: params.ownerAlias,
|
|
1381
|
+
password: params.ownerPassword
|
|
1382
|
+
});
|
|
1383
|
+
const publicClient = createPublicClient({
|
|
1384
|
+
chain: mainnet,
|
|
1385
|
+
transport: http()
|
|
1386
|
+
});
|
|
1387
|
+
const entryPointVersion = "0.7";
|
|
1388
|
+
const kernelVersion = "0.3.2";
|
|
1389
|
+
const index = BigInt(params.index ?? 0);
|
|
1390
|
+
const ownerType = "ecdsa";
|
|
1391
|
+
const saAddress = (yield* Effect.tryPromise({
|
|
1392
|
+
try: () => createAccountClient({
|
|
1393
|
+
type: "ecdsa",
|
|
1394
|
+
bundlerTransport: http(),
|
|
1395
|
+
chain: mainnet,
|
|
1396
|
+
client: publicClient,
|
|
1397
|
+
entrypointVersion: entryPointVersion,
|
|
1398
|
+
kernelVersion,
|
|
1399
|
+
signer: ownerSigner
|
|
1400
|
+
}),
|
|
1401
|
+
catch: () => new SmartAccountManagerError({
|
|
1402
|
+
code: "KernelAddressGenerationError",
|
|
1403
|
+
message: `Unable to compute smart account address for Address: ${ownerKeystore.data.address} and Index: ${params.index}`
|
|
1404
|
+
})
|
|
1405
|
+
})).account.address;
|
|
1406
|
+
if (existingAccounts.find((d) => d.data.smartAccountAddress === saAddress)) return yield* Effect.fail(new SmartAccountManagerError({
|
|
1407
|
+
code: "SmartAccountAlreadyExists",
|
|
1408
|
+
message: `Smart account for owner: ${ownerKeystore.alias} and index: ${params.index} already exists`
|
|
1409
|
+
}));
|
|
1410
|
+
const saData = {
|
|
1411
|
+
entryPointVersion,
|
|
1412
|
+
kernelVersion,
|
|
1413
|
+
ownerAlias: ownerKeystore.alias,
|
|
1414
|
+
ownerType,
|
|
1415
|
+
smartAccountAddress: saAddress,
|
|
1416
|
+
index
|
|
1417
|
+
};
|
|
1418
|
+
const data = {
|
|
1419
|
+
data: saData,
|
|
1420
|
+
path: entityPath,
|
|
1421
|
+
alias: params.alias
|
|
1422
|
+
};
|
|
1423
|
+
const encoded = Schema.encodeSync(LocalSmartAccount)(saData);
|
|
1424
|
+
yield* configManager.storeEntity({
|
|
1425
|
+
alias: params.alias,
|
|
1426
|
+
content: JSON.stringify(encoded),
|
|
1427
|
+
path: entityPath,
|
|
1428
|
+
type: "smart-account"
|
|
1429
|
+
});
|
|
1430
|
+
return data;
|
|
1431
|
+
});
|
|
1432
|
+
const selectSmartAccount = (params) => Effect.gen(function* () {
|
|
1433
|
+
const smartAccounts = yield* listSmartAccounts();
|
|
1434
|
+
return yield* promptManager.selectPrompt({
|
|
1435
|
+
message: params.message,
|
|
1436
|
+
choices: smartAccounts.map((a) => ({
|
|
1437
|
+
title: a.alias,
|
|
1438
|
+
value: a,
|
|
1439
|
+
description: a.data.smartAccountAddress
|
|
1440
|
+
}))
|
|
1441
|
+
});
|
|
1442
|
+
});
|
|
1443
|
+
const removeSmartAccount = (params) => Effect.gen(function* () {
|
|
1444
|
+
const sa = yield* getSmartAccount({ alias: params.alias });
|
|
1445
|
+
return yield* configManager.removeEntity({
|
|
1446
|
+
alias: sa.alias,
|
|
1447
|
+
type: "smart-account"
|
|
1448
|
+
});
|
|
1449
|
+
});
|
|
1450
|
+
const getSmartAccountStatus = (params) => Effect.gen(function* () {
|
|
1451
|
+
const sa = yield* getSmartAccount({ alias: params.alias });
|
|
1452
|
+
const publicClient = yield* web3Service.getPublicClient({
|
|
1453
|
+
chain: params.chain,
|
|
1454
|
+
rpcUrl: params.rpcUrl
|
|
1455
|
+
});
|
|
1456
|
+
const code = yield* Effect.promise(() => publicClient.getCode({ address: sa.data.smartAccountAddress }));
|
|
1457
|
+
return Boolean(code);
|
|
1458
|
+
});
|
|
1459
|
+
const importSmartAccount = (params) => Effect.gen(function* () {
|
|
1460
|
+
const { alias, ...rest } = params;
|
|
1461
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
1462
|
+
alias: params.alias,
|
|
1463
|
+
type: "smart-account"
|
|
1464
|
+
});
|
|
1465
|
+
const ownerKeystore = yield* keystoreManager.getKeystore({ alias: params.ownerAlias });
|
|
1466
|
+
if ((yield* listSmartAccounts()).find((d) => d.data.smartAccountAddress === rest.smartAccountAddress)) return yield* Effect.fail(new SmartAccountManagerError({
|
|
1467
|
+
code: "SmartAccountAlreadyExists",
|
|
1468
|
+
message: `Smart account for owner: ${ownerKeystore.alias} and address: ${rest.smartAccountAddress} already exists`
|
|
1469
|
+
}));
|
|
1470
|
+
yield* configManager.storeEntity({
|
|
1471
|
+
alias: params.alias,
|
|
1472
|
+
content: JSON.stringify(Schema.encodeSync(LocalSmartAccount)(rest)),
|
|
1473
|
+
path: entityPath,
|
|
1474
|
+
type: "smart-account"
|
|
1475
|
+
});
|
|
1476
|
+
return {
|
|
1477
|
+
alias: params.alias,
|
|
1478
|
+
data: rest,
|
|
1479
|
+
path: entityPath
|
|
1480
|
+
};
|
|
1481
|
+
});
|
|
1482
|
+
return SmartAccountManager.of({
|
|
1483
|
+
createSmartAccount,
|
|
1484
|
+
getSmartAccount,
|
|
1485
|
+
listSmartAccounts,
|
|
1486
|
+
selectSmartAccount,
|
|
1487
|
+
removeSmartAccount,
|
|
1488
|
+
getSmartAccountStatus,
|
|
1489
|
+
importSmartAccount
|
|
1490
|
+
});
|
|
1491
|
+
}));
|
|
1492
|
+
//#endregion
|
|
1493
|
+
//#region src/layers/session-key.ts
|
|
1494
|
+
const SessionKeyManager = ServiceMap.Service("@namera-ai/cli/SessionKeyManager");
|
|
1495
|
+
var SessionKeyManagerError = class extends Data.TaggedError("@namera-ai/cli/SessionKeyManagerError") {};
|
|
1496
|
+
const layer$2 = Layer.effect(SessionKeyManager, Effect.gen(function* () {
|
|
1497
|
+
const configManager = yield* ConfigManager;
|
|
1498
|
+
const promptManager = yield* PromptManager;
|
|
1499
|
+
const keystoreManager = yield* KeystoreManager;
|
|
1500
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
1501
|
+
const web3Service = yield* Web3Service;
|
|
1502
|
+
const createSessionKey$1 = (params) => Effect.gen(function* () {
|
|
1503
|
+
if (yield* configManager.checkEntityExists({
|
|
1504
|
+
alias: params.alias,
|
|
1505
|
+
type: "session-key"
|
|
1506
|
+
})) return yield* Effect.fail(new SessionKeyManagerError({
|
|
1507
|
+
code: "SessionKeyAlreadyExists",
|
|
1508
|
+
message: `Session key for alias ${params.alias} already exists`
|
|
1509
|
+
}));
|
|
1510
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
1511
|
+
alias: params.alias,
|
|
1512
|
+
type: "session-key"
|
|
1513
|
+
});
|
|
1514
|
+
const sa = yield* smartAccountManager.getSmartAccount({ alias: params.smartAccountAlias });
|
|
1515
|
+
const ownerSigner = yield* keystoreManager.getSigner({
|
|
1516
|
+
alias: sa.data.ownerAlias,
|
|
1517
|
+
password: params.ownerKeystorePassword
|
|
1518
|
+
});
|
|
1519
|
+
const policies = policyParamsToPolicies(params.policyParams);
|
|
1520
|
+
const clients = yield* Effect.all(params.chains.map((chain) => web3Service.getPublicClient({ chain })), { concurrency: "unbounded" });
|
|
1521
|
+
const sessionPrivateKey = generatePrivateKey();
|
|
1522
|
+
const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey);
|
|
1523
|
+
const res = yield* Effect.promise(() => createSessionKey({
|
|
1524
|
+
type: "ecdsa",
|
|
1525
|
+
accountType: "ecdsa",
|
|
1526
|
+
sessionPrivateKey,
|
|
1527
|
+
clients,
|
|
1528
|
+
entrypointVersion: sa.data.entryPointVersion,
|
|
1529
|
+
kernelVersion: sa.data.kernelVersion,
|
|
1530
|
+
index: sa.data.index,
|
|
1531
|
+
signer: ownerSigner,
|
|
1532
|
+
policies
|
|
1533
|
+
}));
|
|
1534
|
+
const encData = yield* Effect.tryPromise({
|
|
1535
|
+
try: () => Wallet.fromPrivateKey(hexToBytes(sessionPrivateKey)).toV3(params.sessionKeyPassword),
|
|
1536
|
+
catch: () => new SessionKeyManagerError({
|
|
1537
|
+
code: "EncryptionError",
|
|
1538
|
+
message: "Failed to encrypt session key"
|
|
1539
|
+
})
|
|
1540
|
+
});
|
|
1541
|
+
const data = {
|
|
1542
|
+
sessionKeyType: "ecdsa",
|
|
1543
|
+
serializedAccounts: res.serializedAccounts.map((a) => ({
|
|
1544
|
+
chain: chainIdToChainName(a.chainId),
|
|
1545
|
+
serializedAccount: a.serializedAccount
|
|
1546
|
+
})),
|
|
1547
|
+
sessionKeyAddress: sessionKeyAccount.address,
|
|
1548
|
+
smartAccountAlias: sa.alias,
|
|
1549
|
+
...encData
|
|
1550
|
+
};
|
|
1551
|
+
yield* configManager.storeEntity({
|
|
1552
|
+
alias: params.alias,
|
|
1553
|
+
content: JSON.stringify(data),
|
|
1554
|
+
path: entityPath,
|
|
1555
|
+
type: "session-key"
|
|
1556
|
+
});
|
|
1557
|
+
return {
|
|
1558
|
+
alias: params.alias,
|
|
1559
|
+
data,
|
|
1560
|
+
path: entityPath
|
|
1561
|
+
};
|
|
1562
|
+
});
|
|
1563
|
+
const getSessionKey = (params) => Effect.gen(function* () {
|
|
1564
|
+
const res = yield* configManager.getEntity({
|
|
1565
|
+
alias: params.alias,
|
|
1566
|
+
type: "session-key"
|
|
1567
|
+
});
|
|
1568
|
+
const parsed = Schema.decodeUnknownOption(Schema.fromJsonString(SessionKey))(res.content);
|
|
1569
|
+
if (parsed._tag === "None") return yield* Effect.fail(new SessionKeyManagerError({
|
|
1570
|
+
code: "SessionKeyParseError",
|
|
1571
|
+
message: "Unable to parse session key"
|
|
1572
|
+
}));
|
|
1573
|
+
return {
|
|
1574
|
+
alias: res.alias,
|
|
1575
|
+
data: parsed.value,
|
|
1576
|
+
path: res.path
|
|
1577
|
+
};
|
|
1578
|
+
});
|
|
1579
|
+
const listSessionKeys = (params) => Effect.gen(function* () {
|
|
1580
|
+
const effects = (yield* configManager.getEntitiesForType("session-key")).map((entity) => Effect.gen(function* () {
|
|
1581
|
+
const parsed = Schema.decodeUnknownOption(Schema.fromJsonString(SessionKey))(entity.content);
|
|
1582
|
+
if (parsed._tag === "None") return yield* Effect.fail(new SessionKeyManagerError({
|
|
1583
|
+
code: "SessionKeyParseError",
|
|
1584
|
+
message: "Unable to parse session key"
|
|
1585
|
+
}));
|
|
1586
|
+
return {
|
|
1587
|
+
alias: entity.alias,
|
|
1588
|
+
data: parsed.value,
|
|
1589
|
+
path: entity.path
|
|
1590
|
+
};
|
|
1591
|
+
}));
|
|
1592
|
+
const allKeys = yield* Effect.all(effects, { concurrency: "unbounded" });
|
|
1593
|
+
if (params.smartAccount) return allKeys.filter((k) => k.data.smartAccountAlias === params.smartAccount);
|
|
1594
|
+
return allKeys;
|
|
1595
|
+
});
|
|
1596
|
+
const selectSessionKey = (params) => Effect.gen(function* () {
|
|
1597
|
+
const sessionKeys = yield* listSessionKeys({});
|
|
1598
|
+
return yield* promptManager.selectPrompt({
|
|
1599
|
+
message: params.message,
|
|
1600
|
+
choices: sessionKeys.map((sk) => ({
|
|
1601
|
+
title: sk.alias,
|
|
1602
|
+
value: sk,
|
|
1603
|
+
description: `${sk.data.sessionKeyType === "ecdsa" ? sk.data.sessionKeyAddress : sk.data.passKeyName} (${sk.data.smartAccountAlias})`
|
|
1604
|
+
}))
|
|
1605
|
+
});
|
|
1606
|
+
});
|
|
1607
|
+
const getSessionKeyStatus = (params) => Effect.gen(function* () {
|
|
1608
|
+
const sessionKey = yield* getSessionKey({ alias: params.alias });
|
|
1609
|
+
const sa = yield* smartAccountManager.getSmartAccount({ alias: sessionKey.data.smartAccountAlias });
|
|
1610
|
+
const publicClient = yield* web3Service.getPublicClient({
|
|
1611
|
+
chain: params.chain,
|
|
1612
|
+
rpcUrl: params.rpcUrl
|
|
1613
|
+
});
|
|
1614
|
+
if (sessionKey.data.sessionKeyType !== "ecdsa") return yield* Effect.fail(new SessionKeyManagerError({
|
|
1615
|
+
code: "SessionKeyParseError",
|
|
1616
|
+
message: "Only ECDSA session keys have an on-chain status"
|
|
1617
|
+
}));
|
|
1618
|
+
const sessionKeyAddress = sessionKey.data.sessionKeyAddress;
|
|
1619
|
+
return yield* Effect.tryPromise({
|
|
1620
|
+
try: () => isSessionKeyInstalled(publicClient, {
|
|
1621
|
+
accountAddress: sa.data.smartAccountAddress,
|
|
1622
|
+
sessionKeyAddress
|
|
1623
|
+
}),
|
|
1624
|
+
catch: () => false
|
|
1625
|
+
}).pipe(Effect.catch(() => Effect.succeed(false)));
|
|
1626
|
+
});
|
|
1627
|
+
const multiSelectSessionKeys = (params) => Effect.gen(function* () {
|
|
1628
|
+
const allKeys = yield* listSessionKeys({ smartAccount: params.smartAccount });
|
|
1629
|
+
return yield* promptManager.multiSelectPrompt({
|
|
1630
|
+
message: params.message,
|
|
1631
|
+
choices: allKeys.map((k) => ({
|
|
1632
|
+
title: k.alias,
|
|
1633
|
+
value: k,
|
|
1634
|
+
description: `${k.data.sessionKeyType === "ecdsa" ? k.data.sessionKeyAddress : k.data.passKeyName} (${k.data.smartAccountAlias})`
|
|
1635
|
+
})),
|
|
1636
|
+
min: 1
|
|
1637
|
+
});
|
|
1638
|
+
});
|
|
1639
|
+
const getSessionKeyPassword = (params) => Effect.gen(function* () {
|
|
1640
|
+
const key = yield* getSessionKey({ alias: params.alias });
|
|
1641
|
+
return yield* promptManager.passwordPrompt({
|
|
1642
|
+
message: params.message,
|
|
1643
|
+
validate: (v) => Effect.gen(function* () {
|
|
1644
|
+
if (v.trim() === "") return yield* Effect.fail("Password cannot be empty");
|
|
1645
|
+
yield* Effect.tryPromise({
|
|
1646
|
+
try: () => Wallet.fromV3(key.data, v),
|
|
1647
|
+
catch: () => new SessionKeyManagerError({
|
|
1648
|
+
code: "DecryptError",
|
|
1649
|
+
message: `Invalid password for session key ${params.alias}`
|
|
1650
|
+
})
|
|
1651
|
+
}).pipe(Effect.catch(() => Effect.fail("Invalid password")));
|
|
1652
|
+
return v;
|
|
1653
|
+
})
|
|
1654
|
+
});
|
|
1655
|
+
});
|
|
1656
|
+
const getSessionKeySigner = (params) => Effect.gen(function* () {
|
|
1657
|
+
const key = yield* getSessionKey({ alias: params.alias });
|
|
1658
|
+
const signer = yield* Effect.tryPromise({
|
|
1659
|
+
try: async () => {
|
|
1660
|
+
return privateKeyToAccount(toHex((await Wallet.fromV3(key.data, params.password)).getPrivateKey()));
|
|
1661
|
+
},
|
|
1662
|
+
catch: () => new SessionKeyManagerError({
|
|
1663
|
+
code: "DecryptError",
|
|
1664
|
+
message: `Invalid password for session key ${params.alias}`
|
|
1665
|
+
})
|
|
1666
|
+
});
|
|
1667
|
+
return {
|
|
1668
|
+
...key,
|
|
1669
|
+
signer
|
|
1670
|
+
};
|
|
1671
|
+
});
|
|
1672
|
+
const removeSessionKey = (params) => Effect.gen(function* () {
|
|
1673
|
+
const key = yield* getSessionKey({ alias: params.alias });
|
|
1674
|
+
return yield* configManager.removeEntity({
|
|
1675
|
+
alias: key.alias,
|
|
1676
|
+
type: "session-key"
|
|
1677
|
+
});
|
|
1678
|
+
});
|
|
1679
|
+
return SessionKeyManager.of({
|
|
1680
|
+
createSessionKey: createSessionKey$1,
|
|
1681
|
+
getSessionKey,
|
|
1682
|
+
listSessionKeys,
|
|
1683
|
+
selectSessionKey,
|
|
1684
|
+
getSessionKeyStatus,
|
|
1685
|
+
multiSelectSessionKeys,
|
|
1686
|
+
getSessionKeyPassword,
|
|
1687
|
+
getSessionKeySigner,
|
|
1688
|
+
removeSessionKey
|
|
1689
|
+
});
|
|
1690
|
+
}));
|
|
1691
|
+
//#endregion
|
|
1692
|
+
//#region src/layers/mcp.ts
|
|
1693
|
+
const McpManager = ServiceMap.Service("@namera-ai/cli/McpManager");
|
|
1694
|
+
const layer$1 = Layer.effect(McpManager, Effect.gen(function* () {
|
|
1695
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
1696
|
+
const sessionKeyManager = yield* SessionKeyManager;
|
|
1697
|
+
const startMcpServer = (params) => Effect.gen(function* () {
|
|
1698
|
+
const sa = yield* smartAccountManager.getSmartAccount({ alias: params.smartAccountAlias });
|
|
1699
|
+
const sessionKeys = Object.entries(params.sessionKeys);
|
|
1700
|
+
const sessionKeysWithSigners = yield* Effect.all(sessionKeys.map(([alias, password]) => sessionKeyManager.getSessionKeySigner({
|
|
1701
|
+
alias,
|
|
1702
|
+
password
|
|
1703
|
+
})));
|
|
1704
|
+
const context = McpContext.of({
|
|
1705
|
+
smartAccount: sa.data,
|
|
1706
|
+
sessionKeys: sessionKeysWithSigners
|
|
1707
|
+
});
|
|
1708
|
+
if (params.transport === "stdio") yield* startMcpStdioServer().pipe(Effect.provideService(McpContext, context));
|
|
1709
|
+
else yield* startMcpHttpServer(params.port ?? 8080).pipe(Effect.provideService(McpContext, context));
|
|
1710
|
+
});
|
|
1711
|
+
return McpManager.of({ startMcpServer });
|
|
1712
|
+
}));
|
|
1713
|
+
//#endregion
|
|
1714
|
+
//#region src/helpers/pretty.ts
|
|
1715
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: safe */
|
|
1716
|
+
const keyColors = [
|
|
1717
|
+
"\x1B[36m",
|
|
1718
|
+
"\x1B[35m",
|
|
1719
|
+
"\x1B[34m",
|
|
1720
|
+
"\x1B[32m",
|
|
1721
|
+
"\x1B[33m"
|
|
1722
|
+
];
|
|
1723
|
+
const reset = "\x1B[0m";
|
|
1724
|
+
const bold = "\x1B[1m";
|
|
1725
|
+
/**
|
|
1726
|
+
* Returns a shallow copy of an object with humanized key names.
|
|
1727
|
+
*/
|
|
1728
|
+
const transformKeys = (obj) => {
|
|
1729
|
+
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [formatKey(k), v]));
|
|
1730
|
+
};
|
|
1731
|
+
/**
|
|
1732
|
+
* Humanizes a key by spacing words and title-casing tokens.
|
|
1733
|
+
*/
|
|
1734
|
+
const formatKey = (key) => key.replace(/[_-]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1735
|
+
/**
|
|
1736
|
+
* Formats a primitive-like value for display.
|
|
1737
|
+
*/
|
|
1738
|
+
const formatValue = (value) => {
|
|
1739
|
+
if (value === null) return "null";
|
|
1740
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
1741
|
+
if (value instanceof Date) return value.toISOString();
|
|
1742
|
+
if (typeof value === "string") return value;
|
|
1743
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1744
|
+
return String(value);
|
|
1745
|
+
};
|
|
1746
|
+
/**
|
|
1747
|
+
* Returns true when a value is a display-friendly primitive.
|
|
1748
|
+
*/
|
|
1749
|
+
const isPrimitive = (v) => v === null || typeof v !== "object" || v instanceof Date;
|
|
1750
|
+
/**
|
|
1751
|
+
* Determines whether an array contains only flat objects of primitives.
|
|
1752
|
+
*/
|
|
1753
|
+
const isFlatObjectArray = (arr) => arr.length > 0 && arr.every((item) => item && typeof item === "object" && !Array.isArray(item) && Object.values(item).every(isPrimitive));
|
|
1754
|
+
/**
|
|
1755
|
+
* Formats objects or arrays into a human-friendly CLI display string.
|
|
1756
|
+
*
|
|
1757
|
+
* Uses colorized keys when printing objects, and falls back to tabular
|
|
1758
|
+
* output for flat object arrays at the root level.
|
|
1759
|
+
*/
|
|
1760
|
+
const prettyFormat = (input, depth = 0, isRoot = true) => {
|
|
1761
|
+
const indent = 2;
|
|
1762
|
+
const maxDepth = 6;
|
|
1763
|
+
const space = " ".repeat(indent * depth);
|
|
1764
|
+
if (depth > maxDepth) return `${space}…`;
|
|
1765
|
+
if (isRoot && Array.isArray(input) && isFlatObjectArray(input)) {
|
|
1766
|
+
const transformed = input.map(transformKeys);
|
|
1767
|
+
console.table(transformed);
|
|
1768
|
+
return "";
|
|
1769
|
+
}
|
|
1770
|
+
if (Array.isArray(input)) return input.map((item, i) => {
|
|
1771
|
+
if (isPrimitive(item)) return `${space}${formatValue(item)}`;
|
|
1772
|
+
return `${space}[${i}]\n${prettyFormat(item, depth + 1, false)}`;
|
|
1773
|
+
}).join("\n");
|
|
1774
|
+
return Object.entries(input).map(([key, value]) => {
|
|
1775
|
+
const k = `${bold}${keyColors[depth % keyColors.length]}${formatKey(key)}${reset}`;
|
|
1776
|
+
if (isPrimitive(value)) return `${space}${k}: ${formatValue(value)}`;
|
|
1777
|
+
return `${space}${k}:\n${prettyFormat(value, depth + 1, false)}`;
|
|
1778
|
+
}).join("\n");
|
|
1779
|
+
};
|
|
1780
|
+
//#endregion
|
|
1781
|
+
//#region src/layers/output.ts
|
|
1782
|
+
/**
|
|
1783
|
+
* Service tag for resolving {@link OutputFormatter} from the Effect context.
|
|
1784
|
+
*/
|
|
1785
|
+
const OutputFormatter = ServiceMap.Service("@namera-ai/cli/OutputFormatter");
|
|
1786
|
+
/**
|
|
1787
|
+
* Live layer that formats output as pretty, JSON, or NDJSON.
|
|
1788
|
+
*/
|
|
1789
|
+
const layer = Layer.succeed(OutputFormatter, OutputFormatter.of({ format: (data, format) => Effect.gen(function* () {
|
|
1790
|
+
if (format === "pretty") return prettyFormat(data);
|
|
1791
|
+
if (format === "json") return JSON.stringify(data, null, 2);
|
|
1792
|
+
if (Array.isArray(data)) return NdJson.stringify(data);
|
|
1793
|
+
return NdJson.stringify([data]);
|
|
1794
|
+
}) }));
|
|
1795
|
+
//#endregion
|
|
1796
|
+
//#region src/commands/keystore/create.ts
|
|
1797
|
+
const createKeystoreHandler = (flagAlias, flagPassword) => Effect.gen(function* () {
|
|
1798
|
+
const promptManager = yield* PromptManager;
|
|
1799
|
+
const keystoreManager = yield* KeystoreManager;
|
|
1800
|
+
const outputFormatter = yield* OutputFormatter;
|
|
1801
|
+
const globalFlags = yield* getGlobalFlags();
|
|
1802
|
+
let params;
|
|
1803
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(CreateKeystoreParams))(globalFlags.params.value);
|
|
1804
|
+
else {
|
|
1805
|
+
let alias;
|
|
1806
|
+
let password;
|
|
1807
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
1808
|
+
else alias = yield* promptManager.aliasPrompt({
|
|
1809
|
+
aliasType: "new",
|
|
1810
|
+
message: "Enter alias for keystore:",
|
|
1811
|
+
type: "keystore"
|
|
1812
|
+
});
|
|
1813
|
+
if (flagPassword._tag === "Some") password = flagPassword.value;
|
|
1814
|
+
else password = yield* promptManager.passwordPrompt({ message: "Enter password for keystore:" });
|
|
1815
|
+
params = {
|
|
1816
|
+
alias,
|
|
1817
|
+
password: Redacted.value(password)
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
const res = yield* keystoreManager.createKeystore(params);
|
|
1821
|
+
const data = {
|
|
1822
|
+
address: res.data.address,
|
|
1823
|
+
alias: res.alias,
|
|
1824
|
+
path: res.path
|
|
1825
|
+
};
|
|
1826
|
+
if (globalFlags.quite) return;
|
|
1827
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
1828
|
+
yield* Console.log(output);
|
|
1829
|
+
});
|
|
1830
|
+
const alias$12 = Flag.string("alias").pipe(Flag.withDescription("The alias of the keystore to create"), Flag.withAlias("a"), Flag.optional);
|
|
1831
|
+
const password$2 = Flag.redacted("password").pipe(Flag.withDescription("The password to encrypt the keystore with"), Flag.withAlias("p"), Flag.optional);
|
|
1832
|
+
/**
|
|
1833
|
+
* Command that creates a new keystore and stores it locally.
|
|
1834
|
+
*/
|
|
1835
|
+
const createKeystoreCommand = Command.make("create", {
|
|
1836
|
+
alias: alias$12,
|
|
1837
|
+
password: password$2
|
|
1838
|
+
}, ({ alias, password }) => createKeystoreHandler(alias, password)).pipe(Command.withAlias("c"), Command.withDescription("Creates a random keypair and stores it as keystore"), Command.withExamples([
|
|
1839
|
+
{
|
|
1840
|
+
command: "namera keystore create -a my-wallet",
|
|
1841
|
+
description: "Creates a new keystore with alias 'my-wallet'"
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
command: "namera keystore create --alias my-wallet --password my-password",
|
|
1845
|
+
description: "Creates a new keystore with alias 'my-wallet' and password 'my-password'"
|
|
1846
|
+
},
|
|
1847
|
+
{
|
|
1848
|
+
command: `namera keystore create --params '{"alias":"my-wallet","password":"my-password"}'`,
|
|
1849
|
+
description: "Creates a new keystore with json params"
|
|
1850
|
+
},
|
|
1851
|
+
{
|
|
1852
|
+
command: "namera schema keystore.create",
|
|
1853
|
+
description: "Get the schema for the create command"
|
|
1854
|
+
}
|
|
1855
|
+
]));
|
|
1856
|
+
//#endregion
|
|
1857
|
+
//#region src/commands/keystore/decrypt.ts
|
|
1858
|
+
const handler$14 = (flagAlias, flagPassword) => Effect.gen(function* () {
|
|
1859
|
+
const keystoreManager = yield* KeystoreManager;
|
|
1860
|
+
const promptManager = yield* PromptManager;
|
|
1861
|
+
const outputFormatter = yield* OutputFormatter;
|
|
1862
|
+
const globalFlags = yield* getGlobalFlags();
|
|
1863
|
+
let params;
|
|
1864
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(DecryptKeystoreParams))(globalFlags.params.value);
|
|
1865
|
+
else {
|
|
1866
|
+
let alias;
|
|
1867
|
+
let password;
|
|
1868
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
1869
|
+
else alias = (yield* keystoreManager.selectKeystore({ message: "Select keystore:" })).alias;
|
|
1870
|
+
if (flagPassword._tag === "Some") password = flagPassword.value;
|
|
1871
|
+
else password = yield* promptManager.passwordPrompt({ message: "Enter password for keystore:" });
|
|
1872
|
+
params = {
|
|
1873
|
+
alias,
|
|
1874
|
+
password: Redacted.value(password)
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
const res = yield* keystoreManager.decryptKeystore(params);
|
|
1878
|
+
const data = {
|
|
1879
|
+
alias: res.alias,
|
|
1880
|
+
address: res.address,
|
|
1881
|
+
publicKey: res.publicKey,
|
|
1882
|
+
privateKey: Redacted.value(res.privateKey)
|
|
1883
|
+
};
|
|
1884
|
+
if (globalFlags.quite) return;
|
|
1885
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
1886
|
+
yield* Console.log(output);
|
|
1887
|
+
});
|
|
1888
|
+
const alias$11 = Flag.string("alias").pipe(Flag.withDescription("The alias of the keystore to decrypt"), Flag.withAlias("a"), Flag.optional);
|
|
1889
|
+
const password$1 = Flag.redacted("password").pipe(Flag.withDescription("The password to decrypt the keystore with"), Flag.withAlias("p"), Flag.optional);
|
|
1890
|
+
/**
|
|
1891
|
+
* Command that decrypts a keystore and prints key material.
|
|
1892
|
+
*/
|
|
1893
|
+
const decryptKeystoreCommand = Command.make("decrypt", {
|
|
1894
|
+
alias: alias$11,
|
|
1895
|
+
password: password$1
|
|
1896
|
+
}, ({ alias, password }) => handler$14(alias, password)).pipe(Command.withAlias("d"), Command.withDescription("Decrypt a keystore to get the private key"), Command.withExamples([
|
|
1897
|
+
{
|
|
1898
|
+
command: "namera keystore decrypt",
|
|
1899
|
+
description: "Decrypt a keystore with alias and password prompts"
|
|
1900
|
+
},
|
|
1901
|
+
{
|
|
1902
|
+
command: "namera keystore decrypt --alias my-wallet --password my-password",
|
|
1903
|
+
description: "Decrypt a keystore with alias and password"
|
|
1904
|
+
},
|
|
1905
|
+
{
|
|
1906
|
+
command: `namera keystore decrypt --params '{"alias":"my-wallet","password":"my-password"}'`,
|
|
1907
|
+
description: "Decrypt a keystore with json params"
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
command: "namera schema keystore.decrypt",
|
|
1911
|
+
description: "Get the schema for the decrypt command"
|
|
1912
|
+
}
|
|
1913
|
+
]));
|
|
1914
|
+
//#endregion
|
|
1915
|
+
//#region src/commands/keystore/import.ts
|
|
1916
|
+
const handler$13 = (flagAlias, flagPassword, flagPrivateKey) => Effect.gen(function* () {
|
|
1917
|
+
const keystoreManager = yield* KeystoreManager;
|
|
1918
|
+
const promptManager = yield* PromptManager;
|
|
1919
|
+
const outputFormatter = yield* OutputFormatter;
|
|
1920
|
+
const globalFlags = yield* getGlobalFlags();
|
|
1921
|
+
let params;
|
|
1922
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(ImportKeystoreParams))(globalFlags.params.value);
|
|
1923
|
+
else {
|
|
1924
|
+
let alias;
|
|
1925
|
+
let password;
|
|
1926
|
+
let privateKey;
|
|
1927
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
1928
|
+
else alias = yield* promptManager.aliasPrompt({
|
|
1929
|
+
aliasType: "new",
|
|
1930
|
+
message: "Enter alias for keystore:",
|
|
1931
|
+
type: "keystore"
|
|
1932
|
+
});
|
|
1933
|
+
if (flagPassword._tag === "Some") password = flagPassword.value;
|
|
1934
|
+
else password = yield* promptManager.passwordPrompt({ message: "Enter password for keystore:" });
|
|
1935
|
+
if (flagPrivateKey._tag === "Some") privateKey = flagPrivateKey.value;
|
|
1936
|
+
else privateKey = yield* promptManager.hexPrompt({
|
|
1937
|
+
redacted: true,
|
|
1938
|
+
length: 32,
|
|
1939
|
+
message: "Enter private key:"
|
|
1940
|
+
});
|
|
1941
|
+
params = {
|
|
1942
|
+
alias,
|
|
1943
|
+
password: Redacted.value(password),
|
|
1944
|
+
privateKey: Redacted.value(privateKey)
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
const res = yield* keystoreManager.importKeystore(params);
|
|
1948
|
+
const data = {
|
|
1949
|
+
alias: res.alias,
|
|
1950
|
+
address: res.data.address,
|
|
1951
|
+
path: res.path
|
|
1952
|
+
};
|
|
1953
|
+
if (globalFlags.quite) return;
|
|
1954
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
1955
|
+
yield* Console.log(output);
|
|
1956
|
+
});
|
|
1957
|
+
const alias$10 = Flag.string("alias").pipe(Flag.withDescription("The alias of the keystore to import"), Flag.withAlias("a"), Flag.optional);
|
|
1958
|
+
const privateKey = Flag.redacted("private-key").pipe(Flag.withDescription("The private key of the keystore to import"), Flag.optional);
|
|
1959
|
+
const password = Flag.redacted("password").pipe(Flag.withDescription("The password to encrypt the keystore with"), Flag.withAlias("p"), Flag.optional);
|
|
1960
|
+
/**
|
|
1961
|
+
* Command that imports a keystore from a private key.
|
|
1962
|
+
*/
|
|
1963
|
+
const importKeystoreCommand = Command.make("import", {
|
|
1964
|
+
alias: alias$10,
|
|
1965
|
+
privateKey,
|
|
1966
|
+
password
|
|
1967
|
+
}, ({ alias, password, privateKey }) => handler$13(alias, password, privateKey)).pipe(Command.withAlias("i"), Command.withDescription("Import a keystore from a private key"), Command.withExamples([
|
|
1968
|
+
{
|
|
1969
|
+
command: "namera keystore import",
|
|
1970
|
+
description: "Import a keystore with interactive prompts"
|
|
1971
|
+
},
|
|
1972
|
+
{
|
|
1973
|
+
command: "namera keystore import --alias my-wallet",
|
|
1974
|
+
description: "Import a keystore with alias 'my-wallet'"
|
|
1975
|
+
},
|
|
1976
|
+
{
|
|
1977
|
+
command: "namera keystore import -a my-wallet -p my-password --private-key 0x1234567890abcdef",
|
|
1978
|
+
description: "Import a keystore with alias 'my-wallet', password 'my-password', and private key '0x1234567890abcdef'"
|
|
1979
|
+
},
|
|
1980
|
+
{
|
|
1981
|
+
command: "namera schema keystore.import",
|
|
1982
|
+
description: "Get the schema for the import command"
|
|
1983
|
+
}
|
|
1984
|
+
]));
|
|
1985
|
+
//#endregion
|
|
1986
|
+
//#region src/commands/keystore/info.ts
|
|
1987
|
+
const getKeystoreInfoHandler = (flagAlias) => Effect.gen(function* () {
|
|
1988
|
+
const keystoreManager = yield* KeystoreManager;
|
|
1989
|
+
const outputFormatter = yield* OutputFormatter;
|
|
1990
|
+
const globalFlags = yield* getGlobalFlags();
|
|
1991
|
+
let params;
|
|
1992
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(GetKeystoreParams))(globalFlags.params.value);
|
|
1993
|
+
else {
|
|
1994
|
+
let alias;
|
|
1995
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
1996
|
+
else alias = (yield* keystoreManager.selectKeystore({ message: "Select keystore:" })).alias;
|
|
1997
|
+
params = { alias };
|
|
1998
|
+
}
|
|
1999
|
+
const res = yield* keystoreManager.getKeystore(params);
|
|
2000
|
+
const data = {
|
|
2001
|
+
alias: res.alias,
|
|
2002
|
+
address: res.data.address,
|
|
2003
|
+
path: res.path
|
|
2004
|
+
};
|
|
2005
|
+
if (globalFlags.quite) return;
|
|
2006
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2007
|
+
yield* Console.log(output);
|
|
2008
|
+
});
|
|
2009
|
+
const alias$9 = Flag.string("alias").pipe(Flag.withDescription("The alias of the keystore to retrieve"), Flag.withAlias("a"), Flag.optional);
|
|
2010
|
+
/**
|
|
2011
|
+
* Command that returns metadata for a keystore.
|
|
2012
|
+
*/
|
|
2013
|
+
const getKeystoreInfoCommand = Command.make("info", { alias: alias$9 }, ({ alias }) => getKeystoreInfoHandler(alias)).pipe(Command.withDescription("Get information about a keystore"), Command.withExamples([
|
|
2014
|
+
{
|
|
2015
|
+
command: "namera keystore info",
|
|
2016
|
+
description: "Get information about a keystore with alias prompt"
|
|
2017
|
+
},
|
|
2018
|
+
{
|
|
2019
|
+
command: "namera keystore info --alias my-wallet",
|
|
2020
|
+
description: "Get information about a keystore with alias 'my-wallet'"
|
|
2021
|
+
},
|
|
2022
|
+
{
|
|
2023
|
+
command: `namera keystore info --params '{"alias":"my-wallet"}'`,
|
|
2024
|
+
description: "Get information about a keystore with json params"
|
|
2025
|
+
},
|
|
2026
|
+
{
|
|
2027
|
+
command: "namera keystore info --alias my-wallet --output json",
|
|
2028
|
+
description: "Get information about a keystore in json format"
|
|
2029
|
+
},
|
|
2030
|
+
{
|
|
2031
|
+
command: "namera schema keystore.info",
|
|
2032
|
+
description: "Get the schema for the info command"
|
|
2033
|
+
}
|
|
2034
|
+
]));
|
|
2035
|
+
//#endregion
|
|
2036
|
+
//#region src/commands/keystore/list.ts
|
|
2037
|
+
const listKeystoreHandler = () => Effect.gen(function* () {
|
|
2038
|
+
const keystoreManager = yield* KeystoreManager;
|
|
2039
|
+
const outputFormatter = yield* OutputFormatter;
|
|
2040
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2041
|
+
const data = (yield* keystoreManager.listKeystores()).map((k) => ({
|
|
2042
|
+
alias: k.alias,
|
|
2043
|
+
address: k.data.address,
|
|
2044
|
+
path: k.path
|
|
2045
|
+
}));
|
|
2046
|
+
if (globalFlags.quite) return;
|
|
2047
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2048
|
+
yield* Console.log(output);
|
|
2049
|
+
});
|
|
2050
|
+
/**
|
|
2051
|
+
* Command that lists all stored keystores.
|
|
2052
|
+
*/
|
|
2053
|
+
const listKeystoresCommand = Command.make("list", {}, () => listKeystoreHandler()).pipe(Command.withAlias("ls"), Command.withDescription("List all keystores"), Command.withExamples([
|
|
2054
|
+
{
|
|
2055
|
+
command: "namera keystore list",
|
|
2056
|
+
description: "List all keystores"
|
|
2057
|
+
},
|
|
2058
|
+
{
|
|
2059
|
+
command: "namera keystore list --output json",
|
|
2060
|
+
description: "List all keystores in json format"
|
|
2061
|
+
},
|
|
2062
|
+
{
|
|
2063
|
+
command: "namera keystore list --output ndjson",
|
|
2064
|
+
description: "List all keystores in ndjson format"
|
|
2065
|
+
},
|
|
2066
|
+
{
|
|
2067
|
+
command: "namera schema keystore.list",
|
|
2068
|
+
description: "Get the schema for the list command"
|
|
2069
|
+
}
|
|
2070
|
+
]));
|
|
2071
|
+
//#endregion
|
|
2072
|
+
//#region src/commands/keystore/remove.ts
|
|
2073
|
+
const handler$12 = (flagAlias) => Effect.gen(function* () {
|
|
2074
|
+
const keystoreManager = yield* KeystoreManager;
|
|
2075
|
+
const outputFormatter = yield* OutputFormatter;
|
|
2076
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2077
|
+
let params;
|
|
2078
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(RemoveKeystoreParams))(globalFlags.params.value);
|
|
2079
|
+
else {
|
|
2080
|
+
let alias;
|
|
2081
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
2082
|
+
else alias = (yield* keystoreManager.selectKeystore({ message: "Select keystore:" })).alias;
|
|
2083
|
+
params = { alias };
|
|
2084
|
+
}
|
|
2085
|
+
yield* keystoreManager.removeKeystore(params);
|
|
2086
|
+
const data = { success: true };
|
|
2087
|
+
if (globalFlags.quite) return;
|
|
2088
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2089
|
+
yield* Console.log(output);
|
|
2090
|
+
});
|
|
2091
|
+
const alias$8 = Flag.string("alias").pipe(Flag.withDescription("The alias of the keystore to remove"), Flag.withAlias("a"), Flag.optional);
|
|
2092
|
+
const removeKeystoreCommand = Command.make("remove", { alias: alias$8 }, ({ alias }) => handler$12(alias)).pipe(Command.withAlias("rm"), Command.withDescription("Remove a keystore"), Command.withExamples([
|
|
2093
|
+
{
|
|
2094
|
+
command: "namera keystore remove",
|
|
2095
|
+
description: "Remove a keystore with alias prompt"
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
command: "namera keystore remove --alias my-wallet",
|
|
2099
|
+
description: "Remove a keystore with alias 'my-wallet'"
|
|
2100
|
+
},
|
|
2101
|
+
{
|
|
2102
|
+
command: `namera keystore remove --params '{"alias":"my-wallet"}'`,
|
|
2103
|
+
description: "Remove a keystore with json params"
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
command: "namera schema keystore.remove",
|
|
2107
|
+
description: "Get the schema for the remove command"
|
|
2108
|
+
}
|
|
2109
|
+
]));
|
|
2110
|
+
//#endregion
|
|
2111
|
+
//#region src/commands/keystore/index.ts
|
|
2112
|
+
/**
|
|
2113
|
+
* Command group for keystore-related operations.
|
|
2114
|
+
*/
|
|
2115
|
+
const keystoreCommands = Command.make("keystore", {}, () => Effect.void).pipe(Command.withDescription("Keystore management utilities."), Command.withAlias("k"), Command.withSubcommands([
|
|
2116
|
+
createKeystoreCommand,
|
|
2117
|
+
listKeystoresCommand,
|
|
2118
|
+
getKeystoreInfoCommand,
|
|
2119
|
+
decryptKeystoreCommand,
|
|
2120
|
+
importKeystoreCommand,
|
|
2121
|
+
removeKeystoreCommand
|
|
2122
|
+
]));
|
|
2123
|
+
//#endregion
|
|
2124
|
+
//#region src/commands/mcp/start.ts
|
|
2125
|
+
const handler$11 = (flagSmartAccount, flagSessionKeys, flagTransport, flagPort) => Effect.gen(function* () {
|
|
2126
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
2127
|
+
const sessionKeyManager = yield* SessionKeyManager;
|
|
2128
|
+
const mcpManager = yield* McpManager;
|
|
2129
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2130
|
+
let params;
|
|
2131
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(StartMcpServerParams))(globalFlags.params.value);
|
|
2132
|
+
else {
|
|
2133
|
+
let smartAccountAlias;
|
|
2134
|
+
let sessionKeys = {};
|
|
2135
|
+
let transport;
|
|
2136
|
+
let port;
|
|
2137
|
+
if (flagSmartAccount._tag === "Some") smartAccountAlias = flagSmartAccount.value;
|
|
2138
|
+
else smartAccountAlias = (yield* smartAccountManager.selectSmartAccount({ message: "Select smart account to use for the MCP server" })).alias;
|
|
2139
|
+
if (flagSessionKeys._tag === "Some") sessionKeys = flagSessionKeys.value;
|
|
2140
|
+
else {
|
|
2141
|
+
const keys = yield* sessionKeyManager.multiSelectSessionKeys({
|
|
2142
|
+
message: "Select session keys to use for the MCP server",
|
|
2143
|
+
smartAccount: smartAccountAlias
|
|
2144
|
+
});
|
|
2145
|
+
for (const key of keys) {
|
|
2146
|
+
const pass = yield* sessionKeyManager.getSessionKeyPassword({
|
|
2147
|
+
alias: key.alias,
|
|
2148
|
+
message: `Enter password for session key ${key.alias}:`
|
|
2149
|
+
});
|
|
2150
|
+
sessionKeys[key.alias] = Redacted.value(pass);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
if (flagTransport._tag === "Some") transport = flagTransport.value;
|
|
2154
|
+
else transport = "stdio";
|
|
2155
|
+
if (flagPort._tag === "Some") port = flagPort.value;
|
|
2156
|
+
else port = 8080;
|
|
2157
|
+
params = {
|
|
2158
|
+
smartAccountAlias,
|
|
2159
|
+
sessionKeys,
|
|
2160
|
+
transport,
|
|
2161
|
+
port
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
yield* mcpManager.startMcpServer(params);
|
|
2165
|
+
});
|
|
2166
|
+
const smartAccount = Flag.string("smart-account").pipe(Flag.withAlias("sa"), Flag.optional, Flag.withDescription("The smart account alias to use for the MCP server"));
|
|
2167
|
+
const sessionKeys = Flag.keyValuePair("session-key").pipe(Flag.withAlias("sk"), Flag.optional, Flag.withDescription("The session key aliases, password pairs to use for the MCP server"));
|
|
2168
|
+
const transport = Flag.choice("transport", ["stdio", "http"]).pipe(Flag.withAlias("t"), Flag.optional, Flag.withDescription("The transport to use for the MCP server"));
|
|
2169
|
+
const port = Flag.integer("port").pipe(Flag.withAlias("p"), Flag.optional, Flag.withDescription("The port to use for the MCP server when using http transport"));
|
|
2170
|
+
const startMcpCommand = Command.make("start", {
|
|
2171
|
+
smartAccount,
|
|
2172
|
+
sessionKeys,
|
|
2173
|
+
transport,
|
|
2174
|
+
port
|
|
2175
|
+
}, ({ smartAccount, sessionKeys, transport, port }) => handler$11(smartAccount, sessionKeys, transport, port)).pipe(Command.withAlias("s"), Command.withDescription("Starts the local MCP server."), Command.withExamples([
|
|
2176
|
+
{
|
|
2177
|
+
command: "namera mcp start --smart-account my-smart --session-key my-key=my-password",
|
|
2178
|
+
description: "Starts the local MCP server with a single session key and the default transport"
|
|
2179
|
+
},
|
|
2180
|
+
{
|
|
2181
|
+
command: "namera mcp start --smart-account my-smart --session-key my-key=my-password --transport http --port 8080",
|
|
2182
|
+
description: "Starts the local MCP server with a single session key and http transport on port 8080"
|
|
2183
|
+
},
|
|
2184
|
+
{
|
|
2185
|
+
command: "namera mcp start --smart-account my-smart --session-key my-key=my-password --session-key my-other-key=my-other-password",
|
|
2186
|
+
description: "Starts the local MCP server with multiple session keys and the default transport"
|
|
2187
|
+
},
|
|
2188
|
+
{
|
|
2189
|
+
command: "namera schema mcp.start",
|
|
2190
|
+
description: "Get the schema for the start command"
|
|
2191
|
+
}
|
|
2192
|
+
]));
|
|
2193
|
+
//#endregion
|
|
2194
|
+
//#region src/commands/mcp/index.ts
|
|
2195
|
+
const mcpCommands = Command.make("mcp", {}, () => Effect.void).pipe(Command.withDescription("Start and manage the Namera MCP server"), Command.withSubcommands([startMcpCommand]));
|
|
2196
|
+
//#endregion
|
|
2197
|
+
//#region src/helpers/paths.ts
|
|
2198
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: safe */
|
|
2199
|
+
/**
|
|
2200
|
+
* Returns the value at a dot-delimited path within a nested object.
|
|
2201
|
+
*
|
|
2202
|
+
* @param obj - Object to traverse.
|
|
2203
|
+
* @param path - Dot-delimited lookup path.
|
|
2204
|
+
*/
|
|
2205
|
+
const getSchema = (obj, path) => {
|
|
2206
|
+
return path.split(".").reduce((acc, key) => acc[key], obj);
|
|
2207
|
+
};
|
|
2208
|
+
/**
|
|
2209
|
+
* Extracts all dot-delimited paths that terminate at Effect schemas.
|
|
2210
|
+
*
|
|
2211
|
+
* @param obj - Object tree to scan.
|
|
2212
|
+
* @param prefix - Internal prefix used to build nested paths.
|
|
2213
|
+
*/
|
|
2214
|
+
const extractPaths = (obj, prefix = "") => {
|
|
2215
|
+
return Object.entries(obj).flatMap(([key, value]) => {
|
|
2216
|
+
const newPath = prefix ? `${prefix}.${key}` : key;
|
|
2217
|
+
if (Schema.isSchema(value)) return [newPath];
|
|
2218
|
+
if (typeof value === "object" && value !== null) return extractPaths(value, newPath);
|
|
2219
|
+
return [];
|
|
2220
|
+
});
|
|
2221
|
+
};
|
|
2222
|
+
//#endregion
|
|
2223
|
+
//#region src/commands/schema/index.ts
|
|
2224
|
+
/**
|
|
2225
|
+
* Schema registry keyed by command path.
|
|
2226
|
+
*/
|
|
2227
|
+
const schemas = {
|
|
2228
|
+
keystore: {
|
|
2229
|
+
create: CreateKeystoreParams,
|
|
2230
|
+
decrypt: DecryptKeystoreParams,
|
|
2231
|
+
info: GetKeystoreParams,
|
|
2232
|
+
list: ListKeystoreParams,
|
|
2233
|
+
import: ImportKeystoreParams,
|
|
2234
|
+
remove: RemoveKeystoreParams
|
|
2235
|
+
},
|
|
2236
|
+
"smart-account": {
|
|
2237
|
+
create: CreateSmartAccountParams,
|
|
2238
|
+
list: ListSmartAccountParams,
|
|
2239
|
+
info: GetSmartAccountInfoParams,
|
|
2240
|
+
remove: RemoveSmartAccountParams,
|
|
2241
|
+
status: GetSmartAccountStatusParams,
|
|
2242
|
+
import: ImportSmartAccountParams
|
|
2243
|
+
},
|
|
2244
|
+
"session-key": {
|
|
2245
|
+
create: CreateSessionKeyParams,
|
|
2246
|
+
list: ListSessionKeysParams,
|
|
2247
|
+
info: GetSessionKeyInfoParams,
|
|
2248
|
+
remove: RemoveSessionKeyParams,
|
|
2249
|
+
status: GetSessionKeyStatusParams
|
|
2250
|
+
},
|
|
2251
|
+
mcp: { start: StartMcpServerParams }
|
|
2252
|
+
};
|
|
2253
|
+
const commands$1 = extractPaths(schemas);
|
|
2254
|
+
const command$1 = Argument.choice("command", commands$1).pipe(Argument.withDescription("The command to get the schema for"));
|
|
2255
|
+
/**
|
|
2256
|
+
* Command that prints JSON Schema for a given CLI command.
|
|
2257
|
+
*/
|
|
2258
|
+
const schemaCommand = Command.make("schema", { command: command$1 }, ({ command }) => Effect.gen(function* () {
|
|
2259
|
+
const schema = getSchema(schemas, command);
|
|
2260
|
+
const json = Schema.toJsonSchemaDocument(schema);
|
|
2261
|
+
yield* Console.log(JSON.stringify(json, null, 2));
|
|
2262
|
+
})).pipe(Command.withDescription("Get the schema for a command"));
|
|
2263
|
+
//#endregion
|
|
2264
|
+
//#region src/commands/session-key/create/prompts/base.ts
|
|
2265
|
+
const policyChoices = [
|
|
2266
|
+
{
|
|
2267
|
+
description: "Grant access to all operations",
|
|
2268
|
+
title: "Sudo Permission",
|
|
2269
|
+
value: "sudo"
|
|
2270
|
+
},
|
|
2271
|
+
{
|
|
2272
|
+
description: "Whitelist addresses, contract and functions",
|
|
2273
|
+
title: "Call Permission",
|
|
2274
|
+
value: "call"
|
|
2275
|
+
},
|
|
2276
|
+
{
|
|
2277
|
+
description: "Specify the start and end time for when the key is valid",
|
|
2278
|
+
title: "Timestamp Permission",
|
|
2279
|
+
value: "timestamp"
|
|
2280
|
+
},
|
|
2281
|
+
{
|
|
2282
|
+
description: "Specify the allowed gas usage for the session key",
|
|
2283
|
+
title: "Gas Permission",
|
|
2284
|
+
value: "gas"
|
|
2285
|
+
},
|
|
2286
|
+
{
|
|
2287
|
+
description: "Specify which addresses can verify signatures from this session key",
|
|
2288
|
+
title: "Signature Permission",
|
|
2289
|
+
value: "signature-caller"
|
|
2290
|
+
},
|
|
2291
|
+
{
|
|
2292
|
+
description: "Specify the allowed gas usage for the session key",
|
|
2293
|
+
title: "Rate Limit Permission",
|
|
2294
|
+
value: "rate-limit"
|
|
2295
|
+
},
|
|
2296
|
+
{
|
|
2297
|
+
description: "Complete the session key creation",
|
|
2298
|
+
title: "Done",
|
|
2299
|
+
value: "done"
|
|
2300
|
+
}
|
|
2301
|
+
];
|
|
2302
|
+
const policyChoicePrompt = (prevPolicies) => Effect.gen(function* () {
|
|
2303
|
+
const choices = policyChoices.filter((c) => !prevPolicies.some((p) => p.type === c.value));
|
|
2304
|
+
return yield* Prompt.select({
|
|
2305
|
+
choices,
|
|
2306
|
+
message: "Select Permission type you want to add for this session key"
|
|
2307
|
+
});
|
|
2308
|
+
});
|
|
2309
|
+
//#endregion
|
|
2310
|
+
//#region src/commands/session-key/create/prompts/common.ts
|
|
2311
|
+
const addressPrompt = (message) => Prompt.text({
|
|
2312
|
+
message,
|
|
2313
|
+
validate: (value) => Effect.gen(function* () {
|
|
2314
|
+
const res = Schema.decodeUnknownOption(EthereumAddress)(value);
|
|
2315
|
+
if (Option.isNone(res)) return yield* Effect.fail("Invalid Ethereum Address");
|
|
2316
|
+
return value;
|
|
2317
|
+
})
|
|
2318
|
+
});
|
|
2319
|
+
const etherPrompt = (message) => Prompt.text({
|
|
2320
|
+
message,
|
|
2321
|
+
validate: (val) => Effect.gen(function* () {
|
|
2322
|
+
const schema = Schema.NumberFromString.check(Schema.isGreaterThanOrEqualTo(0));
|
|
2323
|
+
const res = Schema.decodeOption(schema)(val);
|
|
2324
|
+
if (Option.isNone(res)) return yield* Effect.fail("Invalid Number");
|
|
2325
|
+
return res.value.toString();
|
|
2326
|
+
})
|
|
2327
|
+
});
|
|
2328
|
+
//#endregion
|
|
2329
|
+
//#region src/commands/session-key/create/prompts/call.ts
|
|
2330
|
+
const getEOAPermission = () => Effect.gen(function* () {
|
|
2331
|
+
return [{
|
|
2332
|
+
target: yield* addressPrompt("Enter target address:"),
|
|
2333
|
+
valueLimit: parseEther(yield* etherPrompt("Max value that can be transferred (in ETH units):"), "wei")
|
|
2334
|
+
}];
|
|
2335
|
+
});
|
|
2336
|
+
const getSmartContractPermission = () => Effect.gen(function* () {
|
|
2337
|
+
const fs = yield* FileSystem.FileSystem;
|
|
2338
|
+
const address = yield* addressPrompt("Enter smart contract address:");
|
|
2339
|
+
const maxLimit = yield* etherPrompt("Max value that can be transferred (in ETH units)");
|
|
2340
|
+
const abiFilePath = yield* Prompt.file({ message: "Select the ABI file for the smart contract" });
|
|
2341
|
+
const abiString = yield* fs.readFileString(abiFilePath).pipe(Effect.orDie);
|
|
2342
|
+
const functions = JSON.parse(abiString).filter((e) => e.type === "function");
|
|
2343
|
+
const allowedFunctions = yield* Prompt.multiSelect({
|
|
2344
|
+
choices: functions.map((f) => {
|
|
2345
|
+
let signature = `${f.name}(`;
|
|
2346
|
+
for (let i = 0; i < f.inputs.length; i++) {
|
|
2347
|
+
const sep = f.inputs.length === 0 || i === f.inputs.length - 1 ? "" : ", ";
|
|
2348
|
+
const input = f.inputs[i];
|
|
2349
|
+
signature += `${input.type}${input.name ? ` ${input.name}` : ""}${sep}`;
|
|
2350
|
+
}
|
|
2351
|
+
signature += ")";
|
|
2352
|
+
return {
|
|
2353
|
+
description: signature,
|
|
2354
|
+
title: f.name,
|
|
2355
|
+
value: f
|
|
2356
|
+
};
|
|
2357
|
+
}),
|
|
2358
|
+
message: "Select the functions you want to allow",
|
|
2359
|
+
min: 1
|
|
2360
|
+
});
|
|
2361
|
+
const weiUnits = parseEther(maxLimit);
|
|
2362
|
+
return allowedFunctions.map((f) => {
|
|
2363
|
+
return {
|
|
2364
|
+
abi: [f],
|
|
2365
|
+
functionName: f.name,
|
|
2366
|
+
selector: toFunctionSelector(f),
|
|
2367
|
+
target: address,
|
|
2368
|
+
valueLimit: weiUnits
|
|
2369
|
+
};
|
|
2370
|
+
});
|
|
2371
|
+
});
|
|
2372
|
+
const getPermissions = () => Effect.gen(function* () {
|
|
2373
|
+
const permissions = [];
|
|
2374
|
+
const targetAddressPrompt = Prompt.select({
|
|
2375
|
+
choices: [{
|
|
2376
|
+
title: "EOA",
|
|
2377
|
+
value: "eoa"
|
|
2378
|
+
}, {
|
|
2379
|
+
title: "Smart Contract",
|
|
2380
|
+
value: "smart-contract"
|
|
2381
|
+
}],
|
|
2382
|
+
message: "Select target address type"
|
|
2383
|
+
});
|
|
2384
|
+
while (true) {
|
|
2385
|
+
if (permissions.length > 0) {
|
|
2386
|
+
if (!(yield* Prompt.confirm({ message: "Do you want to add another call permission?" }))) break;
|
|
2387
|
+
}
|
|
2388
|
+
if ((yield* targetAddressPrompt) === "eoa") {
|
|
2389
|
+
const res = yield* getEOAPermission();
|
|
2390
|
+
permissions.push(...res);
|
|
2391
|
+
} else {
|
|
2392
|
+
const res = yield* getSmartContractPermission();
|
|
2393
|
+
permissions.push(...res);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
return permissions;
|
|
2397
|
+
});
|
|
2398
|
+
const getCallPolicyParams = Effect.gen(function* () {
|
|
2399
|
+
return {
|
|
2400
|
+
permissions: yield* getPermissions(),
|
|
2401
|
+
policyVersion: "0.0.4",
|
|
2402
|
+
type: "call"
|
|
2403
|
+
};
|
|
2404
|
+
});
|
|
2405
|
+
//#endregion
|
|
2406
|
+
//#region src/commands/session-key/create/prompts/gas.ts
|
|
2407
|
+
const getGasPolicyParams = Effect.gen(function* () {
|
|
2408
|
+
const allowed = yield* etherPrompt("Total amount of gas allowed (in ETH units)");
|
|
2409
|
+
const enforcePaymaster = yield* Prompt.confirm({ message: "Should UserOperations require Paymaster?" });
|
|
2410
|
+
return {
|
|
2411
|
+
amount: parseEther(allowed),
|
|
2412
|
+
type: "gas",
|
|
2413
|
+
enforcePaymaster
|
|
2414
|
+
};
|
|
2415
|
+
});
|
|
2416
|
+
//#endregion
|
|
2417
|
+
//#region src/commands/session-key/create/prompts/rate-limit.ts
|
|
2418
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: safe */
|
|
2419
|
+
const getRateLimitPolicyParams = Effect.gen(function* () {
|
|
2420
|
+
const interval = yield* Prompt.text({
|
|
2421
|
+
message: "Duration for which rate limit is enforced? (e.g. 1 hour, 1 day, 1 week)",
|
|
2422
|
+
validate: (v) => Effect.gen(function* () {
|
|
2423
|
+
if (Duration.fromInput(v)._tag === "None") return yield* Effect.fail("Invalid duration");
|
|
2424
|
+
return v;
|
|
2425
|
+
})
|
|
2426
|
+
});
|
|
2427
|
+
const intervalDuration = Duration.fromInputUnsafe(interval);
|
|
2428
|
+
const count = yield* Prompt.integer({
|
|
2429
|
+
message: "Number of requests allowed per interval.",
|
|
2430
|
+
validate: (v) => Effect.gen(function* () {
|
|
2431
|
+
if (v < 1) return yield* Effect.fail("Count must be greater than 0");
|
|
2432
|
+
return v;
|
|
2433
|
+
})
|
|
2434
|
+
});
|
|
2435
|
+
const startAt = yield* Prompt.date({
|
|
2436
|
+
initial: /* @__PURE__ */ new Date(),
|
|
2437
|
+
message: "Date at which the rate limit is enforced.",
|
|
2438
|
+
validate: (d) => Effect.gen(function* () {
|
|
2439
|
+
if (d < /* @__PURE__ */ new Date()) return yield* Effect.fail("Date must be in the future");
|
|
2440
|
+
return d;
|
|
2441
|
+
})
|
|
2442
|
+
});
|
|
2443
|
+
return {
|
|
2444
|
+
type: "rate-limit",
|
|
2445
|
+
count,
|
|
2446
|
+
interval: Duration.toMillis(intervalDuration),
|
|
2447
|
+
startAt: Math.floor(startAt.getTime() / 1e3)
|
|
2448
|
+
};
|
|
2449
|
+
});
|
|
2450
|
+
//#endregion
|
|
2451
|
+
//#region src/commands/session-key/create/prompts/signature-caller.ts
|
|
2452
|
+
const getSignatureCallerPolicyParams = Effect.gen(function* () {
|
|
2453
|
+
return {
|
|
2454
|
+
type: "signature-caller",
|
|
2455
|
+
allowedCallers: (yield* Prompt.text({
|
|
2456
|
+
message: "Comma-separated list of allowed callers:",
|
|
2457
|
+
validate: (v) => Effect.gen(function* () {
|
|
2458
|
+
const address = v.split(",").map((a) => a.trim());
|
|
2459
|
+
for (let i = 0; i < address.length; i++) {
|
|
2460
|
+
const a = address[i];
|
|
2461
|
+
if (Schema.decodeUnknownOption(EthereumAddress)(a)._tag === "None") return yield* Effect.fail(`Invalid Ethereum Address at index ${i}`);
|
|
2462
|
+
}
|
|
2463
|
+
return v;
|
|
2464
|
+
})
|
|
2465
|
+
})).split(",").map((a) => a.trim()).map((a) => Schema.decodeUnknownSync(EthereumAddress)(a))
|
|
2466
|
+
};
|
|
2467
|
+
});
|
|
2468
|
+
//#endregion
|
|
2469
|
+
//#region src/commands/session-key/create/prompts/timestamp.ts
|
|
2470
|
+
const getTimestampPolicyParams = Effect.gen(function* () {
|
|
2471
|
+
const validAfter = yield* Prompt.date({
|
|
2472
|
+
initial: /* @__PURE__ */ new Date(),
|
|
2473
|
+
message: "Time after which the key becomes valid.",
|
|
2474
|
+
validate: (d) => Effect.gen(function* () {
|
|
2475
|
+
if (d < /* @__PURE__ */ new Date()) return yield* Effect.fail("Date must be in the future");
|
|
2476
|
+
return d;
|
|
2477
|
+
})
|
|
2478
|
+
});
|
|
2479
|
+
const validUntil = yield* Prompt.date({
|
|
2480
|
+
initial: /* @__PURE__ */ new Date(),
|
|
2481
|
+
message: "Time until which the key becomes valid.",
|
|
2482
|
+
validate: (d) => Effect.gen(function* () {
|
|
2483
|
+
if (d < /* @__PURE__ */ new Date()) return yield* Effect.fail("Date must be in the future");
|
|
2484
|
+
if (d < validAfter) return yield* Effect.fail("Valid until must be after valid after");
|
|
2485
|
+
return d;
|
|
2486
|
+
})
|
|
2487
|
+
});
|
|
2488
|
+
return {
|
|
2489
|
+
type: "timestamp",
|
|
2490
|
+
validAfter: Math.floor(validAfter.getTime() / 1e3),
|
|
2491
|
+
validUntil: Math.floor(validUntil.getTime() / 1e3)
|
|
2492
|
+
};
|
|
2493
|
+
});
|
|
2494
|
+
//#endregion
|
|
2495
|
+
//#region src/commands/session-key/create/prompts/index.ts
|
|
2496
|
+
const getPoliciesFromUser = () => Effect.gen(function* () {
|
|
2497
|
+
const policyParams = [];
|
|
2498
|
+
while (true) {
|
|
2499
|
+
if (policyParams.length > 0) {
|
|
2500
|
+
if (!(yield* Prompt.confirm({ message: "Do you want to add another policy?" }))) break;
|
|
2501
|
+
}
|
|
2502
|
+
const policyType = yield* policyChoicePrompt(policyParams);
|
|
2503
|
+
if (policyType === "sudo") {
|
|
2504
|
+
policyParams.push({ type: "sudo" });
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
if (policyType === "timestamp") {
|
|
2508
|
+
const res = yield* getTimestampPolicyParams;
|
|
2509
|
+
policyParams.push(res);
|
|
2510
|
+
} else if (policyType === "call") {
|
|
2511
|
+
const res = yield* getCallPolicyParams;
|
|
2512
|
+
policyParams.push(res);
|
|
2513
|
+
} else if (policyType === "gas") {
|
|
2514
|
+
const res = yield* getGasPolicyParams;
|
|
2515
|
+
policyParams.push(res);
|
|
2516
|
+
} else if (policyType === "rate-limit") {
|
|
2517
|
+
const res = yield* getRateLimitPolicyParams;
|
|
2518
|
+
policyParams.push(res);
|
|
2519
|
+
} else if (policyType === "signature-caller") {
|
|
2520
|
+
const res = yield* getSignatureCallerPolicyParams;
|
|
2521
|
+
policyParams.push(res);
|
|
2522
|
+
} else break;
|
|
2523
|
+
}
|
|
2524
|
+
return policyParams;
|
|
2525
|
+
});
|
|
2526
|
+
//#endregion
|
|
2527
|
+
//#region src/commands/session-key/create/index.ts
|
|
2528
|
+
const handler$10 = (flagAlias, flagSmartAccountAlias, flagSessionKeyPassword, flagOwnerKeystorePassword) => Effect.gen(function* () {
|
|
2529
|
+
const promptManager = yield* PromptManager;
|
|
2530
|
+
const web3Service = yield* Web3Service;
|
|
2531
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
2532
|
+
const sessionKeyManager = yield* SessionKeyManager;
|
|
2533
|
+
const outputFormatter = yield* OutputFormatter;
|
|
2534
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2535
|
+
let params;
|
|
2536
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(CreateSessionKeyParams))(globalFlags.params.value);
|
|
2537
|
+
else {
|
|
2538
|
+
let alias;
|
|
2539
|
+
let smartAccountAlias;
|
|
2540
|
+
let sessionKeyPassword;
|
|
2541
|
+
let ownerKeystorePassword;
|
|
2542
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
2543
|
+
else alias = yield* promptManager.aliasPrompt({
|
|
2544
|
+
aliasType: "new",
|
|
2545
|
+
message: "Enter alias for session key:",
|
|
2546
|
+
type: "session-key"
|
|
2547
|
+
});
|
|
2548
|
+
if (flagSmartAccountAlias._tag === "Some") smartAccountAlias = flagSmartAccountAlias.value;
|
|
2549
|
+
else smartAccountAlias = (yield* smartAccountManager.selectSmartAccount({ message: "Select smart account:" })).alias;
|
|
2550
|
+
const chains = yield* web3Service.multiSelectChain({ message: "Select chains:" });
|
|
2551
|
+
if (flagSessionKeyPassword._tag === "Some") sessionKeyPassword = flagSessionKeyPassword.value;
|
|
2552
|
+
else sessionKeyPassword = yield* promptManager.passwordPrompt({ message: "Enter session key password:" });
|
|
2553
|
+
const sa = yield* smartAccountManager.getSmartAccount({ alias: smartAccountAlias });
|
|
2554
|
+
if (flagOwnerKeystorePassword._tag === "Some") ownerKeystorePassword = flagOwnerKeystorePassword.value;
|
|
2555
|
+
else ownerKeystorePassword = yield* promptManager.passwordPrompt({ message: `Enter keystore password for owner (${sa.data.ownerAlias}):` });
|
|
2556
|
+
const policyParams = yield* getPoliciesFromUser();
|
|
2557
|
+
params = {
|
|
2558
|
+
alias,
|
|
2559
|
+
chains,
|
|
2560
|
+
smartAccountAlias,
|
|
2561
|
+
sessionKeyPassword: Redacted.value(sessionKeyPassword),
|
|
2562
|
+
ownerKeystorePassword: Redacted.value(ownerKeystorePassword),
|
|
2563
|
+
policyParams
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
const res = yield* sessionKeyManager.createSessionKey(params);
|
|
2567
|
+
const data = {
|
|
2568
|
+
...res.data.sessionKeyType === "ecdsa" ? { sessionKeyAddress: res.data.sessionKeyAddress } : { passKeyName: res.data.passKeyName },
|
|
2569
|
+
smartAccount: res.data.smartAccountAlias,
|
|
2570
|
+
chains: res.data.serializedAccounts.map((a) => getChain(a.chain).name)
|
|
2571
|
+
};
|
|
2572
|
+
if (globalFlags.quite) return;
|
|
2573
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2574
|
+
yield* Console.log(output);
|
|
2575
|
+
});
|
|
2576
|
+
const alias$7 = Flag.string("alias").pipe(Flag.withDescription("The alias of the session key to create"), Flag.withAlias("a"), Flag.optional);
|
|
2577
|
+
const smartAccountAlias$1 = Flag.string("smart-account").pipe(Flag.withDescription("The alias of the smart account to create session key for"), Flag.withAlias("sa"), Flag.optional);
|
|
2578
|
+
const sessionKeyPassword = Flag.redacted("password").pipe(Flag.withDescription("Password to encrypt session key"), Flag.withAlias("p"), Flag.optional);
|
|
2579
|
+
const ownerKeystorePassword = Flag.redacted("owner-password").pipe(Flag.withDescription("Password to encrypt session key"), Flag.withAlias("op"), Flag.optional);
|
|
2580
|
+
/**
|
|
2581
|
+
* Command that creates a new session key and stores it locally.
|
|
2582
|
+
*/
|
|
2583
|
+
const createSessionKeyCommand = Command.make("create", {
|
|
2584
|
+
alias: alias$7,
|
|
2585
|
+
smartAccountAlias: smartAccountAlias$1,
|
|
2586
|
+
sessionKeyPassword,
|
|
2587
|
+
ownerKeystorePassword
|
|
2588
|
+
}, ({ alias, smartAccountAlias, sessionKeyPassword, ownerKeystorePassword }) => handler$10(alias, smartAccountAlias, sessionKeyPassword, ownerKeystorePassword)).pipe(Command.withAlias("c"), Command.withDescription("Creates a new session key"), Command.withExamples([{
|
|
2589
|
+
command: "namera session-key create",
|
|
2590
|
+
description: "Creates a new session key with interactive prompts"
|
|
2591
|
+
}, {
|
|
2592
|
+
command: "namera schema session-key.create",
|
|
2593
|
+
description: "Get the schema for session key create command"
|
|
2594
|
+
}]));
|
|
2595
|
+
//#endregion
|
|
2596
|
+
//#region src/helpers/humanize.ts
|
|
2597
|
+
const humanizeDuration = (input) => {
|
|
2598
|
+
const millis = Duration.toMillis(Duration.fromInputUnsafe(input));
|
|
2599
|
+
let seconds = Math.floor(millis / 1e3);
|
|
2600
|
+
const units = [
|
|
2601
|
+
{
|
|
2602
|
+
label: "year",
|
|
2603
|
+
value: 3600 * 24 * 365
|
|
2604
|
+
},
|
|
2605
|
+
{
|
|
2606
|
+
label: "month",
|
|
2607
|
+
value: 3600 * 24 * 30
|
|
2608
|
+
},
|
|
2609
|
+
{
|
|
2610
|
+
label: "day",
|
|
2611
|
+
value: 3600 * 24
|
|
2612
|
+
},
|
|
2613
|
+
{
|
|
2614
|
+
label: "hour",
|
|
2615
|
+
value: 3600
|
|
2616
|
+
},
|
|
2617
|
+
{
|
|
2618
|
+
label: "minute",
|
|
2619
|
+
value: 60
|
|
2620
|
+
},
|
|
2621
|
+
{
|
|
2622
|
+
label: "second",
|
|
2623
|
+
value: 1
|
|
2624
|
+
}
|
|
2625
|
+
];
|
|
2626
|
+
const parts = [];
|
|
2627
|
+
for (const unit of units) if (seconds >= unit.value) {
|
|
2628
|
+
const count = Math.floor(seconds / unit.value);
|
|
2629
|
+
seconds %= unit.value;
|
|
2630
|
+
parts.push(`${count} ${unit.label}${count > 1 ? "s" : ""}`);
|
|
2631
|
+
}
|
|
2632
|
+
return parts.length > 0 ? parts.join(" ") : "0 seconds";
|
|
2633
|
+
};
|
|
2634
|
+
const humanizeRelativeTime = (target) => {
|
|
2635
|
+
const now = Date.now();
|
|
2636
|
+
const diff = (target instanceof Date ? target.getTime() : target * 1e3) - now;
|
|
2637
|
+
const abs = Math.abs(diff);
|
|
2638
|
+
const seconds = Math.floor(abs / 1e3);
|
|
2639
|
+
for (const unit of [
|
|
2640
|
+
{
|
|
2641
|
+
label: "year",
|
|
2642
|
+
value: 31536e3
|
|
2643
|
+
},
|
|
2644
|
+
{
|
|
2645
|
+
label: "month",
|
|
2646
|
+
value: 2592e3
|
|
2647
|
+
},
|
|
2648
|
+
{
|
|
2649
|
+
label: "day",
|
|
2650
|
+
value: 86400
|
|
2651
|
+
},
|
|
2652
|
+
{
|
|
2653
|
+
label: "hour",
|
|
2654
|
+
value: 3600
|
|
2655
|
+
},
|
|
2656
|
+
{
|
|
2657
|
+
label: "minute",
|
|
2658
|
+
value: 60
|
|
2659
|
+
},
|
|
2660
|
+
{
|
|
2661
|
+
label: "second",
|
|
2662
|
+
value: 1
|
|
2663
|
+
}
|
|
2664
|
+
]) if (seconds >= unit.value) {
|
|
2665
|
+
const count = Math.floor(seconds / unit.value);
|
|
2666
|
+
const formatted = `${count} ${unit.label}${count > 1 ? "s" : ""}`;
|
|
2667
|
+
return diff > 0 ? `in ${formatted}` : `${formatted} ago`;
|
|
2668
|
+
}
|
|
2669
|
+
return "just now";
|
|
2670
|
+
};
|
|
2671
|
+
const humanizePolicyParams = (serializedAccount) => {
|
|
2672
|
+
const params = deserializePermissionAccountParams(serializedAccount);
|
|
2673
|
+
const result = {};
|
|
2674
|
+
result.permissionId = params.permissionParams.permissionId;
|
|
2675
|
+
for (const policy of params.permissionParams.policies ?? []) {
|
|
2676
|
+
const params = policy.policyParams;
|
|
2677
|
+
if (params.type === "sudo") result.sudoPolicy = { enabled: true };
|
|
2678
|
+
else if (params.type === "gas") result.gasPolicy = {
|
|
2679
|
+
enabled: true,
|
|
2680
|
+
allowed: `${formatEther(params.allowed ?? 0n)} ETH`,
|
|
2681
|
+
...params.enforcePaymaster && { enforcePaymaster: params.enforcePaymaster },
|
|
2682
|
+
...params.allowedPaymaster !== zeroAddress && { allowedPaymaster: params.allowedPaymaster }
|
|
2683
|
+
};
|
|
2684
|
+
else if (params.type === "timestamp") {
|
|
2685
|
+
const validAfter = params.validAfter ?? 0;
|
|
2686
|
+
const validUntil = params.validUntil ?? 0;
|
|
2687
|
+
result.timestampPolicy = {
|
|
2688
|
+
enabled: true,
|
|
2689
|
+
starts: humanizeRelativeTime(validAfter),
|
|
2690
|
+
ends: humanizeRelativeTime(validUntil)
|
|
2691
|
+
};
|
|
2692
|
+
} else if (params.type === "rate-limit") {
|
|
2693
|
+
const formattedInterval = humanizeDuration(params.interval ?? 0);
|
|
2694
|
+
result.rateLimitPolicy = {
|
|
2695
|
+
enabled: true,
|
|
2696
|
+
count: params.count,
|
|
2697
|
+
interval: formattedInterval,
|
|
2698
|
+
starts: humanizeRelativeTime(params.startAt ?? 0)
|
|
2699
|
+
};
|
|
2700
|
+
} else if (params.type === "signature-caller") result.signatureCallerPolicy = {
|
|
2701
|
+
enabled: true,
|
|
2702
|
+
allowedCallers: params.allowedCallers
|
|
2703
|
+
};
|
|
2704
|
+
else if (params.type === "call") {
|
|
2705
|
+
result.callPolicy = {
|
|
2706
|
+
enabled: true,
|
|
2707
|
+
policyVersion: params.policyVersion
|
|
2708
|
+
};
|
|
2709
|
+
if (!params.permissions || params.permissions.length === 0) continue;
|
|
2710
|
+
for (const permission of params.permissions) {
|
|
2711
|
+
result.callPolicy.permissions = result.callPolicy.permissions ?? [];
|
|
2712
|
+
if ("abi" in permission) {
|
|
2713
|
+
const formattedValue = formatEther(permission.valueLimit ?? 0n);
|
|
2714
|
+
const abiFn = permission.abi?.find((f) => f.type === "function" && f.name === permission.functionName);
|
|
2715
|
+
const cyan = "\x1B[36m";
|
|
2716
|
+
const yellow = "\x1B[33m";
|
|
2717
|
+
const magenta = "\x1B[35m";
|
|
2718
|
+
const reset = "\x1B[0m";
|
|
2719
|
+
let fnName = `${magenta}${permission.functionName ?? "unknown"}${reset}(`;
|
|
2720
|
+
if (abiFn) abiFn.inputs.forEach((input, i) => {
|
|
2721
|
+
if (i > 0) fnName += ", ";
|
|
2722
|
+
fnName += `${cyan}${input.type}${reset}${input.name ? ` ${yellow}${input.name}${reset}` : ""}`;
|
|
2723
|
+
});
|
|
2724
|
+
fnName += ")";
|
|
2725
|
+
result.callPolicy.permissions.push({
|
|
2726
|
+
targetAddress: permission.target,
|
|
2727
|
+
valueLimit: `${formattedValue} ETH`,
|
|
2728
|
+
functionName: fnName
|
|
2729
|
+
});
|
|
2730
|
+
} else {
|
|
2731
|
+
const formattedValue = formatEther(permission.valueLimit ?? 0n);
|
|
2732
|
+
result.callPolicy.permissions.push({
|
|
2733
|
+
targetAddress: permission.target,
|
|
2734
|
+
valueLimit: `${formattedValue} ETH`
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
return result;
|
|
2741
|
+
};
|
|
2742
|
+
//#endregion
|
|
2743
|
+
//#region src/commands/session-key/info.ts
|
|
2744
|
+
const handler$9 = (flagAlias) => Effect.gen(function* () {
|
|
2745
|
+
const sessionKeyManager = yield* SessionKeyManager;
|
|
2746
|
+
const outputFormatter = yield* OutputFormatter;
|
|
2747
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2748
|
+
let params;
|
|
2749
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(GetSessionKeyInfoParams))(globalFlags.params.value);
|
|
2750
|
+
else {
|
|
2751
|
+
let alias;
|
|
2752
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
2753
|
+
else alias = (yield* sessionKeyManager.selectSessionKey({ message: "Select Session Key:" })).alias;
|
|
2754
|
+
params = { alias };
|
|
2755
|
+
}
|
|
2756
|
+
const res = yield* sessionKeyManager.getSessionKey(params);
|
|
2757
|
+
const data = {
|
|
2758
|
+
alias: res.alias,
|
|
2759
|
+
...res.data.sessionKeyType === "ecdsa" ? { address: res.data.sessionKeyAddress } : { passKeyName: res.data.passKeyName },
|
|
2760
|
+
smartAccount: res.data.smartAccountAlias,
|
|
2761
|
+
chains: res.data.serializedAccounts.map((a) => getChain(a.chain).name).join(", "),
|
|
2762
|
+
policies: humanizePolicyParams(res.data.serializedAccounts[0]?.serializedAccount ?? "")
|
|
2763
|
+
};
|
|
2764
|
+
if (globalFlags.quite) return;
|
|
2765
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2766
|
+
yield* Console.log(output);
|
|
2767
|
+
});
|
|
2768
|
+
const alias$6 = Flag.string("alias").pipe(Flag.withDescription("The alias of the session key to get info for"), Flag.withAlias("a"), Flag.optional);
|
|
2769
|
+
const getSessionKeyInfoCommand = Command.make("info", { alias: alias$6 }, ({ alias }) => handler$9(alias)).pipe(Command.withDescription("Get information about a session key"), Command.withExamples([
|
|
2770
|
+
{
|
|
2771
|
+
command: "namera session-key info",
|
|
2772
|
+
description: "Get information about a session key with alias prompt"
|
|
2773
|
+
},
|
|
2774
|
+
{
|
|
2775
|
+
command: "namera session-key info --alias my-session-key",
|
|
2776
|
+
description: "Get information about a session key with alias 'my-session-key'"
|
|
2777
|
+
},
|
|
2778
|
+
{
|
|
2779
|
+
command: `namera session-key info --params '{"alias":"my-session-key"}'`,
|
|
2780
|
+
description: "Get information about a session key with json params"
|
|
2781
|
+
},
|
|
2782
|
+
{
|
|
2783
|
+
command: "namera session-key info --alias my-wallet --output json",
|
|
2784
|
+
description: "Get information about a session key in json format"
|
|
2785
|
+
},
|
|
2786
|
+
{
|
|
2787
|
+
command: "namera schema session-key.info",
|
|
2788
|
+
description: "Get the schema for the session key info command"
|
|
2789
|
+
}
|
|
2790
|
+
]));
|
|
2791
|
+
//#endregion
|
|
2792
|
+
//#region src/commands/session-key/list.ts
|
|
2793
|
+
const handler$8 = (flagSmartAccountAlias) => Effect.gen(function* () {
|
|
2794
|
+
const sessionKeyManager = yield* SessionKeyManager;
|
|
2795
|
+
const outputFormatter = yield* OutputFormatter;
|
|
2796
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2797
|
+
const data = (yield* sessionKeyManager.listSessionKeys({ smartAccount: flagSmartAccountAlias._tag === "Some" ? flagSmartAccountAlias.value : void 0 })).map((d) => ({
|
|
2798
|
+
alias: d.alias,
|
|
2799
|
+
...d.data.sessionKeyType === "ecdsa" ? { address: d.data.sessionKeyAddress } : { passKeyName: d.data.passKeyName },
|
|
2800
|
+
smartAccount: d.data.smartAccountAlias,
|
|
2801
|
+
chains: d.data.serializedAccounts.map((a) => getChain(a.chain).name).join(", ")
|
|
2802
|
+
}));
|
|
2803
|
+
if (globalFlags.quite) return;
|
|
2804
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2805
|
+
yield* Console.log(output);
|
|
2806
|
+
});
|
|
2807
|
+
const smartAccountAlias = Flag.string("smart-account").pipe(Flag.withDescription("The alias of the smart account to list session keys for"), Flag.withAlias("sa"), Flag.optional);
|
|
2808
|
+
/**
|
|
2809
|
+
* Command that lists all locally stored session keys.
|
|
2810
|
+
*/
|
|
2811
|
+
const listSessionKeysCommand = Command.make("list", { smartAccountAlias }, ({ smartAccountAlias }) => handler$8(smartAccountAlias)).pipe(Command.withAlias("ls"), Command.withDescription("List session keys"), Command.withExamples([
|
|
2812
|
+
{
|
|
2813
|
+
command: "namera session-key list",
|
|
2814
|
+
description: "List all session keys"
|
|
2815
|
+
},
|
|
2816
|
+
{
|
|
2817
|
+
command: "namera session-key list --smart-account my-smart",
|
|
2818
|
+
description: "List all session keys for smart account 'my-smart'"
|
|
2819
|
+
},
|
|
2820
|
+
{
|
|
2821
|
+
command: `namera session-key list --params '{"smartAccount":"my-smart"}'`,
|
|
2822
|
+
description: "List all session keys for smart account 'my-smart'"
|
|
2823
|
+
},
|
|
2824
|
+
{
|
|
2825
|
+
command: "namera session-key list --output json",
|
|
2826
|
+
description: "List all session keys in json format"
|
|
2827
|
+
},
|
|
2828
|
+
{
|
|
2829
|
+
command: "namera schema session-key.list",
|
|
2830
|
+
description: "Get the schema for the list session key command"
|
|
2831
|
+
}
|
|
2832
|
+
]));
|
|
2833
|
+
//#endregion
|
|
2834
|
+
//#region src/commands/session-key/remove.ts
|
|
2835
|
+
const handler$7 = (flagAlias) => Effect.gen(function* () {
|
|
2836
|
+
const sessionKeyManager = yield* SessionKeyManager;
|
|
2837
|
+
const outputFormatter = yield* OutputFormatter;
|
|
2838
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2839
|
+
let params;
|
|
2840
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(RemoveSessionKeyParams))(globalFlags.params.value);
|
|
2841
|
+
else {
|
|
2842
|
+
let alias;
|
|
2843
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
2844
|
+
else alias = (yield* sessionKeyManager.selectSessionKey({ message: "Select Session Key:" })).alias;
|
|
2845
|
+
params = { alias };
|
|
2846
|
+
}
|
|
2847
|
+
yield* sessionKeyManager.removeSessionKey(params);
|
|
2848
|
+
const data = { success: true };
|
|
2849
|
+
if (globalFlags.quite) return;
|
|
2850
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2851
|
+
yield* Console.log(output);
|
|
2852
|
+
});
|
|
2853
|
+
const alias$5 = Flag.string("alias").pipe(Flag.withDescription("The alias of the session key to remove"), Flag.withAlias("a"), Flag.optional);
|
|
2854
|
+
const removeSessionKeyCommand = Command.make("remove", { alias: alias$5 }, ({ alias }) => handler$7(alias)).pipe(Command.withAlias("rm"), Command.withDescription("Remove a Session Key"), Command.withExamples([
|
|
2855
|
+
{
|
|
2856
|
+
command: "namera session-key remove",
|
|
2857
|
+
description: "Remove a session key with alias prompt"
|
|
2858
|
+
},
|
|
2859
|
+
{
|
|
2860
|
+
command: "namera session-key remove --alias my-session-key",
|
|
2861
|
+
description: "Remove a session key with alias 'my-session-key'"
|
|
2862
|
+
},
|
|
2863
|
+
{
|
|
2864
|
+
command: `namera session-key remove --params '{"alias":"my-session-key"}'`,
|
|
2865
|
+
description: "Remove a session key with json params"
|
|
2866
|
+
},
|
|
2867
|
+
{
|
|
2868
|
+
command: "namera schema session-key.remove",
|
|
2869
|
+
description: "Get the schema for the remove session key command"
|
|
2870
|
+
}
|
|
2871
|
+
]));
|
|
2872
|
+
const chainAndRpcUrl = {
|
|
2873
|
+
chain: Flag.choice("chain", Object.keys(supportedChains)).pipe(Flag.withDescription("Chain Name"), Flag.optional),
|
|
2874
|
+
rpcUrl: Flag.string("rpc-url").pipe(Flag.withDescription("RPC URL"), Flag.optional)
|
|
2875
|
+
};
|
|
2876
|
+
//#endregion
|
|
2877
|
+
//#region src/commands/session-key/status.ts
|
|
2878
|
+
const handler$6 = (flagAlias, flagChain, flagRpcUrl) => Effect.gen(function* () {
|
|
2879
|
+
const web3Service = yield* Web3Service;
|
|
2880
|
+
const sessionKeyManager = yield* SessionKeyManager;
|
|
2881
|
+
const outputFormatter = yield* OutputFormatter;
|
|
2882
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2883
|
+
let params;
|
|
2884
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(GetSessionKeyStatusParams))(globalFlags.params.value);
|
|
2885
|
+
else {
|
|
2886
|
+
let alias;
|
|
2887
|
+
let chain;
|
|
2888
|
+
const rpcUrl = flagRpcUrl._tag === "Some" ? flagRpcUrl.value : void 0;
|
|
2889
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
2890
|
+
else alias = (yield* sessionKeyManager.selectSessionKey({ message: "Select Session Key:" })).alias;
|
|
2891
|
+
if (flagChain._tag === "Some") chain = flagChain.value;
|
|
2892
|
+
else chain = yield* web3Service.selectChain({ message: "Select Chain:" });
|
|
2893
|
+
params = {
|
|
2894
|
+
alias,
|
|
2895
|
+
chain,
|
|
2896
|
+
rpcUrl
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
const data = { installed: yield* sessionKeyManager.getSessionKeyStatus(params) };
|
|
2900
|
+
if (globalFlags.quite) return;
|
|
2901
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2902
|
+
yield* Console.log(output);
|
|
2903
|
+
});
|
|
2904
|
+
const alias$4 = Flag.string("alias").pipe(Flag.withDescription("The alias of the session key"), Flag.withAlias("a"), Flag.optional);
|
|
2905
|
+
const getSessionKeyStatusCommand = Command.make("status", {
|
|
2906
|
+
alias: alias$4,
|
|
2907
|
+
...chainAndRpcUrl
|
|
2908
|
+
}, ({ alias, chain, rpcUrl }) => handler$6(alias, chain, rpcUrl)).pipe(Command.withDescription("Get Session key status for a given chain"), Command.withExamples([
|
|
2909
|
+
{
|
|
2910
|
+
command: "namera session-key status -a my-smart --chain eth-mainnet",
|
|
2911
|
+
description: "Get Session key status for a given chain"
|
|
2912
|
+
},
|
|
2913
|
+
{
|
|
2914
|
+
command: "namera session-key status --alias my-smart --chain eth-mainnet --rpc-url https://rpc.ankr.com/eth",
|
|
2915
|
+
description: "Get Session key status for a given chain with custom RPC URL"
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
command: `namera session-key status --params '{"alias":"my-session-key","chain":"eth-mainnet"}'`,
|
|
2919
|
+
description: "Get Session key status for a given chain with json params"
|
|
2920
|
+
},
|
|
2921
|
+
{
|
|
2922
|
+
command: "namera schema session-key.status",
|
|
2923
|
+
description: "Get the schema for the session key status command"
|
|
2924
|
+
}
|
|
2925
|
+
]));
|
|
2926
|
+
//#endregion
|
|
2927
|
+
//#region src/commands/session-key/index.ts
|
|
2928
|
+
/**
|
|
2929
|
+
* Command group for session-key related operations.
|
|
2930
|
+
*/
|
|
2931
|
+
const sessionKeyCommands = Command.make("session-key", {}, () => Effect.void).pipe(Command.withAlias("sk"), Command.withDescription("Session Key management commands."), Command.withSubcommands([
|
|
2932
|
+
createSessionKeyCommand,
|
|
2933
|
+
listSessionKeysCommand,
|
|
2934
|
+
getSessionKeyInfoCommand,
|
|
2935
|
+
getSessionKeyStatusCommand,
|
|
2936
|
+
removeSessionKeyCommand
|
|
2937
|
+
]));
|
|
2938
|
+
//#endregion
|
|
2939
|
+
//#region src/commands/smart-account/create.ts
|
|
2940
|
+
const handler$5 = (flagAlias, flagOwnerAlias, flagOwnerPassword, flagIndex) => Effect.gen(function* () {
|
|
2941
|
+
const promptManager = yield* PromptManager;
|
|
2942
|
+
const keystoreManager = yield* KeystoreManager;
|
|
2943
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
2944
|
+
const outputFormatter = yield* OutputFormatter;
|
|
2945
|
+
const globalFlags = yield* getGlobalFlags();
|
|
2946
|
+
let params;
|
|
2947
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(CreateSmartAccountParams))(globalFlags.params.value);
|
|
2948
|
+
else {
|
|
2949
|
+
let alias;
|
|
2950
|
+
let ownerAlias;
|
|
2951
|
+
let ownerPassword;
|
|
2952
|
+
let index;
|
|
2953
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
2954
|
+
else alias = yield* promptManager.aliasPrompt({
|
|
2955
|
+
aliasType: "new",
|
|
2956
|
+
message: "Enter alias for smart account:",
|
|
2957
|
+
type: "smart-account"
|
|
2958
|
+
});
|
|
2959
|
+
if (flagOwnerAlias._tag === "Some") ownerAlias = flagOwnerAlias.value;
|
|
2960
|
+
else ownerAlias = (yield* keystoreManager.selectKeystore({ message: "Select owner keystore:" })).alias;
|
|
2961
|
+
if (flagOwnerPassword._tag === "Some") ownerPassword = flagOwnerPassword.value;
|
|
2962
|
+
else ownerPassword = yield* keystoreManager.getKeystorePassword({
|
|
2963
|
+
alias: ownerAlias,
|
|
2964
|
+
message: `Enter password for owner (${ownerAlias}):`
|
|
2965
|
+
});
|
|
2966
|
+
if (flagIndex._tag === "Some") index = BigInt(flagIndex.value);
|
|
2967
|
+
else index = 0n;
|
|
2968
|
+
params = {
|
|
2969
|
+
alias,
|
|
2970
|
+
ownerAlias,
|
|
2971
|
+
index,
|
|
2972
|
+
ownerPassword: Redacted.value(ownerPassword)
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
const res = yield* smartAccountManager.createSmartAccount(params);
|
|
2976
|
+
const data = {
|
|
2977
|
+
address: res.data.smartAccountAddress,
|
|
2978
|
+
kernelVersion: res.data.kernelVersion,
|
|
2979
|
+
index: Number(res.data.index),
|
|
2980
|
+
owner: res.data.ownerAlias
|
|
2981
|
+
};
|
|
2982
|
+
if (globalFlags.quite) return;
|
|
2983
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
2984
|
+
yield* Console.log(output);
|
|
2985
|
+
});
|
|
2986
|
+
const alias$3 = Flag.string("alias").pipe(Flag.withDescription("The alias of the smart account to create"), Flag.withAlias("a"), Flag.optional);
|
|
2987
|
+
const ownerAlias = Flag.string("owner-alias").pipe(Flag.withDescription("The alias of the owner keystore"), Flag.withAlias("oa"), Flag.optional);
|
|
2988
|
+
const ownerPassword = Flag.redacted("owner-password").pipe(Flag.withDescription("The password of the owner keystore"), Flag.withAlias("op"), Flag.optional);
|
|
2989
|
+
const index = Flag.integer("index").pipe(Flag.withDescription("The index of the smart account"), Flag.withAlias("i"), Flag.optional);
|
|
2990
|
+
/**
|
|
2991
|
+
* Command that creates a new smart account and stores it locally.
|
|
2992
|
+
*
|
|
2993
|
+
* Accepts alias, owner alias, and index via flags or JSON params.
|
|
2994
|
+
*/
|
|
2995
|
+
const createSmartAccountCommand = Command.make("create", {
|
|
2996
|
+
alias: alias$3,
|
|
2997
|
+
ownerAlias,
|
|
2998
|
+
ownerPassword,
|
|
2999
|
+
index
|
|
3000
|
+
}, ({ alias, ownerAlias, ownerPassword, index }) => handler$5(alias, ownerAlias, ownerPassword, index)).pipe(Command.withAlias("c"), Command.withDescription("Creates a new smart account"), Command.withExamples([
|
|
3001
|
+
{
|
|
3002
|
+
command: "namera smart-account create",
|
|
3003
|
+
description: "Creates a new smart account with interactive prompts"
|
|
3004
|
+
},
|
|
3005
|
+
{
|
|
3006
|
+
command: "namera smart-account create --alias my-smart --owner-alias my-owner --index 0",
|
|
3007
|
+
description: "Creates a new smart account with alias 'my-smart', owner alias 'my-owner', and index 0"
|
|
3008
|
+
},
|
|
3009
|
+
{
|
|
3010
|
+
command: `namera smart-account create --params '{"alias":"my-smart","owner-alias":"my-owner","index":0}'`,
|
|
3011
|
+
description: "Creates a new smart account with json params"
|
|
3012
|
+
},
|
|
3013
|
+
{
|
|
3014
|
+
command: "namera schema smart-account.create",
|
|
3015
|
+
description: "Get the schema for the create command"
|
|
3016
|
+
}
|
|
3017
|
+
]));
|
|
3018
|
+
//#endregion
|
|
3019
|
+
//#region src/commands/smart-account/import.ts
|
|
3020
|
+
const handler$4 = () => Effect.gen(function* () {
|
|
3021
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
3022
|
+
const outputFormatter = yield* OutputFormatter;
|
|
3023
|
+
const globalFlags = yield* getGlobalFlags();
|
|
3024
|
+
if (globalFlags.params._tag === "None") return yield* Effect.fail(new SmartAccountManagerError({
|
|
3025
|
+
code: "SmartAccountImportError",
|
|
3026
|
+
message: "Smart Account import is only supported via JSON params"
|
|
3027
|
+
}));
|
|
3028
|
+
const params = Schema.decodeUnknownSync(Schema.fromJsonString(ImportSmartAccountParams))(globalFlags.params.value);
|
|
3029
|
+
const res = yield* smartAccountManager.importSmartAccount(params);
|
|
3030
|
+
const data = {
|
|
3031
|
+
alias: res.alias,
|
|
3032
|
+
address: res.data.smartAccountAddress,
|
|
3033
|
+
owner: res.data.ownerAlias,
|
|
3034
|
+
index: Number(res.data.index),
|
|
3035
|
+
kernelVersion: res.data.kernelVersion
|
|
3036
|
+
};
|
|
3037
|
+
if (globalFlags.quite) return;
|
|
3038
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
3039
|
+
yield* Console.log(output);
|
|
3040
|
+
});
|
|
3041
|
+
/**
|
|
3042
|
+
* Command that imports a smart account using JSON params only.
|
|
3043
|
+
*/
|
|
3044
|
+
const importSmartAccountCommand = Command.make("import", {}, () => handler$4()).pipe(Command.withAlias("i"), Command.withDescription("Import a smart account"), Command.withExamples([{
|
|
3045
|
+
command: `namera smart-account import --params '{"alias":"my-smart","ownerAlias":"my-owner","index":0,"kernelVersion":"0.3.2","smartAccountAddress":"0x1bC710cbA70f8Ce638dC5c8F50FDb05d87a7D652","entryPointVersion":"0.7","ownerType":"ecdsa"}'`,
|
|
3046
|
+
description: "Import a smart account with json params"
|
|
3047
|
+
}, {
|
|
3048
|
+
command: "namera schema smart-account.import",
|
|
3049
|
+
description: "Get the schema for the smart account import command"
|
|
3050
|
+
}]));
|
|
3051
|
+
//#endregion
|
|
3052
|
+
//#region src/commands/smart-account/info.ts
|
|
3053
|
+
const handler$3 = (flagAlias) => Effect.gen(function* () {
|
|
3054
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
3055
|
+
const outputFormatter = yield* OutputFormatter;
|
|
3056
|
+
const globalFlags = yield* getGlobalFlags();
|
|
3057
|
+
let params;
|
|
3058
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(GetKeystoreParams))(globalFlags.params.value);
|
|
3059
|
+
else {
|
|
3060
|
+
let alias;
|
|
3061
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
3062
|
+
else alias = (yield* smartAccountManager.selectSmartAccount({ message: "Select Smart Account:" })).alias;
|
|
3063
|
+
params = { alias };
|
|
3064
|
+
}
|
|
3065
|
+
const res = yield* smartAccountManager.getSmartAccount(params);
|
|
3066
|
+
const data = {
|
|
3067
|
+
alias: res.alias,
|
|
3068
|
+
address: res.data.smartAccountAddress,
|
|
3069
|
+
owner: res.data.ownerAlias,
|
|
3070
|
+
index: Number(res.data.index),
|
|
3071
|
+
kernelVersion: res.data.kernelVersion
|
|
3072
|
+
};
|
|
3073
|
+
if (globalFlags.quite) return;
|
|
3074
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
3075
|
+
yield* Console.log(output);
|
|
3076
|
+
});
|
|
3077
|
+
const alias$2 = Flag.string("alias").pipe(Flag.withDescription("The alias of the smart account to retrieve"), Flag.withAlias("a"), Flag.optional);
|
|
3078
|
+
/**
|
|
3079
|
+
* Command that returns stored metadata for a smart account.
|
|
3080
|
+
*
|
|
3081
|
+
* Accepts alias via flag or JSON params, otherwise prompts for selection.
|
|
3082
|
+
*/
|
|
3083
|
+
const getSmartAccountInfoCommand = Command.make("info", { alias: alias$2 }, ({ alias }) => handler$3(alias)).pipe(Command.withDescription("Get information about a smart account"), Command.withExamples([
|
|
3084
|
+
{
|
|
3085
|
+
command: "namera smart-account info",
|
|
3086
|
+
description: "Get information about a smart account with alias prompt"
|
|
3087
|
+
},
|
|
3088
|
+
{
|
|
3089
|
+
command: "namera smart-account info --alias my-smart",
|
|
3090
|
+
description: "Get information about a smart account with alias 'my-smart'"
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
command: `namera smart-account info --params '{"alias":"my-smart"}'`,
|
|
3094
|
+
description: "Get information about a smart account with json params"
|
|
3095
|
+
},
|
|
3096
|
+
{
|
|
3097
|
+
command: "namera smart-account info --alias my-wallet --output json",
|
|
3098
|
+
description: "Get information about a smart account in json format"
|
|
3099
|
+
},
|
|
3100
|
+
{
|
|
3101
|
+
command: "namera schema smart-account.info",
|
|
3102
|
+
description: "Get the schema for the smart account info command"
|
|
3103
|
+
}
|
|
3104
|
+
]));
|
|
3105
|
+
//#endregion
|
|
3106
|
+
//#region src/commands/smart-account/list.ts
|
|
3107
|
+
const handler$2 = () => Effect.gen(function* () {
|
|
3108
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
3109
|
+
const outputFormatter = yield* OutputFormatter;
|
|
3110
|
+
const globalFlags = yield* getGlobalFlags();
|
|
3111
|
+
const data = (yield* smartAccountManager.listSmartAccounts()).map((d) => ({
|
|
3112
|
+
alias: d.alias,
|
|
3113
|
+
address: d.data.smartAccountAddress,
|
|
3114
|
+
owner: d.data.ownerAlias,
|
|
3115
|
+
index: Number(d.data.index),
|
|
3116
|
+
kernelVersion: d.data.kernelVersion
|
|
3117
|
+
}));
|
|
3118
|
+
if (globalFlags.quite) return;
|
|
3119
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
3120
|
+
yield* Console.log(output);
|
|
3121
|
+
});
|
|
3122
|
+
/**
|
|
3123
|
+
* Command that lists all locally stored smart accounts.
|
|
3124
|
+
*/
|
|
3125
|
+
const listSmartAccountsCommand = Command.make("list", {}, () => handler$2()).pipe(Command.withAlias("ls"), Command.withDescription("List all smart accounts"), Command.withExamples([
|
|
3126
|
+
{
|
|
3127
|
+
command: "namera smart-account list",
|
|
3128
|
+
description: "List all smart accounts"
|
|
3129
|
+
},
|
|
3130
|
+
{
|
|
3131
|
+
command: "namera smart-account list --output json",
|
|
3132
|
+
description: "List all smart accounts in json format"
|
|
3133
|
+
},
|
|
3134
|
+
{
|
|
3135
|
+
command: "namera smart-account list --output ndjson",
|
|
3136
|
+
description: "List all smart accounts in ndjson format"
|
|
3137
|
+
},
|
|
3138
|
+
{
|
|
3139
|
+
command: "namera schema smart-account.list",
|
|
3140
|
+
description: "Get the schema for the list smart account command"
|
|
3141
|
+
}
|
|
3142
|
+
]));
|
|
3143
|
+
//#endregion
|
|
3144
|
+
//#region src/commands/smart-account/remove.ts
|
|
3145
|
+
const handler$1 = (flagAlias) => Effect.gen(function* () {
|
|
3146
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
3147
|
+
const outputFormatter = yield* OutputFormatter;
|
|
3148
|
+
const globalFlags = yield* getGlobalFlags();
|
|
3149
|
+
let params;
|
|
3150
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(RemoveSmartAccountParams))(globalFlags.params.value);
|
|
3151
|
+
else {
|
|
3152
|
+
let alias;
|
|
3153
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
3154
|
+
else alias = (yield* smartAccountManager.selectSmartAccount({ message: "Select Smart Account:" })).alias;
|
|
3155
|
+
params = { alias };
|
|
3156
|
+
}
|
|
3157
|
+
yield* smartAccountManager.removeSmartAccount(params);
|
|
3158
|
+
const data = { success: true };
|
|
3159
|
+
if (globalFlags.quite) return;
|
|
3160
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
3161
|
+
yield* Console.log(output);
|
|
3162
|
+
});
|
|
3163
|
+
const alias$1 = Flag.string("alias").pipe(Flag.withDescription("The alias of the smart account to remove"), Flag.withAlias("a"), Flag.optional);
|
|
3164
|
+
/**
|
|
3165
|
+
* Command that removes a locally stored smart account by alias.
|
|
3166
|
+
*/
|
|
3167
|
+
const removeSmartAccountCommand = Command.make("remove", { alias: alias$1 }, ({ alias }) => handler$1(alias)).pipe(Command.withAlias("rm"), Command.withDescription("Remove a Smart Account"), Command.withExamples([
|
|
3168
|
+
{
|
|
3169
|
+
command: "namera smart-account remove",
|
|
3170
|
+
description: "Remove a smart-account with alias prompt"
|
|
3171
|
+
},
|
|
3172
|
+
{
|
|
3173
|
+
command: "namera smart-account remove --alias my-smart",
|
|
3174
|
+
description: "Remove a smart account with alias 'my-smart'"
|
|
3175
|
+
},
|
|
3176
|
+
{
|
|
3177
|
+
command: `namera smart-account remove --params '{"alias":"my-smart"}'`,
|
|
3178
|
+
description: "Remove a smart account with json params"
|
|
3179
|
+
},
|
|
3180
|
+
{
|
|
3181
|
+
command: "namera schema smart-account.remove",
|
|
3182
|
+
description: "Get the schema for the remove smart account command"
|
|
3183
|
+
}
|
|
3184
|
+
]));
|
|
3185
|
+
//#endregion
|
|
3186
|
+
//#region src/commands/smart-account/status.ts
|
|
3187
|
+
const handler = (flagAlias, flagChain, flagRpcUrl) => Effect.gen(function* () {
|
|
3188
|
+
const web3Service = yield* Web3Service;
|
|
3189
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
3190
|
+
const outputFormatter = yield* OutputFormatter;
|
|
3191
|
+
const globalFlags = yield* getGlobalFlags();
|
|
3192
|
+
let params;
|
|
3193
|
+
if (globalFlags.params._tag === "Some") params = Schema.decodeUnknownSync(Schema.fromJsonString(GetSmartAccountStatusParams))(globalFlags.params.value);
|
|
3194
|
+
else {
|
|
3195
|
+
let alias;
|
|
3196
|
+
let chain;
|
|
3197
|
+
const rpcUrl = flagRpcUrl._tag === "Some" ? flagRpcUrl.value : void 0;
|
|
3198
|
+
if (flagAlias._tag === "Some") alias = flagAlias.value;
|
|
3199
|
+
else alias = (yield* smartAccountManager.selectSmartAccount({ message: "Select Smart Account:" })).alias;
|
|
3200
|
+
if (flagChain._tag === "Some") chain = flagChain.value;
|
|
3201
|
+
else chain = yield* web3Service.selectChain({ message: "Select Chain:" });
|
|
3202
|
+
params = {
|
|
3203
|
+
alias,
|
|
3204
|
+
chain,
|
|
3205
|
+
rpcUrl
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
const data = { deployed: yield* smartAccountManager.getSmartAccountStatus(params) };
|
|
3209
|
+
if (globalFlags.quite) return;
|
|
3210
|
+
const output = yield* outputFormatter.format(data, globalFlags.out);
|
|
3211
|
+
yield* Console.log(output);
|
|
3212
|
+
});
|
|
3213
|
+
const alias = Flag.string("alias").pipe(Flag.withDescription("The alias of the smart account"), Flag.withAlias("a"), Flag.optional);
|
|
3214
|
+
/**
|
|
3215
|
+
* Command that checks deployment status of a smart account on a chain.
|
|
3216
|
+
*/
|
|
3217
|
+
const getSmartAccountStatus = Command.make("status", {
|
|
3218
|
+
alias,
|
|
3219
|
+
...chainAndRpcUrl
|
|
3220
|
+
}, ({ alias, chain, rpcUrl }) => handler(alias, chain, rpcUrl)).pipe(Command.withDescription("Get Smart Account Deployment Status for a given chain"), Command.withExamples([
|
|
3221
|
+
{
|
|
3222
|
+
command: "namera smart-account status -a my-smart --chain eth-mainnet",
|
|
3223
|
+
description: "Get Smart Account Deployment Status for a given chain"
|
|
3224
|
+
},
|
|
3225
|
+
{
|
|
3226
|
+
command: "namera smart-account status --alias my-smart --chain eth-mainnet --rpc-url https://rpc.ankr.com/eth",
|
|
3227
|
+
description: "Get Smart Account Deployment Status for a given chain with custom RPC URL"
|
|
3228
|
+
},
|
|
3229
|
+
{
|
|
3230
|
+
command: `namera smart-account status --params '{"alias":"my-smart","chain":"eth-mainnet"}'`,
|
|
3231
|
+
description: "Get Smart Account Deployment Status for a given chain with json params"
|
|
3232
|
+
}
|
|
3233
|
+
]));
|
|
3234
|
+
//#endregion
|
|
3235
|
+
//#region src/commands/index.ts
|
|
3236
|
+
/**
|
|
3237
|
+
* Root CLI command set.
|
|
3238
|
+
*/
|
|
3239
|
+
const commands = [
|
|
3240
|
+
keystoreCommands,
|
|
3241
|
+
Command.make("smart-account", {}, () => Effect.void).pipe(Command.withAlias("sa"), Command.withDescription("Smart Account management utilities."), Command.withSubcommands([
|
|
3242
|
+
createSmartAccountCommand,
|
|
3243
|
+
listSmartAccountsCommand,
|
|
3244
|
+
getSmartAccountInfoCommand,
|
|
3245
|
+
removeSmartAccountCommand,
|
|
3246
|
+
getSmartAccountStatus,
|
|
3247
|
+
importSmartAccountCommand
|
|
3248
|
+
])),
|
|
3249
|
+
sessionKeyCommands,
|
|
3250
|
+
mcpCommands,
|
|
3251
|
+
schemaCommand
|
|
3252
|
+
];
|
|
3253
|
+
//#endregion
|
|
3254
|
+
//#region src/index.ts
|
|
3255
|
+
const command = Command.make("namera", {}, () => Effect.void).pipe(Command.withDescription("Programmable Session keys for Smart Contracts Accounts."), Command.withGlobalFlags(globalFlags), Command.withSubcommands(commands), Command.withExamples([{
|
|
3256
|
+
command: "namera --help",
|
|
3257
|
+
description: "Print help"
|
|
3258
|
+
}, {
|
|
3259
|
+
command: "namera --version",
|
|
3260
|
+
description: "Print version"
|
|
3261
|
+
}]));
|
|
3262
|
+
const Layers = layer$1.pipe(Layer.provideMerge(layer$2), Layer.provideMerge(layer$3), Layer.provideMerge(layer$5), Layer.provideMerge(layer$4), Layer.provideMerge(layer$6), Layer.provideMerge(layer$7), Layer.provideMerge(layer), Layer.provideMerge(NodeServices.layer));
|
|
3263
|
+
Effect.gen(function* () {
|
|
3264
|
+
yield* (yield* ConfigManager).ensureConfigDirExists();
|
|
3265
|
+
yield* Command.run(command, { version: "0.0.1" });
|
|
3266
|
+
}).pipe(Effect.provide(Layers), Effect.provideService(ConfigProvider.ConfigProvider, ConfigProvider.fromEnv()), Effect.catch((e) => Console.error(e.message))).pipe(NodeRuntime.runMain);
|
|
3267
|
+
//#endregion
|
|
3268
|
+
export {};
|
|
3269
|
+
|
|
3270
|
+
//# sourceMappingURL=index.mjs.map
|