alchemy 0.69.0 → 0.70.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.
Files changed (314) hide show
  1. package/bin/alchemy.js +3810 -3889
  2. package/bin/commands/init.ts +41 -55
  3. package/bin/trpc.ts +4 -9
  4. package/lib/alchemy.d.ts +3 -3
  5. package/lib/alchemy.d.ts.map +1 -1
  6. package/lib/alchemy.js +2 -15
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/apply.js +24 -9
  9. package/lib/apply.js.map +1 -1
  10. package/lib/aws/utils.d.ts +2 -1
  11. package/lib/aws/utils.d.ts.map +1 -1
  12. package/lib/aws/utils.js.map +1 -1
  13. package/lib/clickhouse/api/client.gen.d.ts +13 -0
  14. package/lib/clickhouse/api/client.gen.d.ts.map +1 -0
  15. package/lib/clickhouse/api/client.gen.js +7 -0
  16. package/lib/clickhouse/api/client.gen.js.map +1 -0
  17. package/lib/clickhouse/api/sdk.gen.d.ts +322 -0
  18. package/lib/clickhouse/api/sdk.gen.d.ts.map +1 -0
  19. package/lib/clickhouse/api/sdk.gen.js +683 -0
  20. package/lib/clickhouse/api/sdk.gen.js.map +1 -0
  21. package/lib/clickhouse/api/types.gen.d.ts +5030 -0
  22. package/lib/clickhouse/api/types.gen.d.ts.map +1 -0
  23. package/lib/clickhouse/api/types.gen.js +2 -0
  24. package/lib/clickhouse/api/types.gen.js.map +1 -0
  25. package/lib/clickhouse/api-key.d.ts +29 -0
  26. package/lib/clickhouse/api-key.d.ts.map +1 -0
  27. package/lib/clickhouse/api-key.js +83 -0
  28. package/lib/clickhouse/api-key.js.map +1 -0
  29. package/lib/clickhouse/api.d.ts +36 -0
  30. package/lib/clickhouse/api.d.ts.map +1 -0
  31. package/lib/clickhouse/api.js +48 -0
  32. package/lib/clickhouse/api.js.map +1 -0
  33. package/lib/clickhouse/index.d.ts +4 -0
  34. package/lib/clickhouse/index.d.ts.map +1 -0
  35. package/lib/clickhouse/index.js +4 -0
  36. package/lib/clickhouse/index.js.map +1 -0
  37. package/lib/clickhouse/organization.d.ts +16 -0
  38. package/lib/clickhouse/organization.d.ts.map +1 -0
  39. package/lib/clickhouse/organization.js +21 -0
  40. package/lib/clickhouse/organization.js.map +1 -0
  41. package/lib/clickhouse/service.d.ts +275 -0
  42. package/lib/clickhouse/service.d.ts.map +1 -0
  43. package/lib/clickhouse/service.js +274 -0
  44. package/lib/clickhouse/service.js.map +1 -0
  45. package/lib/cloudflare/account-api-token.d.ts.map +1 -1
  46. package/lib/cloudflare/account-api-token.js +1 -1
  47. package/lib/cloudflare/account-api-token.js.map +1 -1
  48. package/lib/cloudflare/bucket-object.d.ts +5 -0
  49. package/lib/cloudflare/bucket-object.d.ts.map +1 -1
  50. package/lib/cloudflare/bucket-object.js +1 -1
  51. package/lib/cloudflare/bucket-object.js.map +1 -1
  52. package/lib/cloudflare/bucket.d.ts +6 -3
  53. package/lib/cloudflare/bucket.d.ts.map +1 -1
  54. package/lib/cloudflare/bucket.js +60 -8
  55. package/lib/cloudflare/bucket.js.map +1 -1
  56. package/lib/cloudflare/bun-spa/bun-spa.d.ts +15 -0
  57. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -0
  58. package/lib/cloudflare/bun-spa/bun-spa.js +95 -0
  59. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -0
  60. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  61. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  62. package/lib/cloudflare/container.d.ts +11 -1
  63. package/lib/cloudflare/container.d.ts.map +1 -1
  64. package/lib/cloudflare/container.js +1 -1
  65. package/lib/cloudflare/container.js.map +1 -1
  66. package/lib/cloudflare/index.d.ts +1 -0
  67. package/lib/cloudflare/index.d.ts.map +1 -1
  68. package/lib/cloudflare/index.js +1 -0
  69. package/lib/cloudflare/index.js.map +1 -1
  70. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  71. package/lib/cloudflare/miniflare/build-worker-options.js +3 -3
  72. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  73. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  74. package/lib/cloudflare/miniflare/miniflare-controller.js +2 -0
  75. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  76. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +1 -1
  77. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  78. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +33 -14
  79. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  80. package/lib/cloudflare/permission-groups.d.ts +2 -3
  81. package/lib/cloudflare/permission-groups.d.ts.map +1 -1
  82. package/lib/cloudflare/permission-groups.js +10 -15
  83. package/lib/cloudflare/permission-groups.js.map +1 -1
  84. package/lib/cloudflare/redwood/redwood.d.ts +1 -1
  85. package/lib/cloudflare/redwood/redwood.js +1 -1
  86. package/lib/cloudflare/tanstack-start/plugin.d.ts +2 -15
  87. package/lib/cloudflare/tanstack-start/plugin.d.ts.map +1 -1
  88. package/lib/cloudflare/tanstack-start/plugin.js +5 -25
  89. package/lib/cloudflare/tanstack-start/plugin.js.map +1 -1
  90. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -1
  91. package/lib/cloudflare/tanstack-start/tanstack-start.js +15 -4
  92. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -1
  93. package/lib/cloudflare/util/filter-env-bindings.d.ts +62 -0
  94. package/lib/cloudflare/util/filter-env-bindings.d.ts.map +1 -0
  95. package/lib/cloudflare/util/filter-env-bindings.js +81 -0
  96. package/lib/cloudflare/util/filter-env-bindings.js.map +1 -0
  97. package/lib/cloudflare/website.d.ts +2 -2
  98. package/lib/cloudflare/website.d.ts.map +1 -1
  99. package/lib/cloudflare/website.js +7 -18
  100. package/lib/cloudflare/website.js.map +1 -1
  101. package/lib/cloudflare/worker-metadata.d.ts +5 -1
  102. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  103. package/lib/cloudflare/worker-metadata.js +3 -0
  104. package/lib/cloudflare/worker-metadata.js.map +1 -1
  105. package/lib/cloudflare/worker.d.ts +17 -1
  106. package/lib/cloudflare/worker.d.ts.map +1 -1
  107. package/lib/cloudflare/worker.js +1 -0
  108. package/lib/cloudflare/worker.js.map +1 -1
  109. package/lib/cloudflare/wrangler.json.d.ts +15 -1
  110. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  111. package/lib/cloudflare/wrangler.json.js +12 -4
  112. package/lib/cloudflare/wrangler.json.js.map +1 -1
  113. package/lib/coinbase/client.d.ts +54 -0
  114. package/lib/coinbase/client.d.ts.map +1 -0
  115. package/lib/coinbase/client.js +89 -0
  116. package/lib/coinbase/client.js.map +1 -0
  117. package/lib/coinbase/evm-account.d.ts +118 -0
  118. package/lib/coinbase/evm-account.d.ts.map +1 -0
  119. package/lib/coinbase/evm-account.js +198 -0
  120. package/lib/coinbase/evm-account.js.map +1 -0
  121. package/lib/coinbase/evm-smart-account.d.ts +122 -0
  122. package/lib/coinbase/evm-smart-account.d.ts.map +1 -0
  123. package/lib/coinbase/evm-smart-account.js +213 -0
  124. package/lib/coinbase/evm-smart-account.js.map +1 -0
  125. package/lib/coinbase/faucet.d.ts +11 -0
  126. package/lib/coinbase/faucet.d.ts.map +1 -0
  127. package/lib/coinbase/faucet.js +146 -0
  128. package/lib/coinbase/faucet.js.map +1 -0
  129. package/lib/coinbase/index.d.ts +4 -0
  130. package/lib/coinbase/index.d.ts.map +1 -0
  131. package/lib/coinbase/index.js +4 -0
  132. package/lib/coinbase/index.js.map +1 -0
  133. package/lib/coinbase/types.d.ts +28 -0
  134. package/lib/coinbase/types.d.ts.map +1 -0
  135. package/lib/coinbase/types.js +4 -0
  136. package/lib/{util/telemetry → coinbase}/types.js.map +1 -1
  137. package/lib/coinbase/utils.d.ts +13 -0
  138. package/lib/coinbase/utils.d.ts.map +1 -0
  139. package/lib/coinbase/utils.js +17 -0
  140. package/lib/coinbase/utils.js.map +1 -0
  141. package/lib/docker/api.d.ts +22 -1
  142. package/lib/docker/api.d.ts.map +1 -1
  143. package/lib/docker/api.js +64 -2
  144. package/lib/docker/api.js.map +1 -1
  145. package/lib/docker/container.d.ts +88 -0
  146. package/lib/docker/container.d.ts.map +1 -1
  147. package/lib/docker/container.js +36 -0
  148. package/lib/docker/container.js.map +1 -1
  149. package/lib/neon/api/types.gen.d.ts +7 -2
  150. package/lib/neon/api/types.gen.d.ts.map +1 -1
  151. package/lib/planetscale/api/types.gen.d.ts +20 -4
  152. package/lib/planetscale/api/types.gen.d.ts.map +1 -1
  153. package/lib/planetscale/api.d.ts.map +1 -1
  154. package/lib/planetscale/api.js +1 -0
  155. package/lib/planetscale/api.js.map +1 -1
  156. package/lib/planetscale/password.d.ts.map +1 -1
  157. package/lib/planetscale/password.js +1 -13
  158. package/lib/planetscale/password.js.map +1 -1
  159. package/lib/resource.d.ts.map +1 -1
  160. package/lib/resource.js +7 -3
  161. package/lib/resource.js.map +1 -1
  162. package/lib/scope.d.ts +4 -9
  163. package/lib/scope.d.ts.map +1 -1
  164. package/lib/scope.js +16 -37
  165. package/lib/scope.js.map +1 -1
  166. package/lib/state/instrumented-state-store.d.ts +1 -3
  167. package/lib/state/instrumented-state-store.d.ts.map +1 -1
  168. package/lib/state/instrumented-state-store.js +19 -21
  169. package/lib/state/instrumented-state-store.js.map +1 -1
  170. package/lib/test/bun.d.ts.map +1 -1
  171. package/lib/test/bun.js +1 -2
  172. package/lib/test/bun.js.map +1 -1
  173. package/lib/test/vitest.d.ts.map +1 -1
  174. package/lib/test/vitest.js +1 -2
  175. package/lib/test/vitest.js.map +1 -1
  176. package/lib/util/diff.d.ts +5 -0
  177. package/lib/util/diff.d.ts.map +1 -0
  178. package/lib/util/diff.js +14 -0
  179. package/lib/util/diff.js.map +1 -0
  180. package/lib/util/telemetry.d.ts +52 -0
  181. package/lib/util/telemetry.d.ts.map +1 -0
  182. package/lib/util/telemetry.js +191 -0
  183. package/lib/util/telemetry.js.map +1 -0
  184. package/package.json +19 -1
  185. package/src/alchemy.ts +5 -20
  186. package/src/apply.ts +29 -12
  187. package/src/aws/utils.ts +2 -1
  188. package/src/clickhouse/api/client.gen.ts +28 -0
  189. package/src/clickhouse/api/sdk.gen.ts +1286 -0
  190. package/src/clickhouse/api/types.gen.ts +5898 -0
  191. package/src/clickhouse/api-key.ts +136 -0
  192. package/src/clickhouse/api.ts +84 -0
  193. package/src/clickhouse/index.ts +3 -0
  194. package/src/clickhouse/organization.ts +31 -0
  195. package/src/clickhouse/service.ts +668 -0
  196. package/src/cloudflare/account-api-token.ts +1 -4
  197. package/src/cloudflare/bucket-object.ts +11 -1
  198. package/src/cloudflare/bucket.ts +72 -6
  199. package/src/cloudflare/bun-spa/bun-spa.ts +139 -0
  200. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  201. package/src/cloudflare/container.ts +22 -3
  202. package/src/cloudflare/index.ts +1 -0
  203. package/src/cloudflare/miniflare/build-worker-options.ts +6 -4
  204. package/src/cloudflare/miniflare/miniflare-controller.ts +2 -0
  205. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +42 -15
  206. package/src/cloudflare/permission-groups.ts +21 -49
  207. package/src/cloudflare/redwood/redwood.ts +1 -1
  208. package/src/cloudflare/tanstack-start/plugin.ts +7 -28
  209. package/src/cloudflare/tanstack-start/tanstack-start.ts +15 -4
  210. package/src/cloudflare/util/filter-env-bindings.ts +96 -0
  211. package/src/cloudflare/website.ts +12 -24
  212. package/src/cloudflare/worker-metadata.ts +8 -1
  213. package/src/cloudflare/worker.ts +19 -1
  214. package/src/cloudflare/wrangler.json.ts +27 -9
  215. package/src/coinbase/client.ts +146 -0
  216. package/src/coinbase/evm-account.ts +317 -0
  217. package/src/coinbase/evm-smart-account.ts +339 -0
  218. package/src/coinbase/faucet.ts +173 -0
  219. package/src/coinbase/index.ts +3 -0
  220. package/src/coinbase/types.ts +33 -0
  221. package/src/coinbase/utils.ts +19 -0
  222. package/src/docker/api.ts +86 -2
  223. package/src/docker/container.ts +97 -0
  224. package/src/neon/api/types.gen.ts +13 -1
  225. package/src/planetscale/api/types.gen.ts +22 -2
  226. package/src/planetscale/api.ts +1 -0
  227. package/src/planetscale/password.ts +1 -14
  228. package/src/resource.ts +11 -4
  229. package/src/scope.ts +22 -45
  230. package/src/state/instrumented-state-store.ts +27 -26
  231. package/src/test/bun.ts +1 -2
  232. package/src/test/vitest.ts +1 -2
  233. package/src/util/diff.ts +14 -0
  234. package/src/util/telemetry.ts +297 -0
  235. package/templates/tanstack-start/.cta.json +13 -0
  236. package/templates/tanstack-start/README.md +267 -49
  237. package/templates/tanstack-start/_gitignore +9 -18
  238. package/templates/tanstack-start/alchemy.run.ts +2 -2
  239. package/templates/tanstack-start/package.json +24 -19
  240. package/templates/tanstack-start/public/favicon.ico +0 -0
  241. package/templates/tanstack-start/public/logo192.png +0 -0
  242. package/templates/tanstack-start/public/logo512.png +0 -0
  243. package/templates/tanstack-start/public/manifest.json +25 -0
  244. package/templates/tanstack-start/public/robots.txt +3 -0
  245. package/templates/tanstack-start/src/components/Header.tsx +21 -0
  246. package/templates/tanstack-start/src/logo.svg +12 -0
  247. package/templates/tanstack-start/src/router.tsx +7 -16
  248. package/templates/tanstack-start/src/routes/__root.tsx +13 -114
  249. package/templates/tanstack-start/src/routes/api.demo-names.ts +15 -0
  250. package/templates/tanstack-start/src/routes/demo.start.api-request.tsx +44 -0
  251. package/templates/tanstack-start/src/routes/demo.start.server-funcs.tsx +97 -0
  252. package/templates/tanstack-start/src/routes/index.tsx +31 -4
  253. package/templates/tanstack-start/src/styles.css +15 -0
  254. package/templates/tanstack-start/tsconfig.json +19 -22
  255. package/templates/tanstack-start/types/env.d.ts +2 -2
  256. package/templates/tanstack-start/vite.config.ts +16 -25
  257. package/workers/tunnel-proxy.js +1 -1
  258. package/lib/util/telemetry/client.d.ts +0 -31
  259. package/lib/util/telemetry/client.d.ts.map +0 -1
  260. package/lib/util/telemetry/client.js +0 -121
  261. package/lib/util/telemetry/client.js.map +0 -1
  262. package/lib/util/telemetry/constants.d.ts +0 -5
  263. package/lib/util/telemetry/constants.d.ts.map +0 -1
  264. package/lib/util/telemetry/constants.js +0 -8
  265. package/lib/util/telemetry/constants.js.map +0 -1
  266. package/lib/util/telemetry/context.d.ts +0 -15
  267. package/lib/util/telemetry/context.d.ts.map +0 -1
  268. package/lib/util/telemetry/context.js +0 -128
  269. package/lib/util/telemetry/context.js.map +0 -1
  270. package/lib/util/telemetry/index.d.ts +0 -3
  271. package/lib/util/telemetry/index.d.ts.map +0 -1
  272. package/lib/util/telemetry/index.js +0 -3
  273. package/lib/util/telemetry/index.js.map +0 -1
  274. package/lib/util/telemetry/types.d.ts +0 -68
  275. package/lib/util/telemetry/types.d.ts.map +0 -1
  276. package/lib/util/telemetry/types.js +0 -1
  277. package/src/util/telemetry/client.ts +0 -156
  278. package/src/util/telemetry/constants.ts +0 -13
  279. package/src/util/telemetry/context.ts +0 -154
  280. package/src/util/telemetry/index.ts +0 -2
  281. package/src/util/telemetry/types.ts +0 -105
  282. package/templates/tanstack-start/_prettierignore +0 -4
  283. package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
  284. package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
  285. package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
  286. package/templates/tanstack-start/public/favicon-16x16.png +0 -0
  287. package/templates/tanstack-start/public/favicon-32x32.png +0 -0
  288. package/templates/tanstack-start/public/favicon.png +0 -0
  289. package/templates/tanstack-start/public/site.webmanifest +0 -19
  290. package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +0 -53
  291. package/templates/tanstack-start/src/components/NotFound.tsx +0 -25
  292. package/templates/tanstack-start/src/components/PostError.tsx +0 -5
  293. package/templates/tanstack-start/src/components/UserError.tsx +0 -5
  294. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +0 -10
  295. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +0 -10
  296. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +0 -34
  297. package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +0 -16
  298. package/templates/tanstack-start/src/routes/api/users.$userId.ts +0 -28
  299. package/templates/tanstack-start/src/routes/api/users.ts +0 -59
  300. package/templates/tanstack-start/src/routes/customScript[.]js.ts +0 -10
  301. package/templates/tanstack-start/src/routes/deferred.tsx +0 -62
  302. package/templates/tanstack-start/src/routes/posts.$postId.tsx +0 -34
  303. package/templates/tanstack-start/src/routes/posts.index.tsx +0 -8
  304. package/templates/tanstack-start/src/routes/posts.tsx +0 -38
  305. package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +0 -29
  306. package/templates/tanstack-start/src/routes/redirect.tsx +0 -9
  307. package/templates/tanstack-start/src/routes/users.$userId.tsx +0 -44
  308. package/templates/tanstack-start/src/routes/users.index.tsx +0 -18
  309. package/templates/tanstack-start/src/routes/users.tsx +0 -49
  310. package/templates/tanstack-start/src/styles/app.css +0 -12
  311. package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +0 -41
  312. package/templates/tanstack-start/src/utils/posts.tsx +0 -40
  313. package/templates/tanstack-start/src/utils/seo.ts +0 -33
  314. package/templates/tanstack-start/src/utils/users.tsx +0 -5
@@ -0,0 +1,317 @@
1
+ import type { Context } from "../context.ts";
2
+ import type { Secret } from "../index.ts";
3
+ import { Resource } from "../resource.ts";
4
+ import { createCdpClient, type CoinbaseClientOptions } from "./client.ts";
5
+ import type {
6
+ Address,
7
+ FaucetConfig,
8
+ FaucetNetwork,
9
+ FaucetToken,
10
+ PrivateKey,
11
+ } from "./types.ts";
12
+ import { validateAccountName } from "./utils.ts";
13
+
14
+ // Re-export types for backward compatibility
15
+ export type { FaucetConfig, FaucetNetwork, FaucetToken } from "./types.ts";
16
+
17
+ export interface EvmAccountProps extends CoinbaseClientOptions {
18
+ /**
19
+ * Name for the account.
20
+ * Used for identification in CDP.
21
+ * Must contain only letters, numbers, and hyphens.
22
+ */
23
+ name: string;
24
+ /**
25
+ * Optional private key to import an existing account.
26
+ * Must be a hex string (starting with 0x) encrypted using alchemy.secret().
27
+ * If not provided, a new account will be created or existing one will be used.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * privateKey: alchemy.secret(process.env.PRIVATE_KEY)
32
+ * ```
33
+ */
34
+ privateKey?: Secret<PrivateKey>;
35
+ /**
36
+ * Whether to adopt an existing account with the same name if it already exists.
37
+ * Without adoption, creation will fail if an account with the same name exists.
38
+ * With adoption, the existing account will be used.
39
+ * @default false
40
+ */
41
+ adopt?: boolean;
42
+ /**
43
+ * Faucet configuration for development funding.
44
+ * Declares which tokens this account should have.
45
+ * Used by external funding scripts - not processed by the resource.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * faucet: {
50
+ * "base-sepolia": ["eth", "usdc"],
51
+ * "ethereum-sepolia": ["eth"]
52
+ * }
53
+ * ```
54
+ */
55
+ faucet?: FaucetConfig;
56
+ }
57
+
58
+ export interface EvmAccount extends Resource<"coinbase::evm-account"> {
59
+ /**
60
+ * The account name in CDP
61
+ */
62
+ name: string;
63
+ /**
64
+ * The EVM address (same across all EVM networks)
65
+ */
66
+ address: Address;
67
+ /**
68
+ * Faucet configuration (passed through from props)
69
+ */
70
+ faucet?: FaucetConfig;
71
+ }
72
+
73
+ /**
74
+ * Manages EVM EOA (Externally Owned Accounts) on Coinbase Developer Platform.
75
+ *
76
+ * @example
77
+ * ## Create a new EVM account
78
+ *
79
+ * ```ts
80
+ * const account = await EvmAccount("my-account", {
81
+ * name: "my-account"
82
+ * });
83
+ *
84
+ * console.log("Account address:", account.address);
85
+ * ```
86
+ *
87
+ * @example
88
+ * ## Create account with funding metadata
89
+ *
90
+ * Declare what tokens this account needs for development
91
+ *
92
+ * ```ts
93
+ * const account = await EvmAccount("test-account", {
94
+ * name: "test-account",
95
+ * faucet: {
96
+ * "base-sepolia": ["eth", "usdc"],
97
+ * "ethereum-sepolia": ["eth"]
98
+ * }
99
+ * });
100
+ *
101
+ * // Account automatically requests tokens on creation
102
+ * // The faucet script can be used for bulk funding operations
103
+ * ```
104
+ *
105
+ * @example
106
+ * ## Import an existing account
107
+ *
108
+ * Import an existing EVM account using a private key
109
+ *
110
+ * ```ts
111
+ * const account = await EvmAccount("imported-account", {
112
+ * name: "imported-account",
113
+ * privateKey: alchemy.secret(process.env.COINBASE_PRIVATE_KEY)
114
+ * });
115
+ * ```
116
+ *
117
+ * @example
118
+ * ## Adopt an existing account
119
+ *
120
+ * Use an existing account with the same name if it exists
121
+ *
122
+ * ```ts
123
+ * const account = await EvmAccount("my-account", {
124
+ * name: "existing-account",
125
+ * adopt: true // Uses existing account if it exists
126
+ * });
127
+ * ```
128
+ */
129
+ export const EvmAccount = Resource(
130
+ "coinbase::evm-account",
131
+ async function (
132
+ this: Context<EvmAccount>,
133
+ _id: string,
134
+ props: EvmAccountProps,
135
+ ): Promise<EvmAccount> {
136
+ // Validate account name format
137
+ validateAccountName(props.name);
138
+
139
+ // Initialize CDP client with credentials from props or environment
140
+ const cdp = await createCdpClient({
141
+ apiKeyId: props.apiKeyId,
142
+ apiKeySecret: props.apiKeySecret,
143
+ walletSecret: props.walletSecret,
144
+ });
145
+
146
+ // Handle update phase
147
+ if (this.phase === "update" && this.output) {
148
+ // Only name can be updated
149
+ if (props.name !== this.output.name) {
150
+ // CDP SDK supports updating account names via updateAccount
151
+ await cdp.evm.updateAccount({
152
+ address: this.output.address,
153
+ update: {
154
+ name: props.name,
155
+ },
156
+ });
157
+ return {
158
+ ...this.output,
159
+ name: props.name, // Use the requested name
160
+ faucet: props.faucet,
161
+ };
162
+ }
163
+
164
+ // Update faucet configuration if changed
165
+ if (JSON.stringify(props.faucet) !== JSON.stringify(this.output.faucet)) {
166
+ // Only fund NEW combinations that weren't in the previous config
167
+ try {
168
+ // Get all old combinations
169
+ const oldCombinations = new Set(
170
+ Object.entries(this.output.faucet ?? {}).flatMap(
171
+ ([network, tokens]) =>
172
+ tokens.map((token) => `${network}:${token}`),
173
+ ),
174
+ );
175
+
176
+ // Get all new combinations
177
+ const newCombinations = Object.entries(props.faucet ?? {}).flatMap(
178
+ ([network, tokens]) =>
179
+ tokens.map((token) => ({
180
+ network: network as FaucetNetwork,
181
+ token: token as FaucetToken,
182
+ key: `${network}:${token}`,
183
+ })),
184
+ );
185
+
186
+ // Filter to only combinations that are actually new
187
+ const toFund = newCombinations.filter(
188
+ ({ key }) => !oldCombinations.has(key),
189
+ );
190
+
191
+ if (toFund.length > 0) {
192
+ await Promise.all(
193
+ toFund.map(({ network, token }) =>
194
+ cdp.evm
195
+ .requestFaucet({
196
+ address: this.output.address,
197
+ network,
198
+ token,
199
+ })
200
+ .catch((err) => {
201
+ console.warn(
202
+ `⚠️ Failed to request ${token} on ${network} for ${this.output.address}: ${err.message}`,
203
+ );
204
+ return null;
205
+ }),
206
+ ),
207
+ );
208
+
209
+ console.log(
210
+ `💧 Requested ${toFund.length} new faucet fund${toFund.length === 1 ? "" : "s"} for ${this.output.address}`,
211
+ );
212
+ }
213
+ } catch (error: any) {
214
+ console.warn(
215
+ `⚠️ Failed to request additional faucet funds for ${this.output.address}: ${error.message}`,
216
+ );
217
+ // Continue - don't break the update
218
+ }
219
+
220
+ return {
221
+ ...this.output,
222
+ faucet: props.faucet,
223
+ };
224
+ }
225
+
226
+ return this.output;
227
+ }
228
+
229
+ // Handle delete phase
230
+ if (this.phase === "delete") {
231
+ // CDP SDK doesn't support deleting accounts
232
+ // Accounts remain in CDP but are no longer tracked by Alchemy
233
+ console.log(`🗑️ Untracking EVM Account: ${this.output.address}`);
234
+ return this.destroy();
235
+ }
236
+
237
+ let account;
238
+
239
+ // Handle account creation/retrieval
240
+ if (props.privateKey) {
241
+ // Import account with private key
242
+ // This is idempotent in CDP
243
+ account = await cdp.evm.importAccount({
244
+ privateKey: props.privateKey.unencrypted,
245
+ name: props.name,
246
+ });
247
+ } else {
248
+ // Check for adoption or use getOrCreate pattern
249
+ const adopt = props.adopt ?? this.scope.adopt;
250
+ if (adopt) {
251
+ // With adoption, use getOrCreate which returns existing if it exists
252
+ account = await cdp.evm.getOrCreateAccount({
253
+ name: props.name,
254
+ });
255
+ } else {
256
+ // Without adoption, we can attempt to create it.
257
+ // It will throw if already exist
258
+ try {
259
+ account = await cdp.evm.createAccount({ name: props.name });
260
+ } catch (error: any) {
261
+ // Provide helpful error message if account already exists
262
+ if (error.errorType === "already_exists") {
263
+ throw new Error(
264
+ `Account with name '${props.name}' already exists. Use adopt: true to use the existing account.`,
265
+ );
266
+ }
267
+ // Rethrow other errors unchanged
268
+ throw error;
269
+ }
270
+ }
271
+ }
272
+
273
+ // Fund the account if faucet configuration is provided
274
+ if (props.faucet) {
275
+ try {
276
+ const combinations = Object.entries(props.faucet).flatMap(
277
+ ([network, tokens]) =>
278
+ tokens.map((token) => ({
279
+ network: network as FaucetNetwork,
280
+ token: token as FaucetToken,
281
+ })),
282
+ );
283
+
284
+ await Promise.all(
285
+ combinations.map(({ network, token }) =>
286
+ cdp.evm
287
+ .requestFaucet({
288
+ address: account.address,
289
+ network,
290
+ token,
291
+ })
292
+ .catch((err) => {
293
+ console.warn(
294
+ `⚠️ Failed to request ${token} on ${network} for ${account.address}: ${err.message}`,
295
+ );
296
+ return null;
297
+ }),
298
+ ),
299
+ );
300
+
301
+ console.log(`💧 Requested faucet funds for ${account.address}`);
302
+ } catch (error: any) {
303
+ console.warn(
304
+ `⚠️ Failed to request faucet funds for ${account.address}: ${error.message}`,
305
+ );
306
+ // Continue - don't break account creation
307
+ }
308
+ }
309
+
310
+ // Return account details
311
+ return {
312
+ name: props.name, // Always use the name from props for consistency
313
+ address: account.address,
314
+ faucet: props.faucet,
315
+ } as EvmAccount;
316
+ },
317
+ );
@@ -0,0 +1,339 @@
1
+ import type { Context } from "../context.ts";
2
+ import { Resource } from "../resource.ts";
3
+ import { createCdpClient, type CoinbaseClientOptions } from "./client.ts";
4
+ import type { EvmAccount } from "./evm-account.ts";
5
+ import type {
6
+ Address,
7
+ FaucetConfig,
8
+ FaucetNetwork,
9
+ FaucetToken,
10
+ } from "./types.ts";
11
+ import { validateAccountName } from "./utils.ts";
12
+
13
+ export interface EvmSmartAccountProps extends CoinbaseClientOptions {
14
+ /**
15
+ * Name for the smart account.
16
+ * If not provided, inherits the name from the owner account.
17
+ * This allows EOA and Smart Account to have matching names in CDP.
18
+ * Used for identification in CDP.
19
+ * Must contain only letters, numbers, and hyphens.
20
+ * @default Inherits from owner account name
21
+ */
22
+ name?: string;
23
+ /**
24
+ * The owner account that controls this smart account.
25
+ * Can be either an EvmAccount resource or an owner address.
26
+ */
27
+ owner: EvmAccount | Address;
28
+ /**
29
+ * Whether to adopt an existing smart account with the same name if it already exists.
30
+ * Without adoption, creation will fail if a smart account with the same name exists.
31
+ * With adoption, the existing smart account will be used.
32
+ * @default false
33
+ */
34
+ adopt?: boolean;
35
+ /**
36
+ * Faucet configuration for development funding.
37
+ * Declares which tokens this smart account should have.
38
+ * Used by external funding scripts - not processed by the resource.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * faucet: {
43
+ * "base-sepolia": ["eth", "usdc"],
44
+ * "ethereum-sepolia": ["eth"]
45
+ * }
46
+ * ```
47
+ */
48
+ faucet?: FaucetConfig;
49
+ }
50
+
51
+ export interface EvmSmartAccount
52
+ extends Resource<"coinbase::evm-smart-account"> {
53
+ /**
54
+ * The smart account name in CDP
55
+ */
56
+ name: string;
57
+ /**
58
+ * The smart account address (same across all EVM networks)
59
+ */
60
+ address: Address;
61
+ /**
62
+ * The owner account address
63
+ */
64
+ ownerAddress: Address;
65
+ /**
66
+ * Faucet configuration (passed through from props)
67
+ */
68
+ faucet?: FaucetConfig;
69
+ }
70
+
71
+ /**
72
+ * Manages ERC-4337 smart accounts on Coinbase Developer Platform.
73
+ * Smart accounts enable gasless transactions and advanced features like batch operations.
74
+ *
75
+ * @example
76
+ * ## Create a smart account with an EVM account owner
77
+ *
78
+ * ```ts
79
+ * const owner = await EvmAccount("owner", {
80
+ * name: "owner-account"
81
+ * });
82
+ *
83
+ * const smartAccount = await EvmSmartAccount("my-smart-account", {
84
+ * name: "my-smart-account",
85
+ * owner: owner
86
+ * });
87
+ *
88
+ * console.log("Smart account address:", smartAccount.address);
89
+ * ```
90
+ *
91
+ * @example
92
+ * ## Create a smart account with existing owner
93
+ *
94
+ * ```ts
95
+ * const smartAccount = await EvmSmartAccount("my-smart-account", {
96
+ * name: "my-smart-account",
97
+ * owner: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" // Owner address
98
+ * });
99
+ * ```
100
+ *
101
+ * @example
102
+ * ## Smart account with inherited name
103
+ *
104
+ * When name is omitted, the smart account inherits the owner's name
105
+ *
106
+ * ```ts
107
+ * const owner = await EvmAccount("owner", {
108
+ * name: "my-app-wallet"
109
+ * });
110
+ *
111
+ * // Smart account will also be named "my-app-wallet" in CDP
112
+ * const smartAccount = await EvmSmartAccount("smart", {
113
+ * owner: owner
114
+ * // name is omitted, inherits "my-app-wallet" from owner
115
+ * });
116
+ *
117
+ * console.log(smartAccount.name); // "my-app-wallet"
118
+ * ```
119
+ *
120
+ * @example
121
+ * ## Adopt an existing smart account
122
+ *
123
+ * ```ts
124
+ * const smartAccount = await EvmSmartAccount("my-smart-account", {
125
+ * name: "existing-smart-account",
126
+ * owner: ownerAccount, // or "0x..." address
127
+ * adopt: true // Uses existing smart account if it exists
128
+ * });
129
+ * ```
130
+ */
131
+ export const EvmSmartAccount = Resource(
132
+ "coinbase::evm-smart-account",
133
+ async function (
134
+ this: Context<EvmSmartAccount>,
135
+ _id: string,
136
+ props: EvmSmartAccountProps,
137
+ ): Promise<EvmSmartAccount> {
138
+ // Initialize CDP client with credentials from props or environment
139
+ const cdp = await createCdpClient({
140
+ apiKeyId: props.apiKeyId,
141
+ apiKeySecret: props.apiKeySecret,
142
+ walletSecret: props.walletSecret,
143
+ });
144
+
145
+ // Get owner address
146
+ const ownerAddress =
147
+ typeof props.owner === "string" ? props.owner : props.owner.address;
148
+
149
+ // Get owner account for CDP operations
150
+ // When owner is an address string, we use it to get the account
151
+ const ownerAccount =
152
+ typeof props.owner === "string"
153
+ ? await cdp.evm.getAccount({ address: props.owner })
154
+ : await cdp.evm.getAccount({ address: props.owner.address });
155
+
156
+ // If no name provided, inherit from owner
157
+ // this mean that if you pass only { owner } the created smartAccount will be same name as the EOA owner
158
+ // this to support https://github.com/base/account-sdk/blob/master/packages/account-sdk/src/interface/payment/charge.ts#L120
159
+ let accountName = props.name ? props.name : ownerAccount.name;
160
+ if (!accountName) {
161
+ throw new Error(
162
+ `Smart account requires a name. Either provide 'name' in props or ensure the owner account has a name. Owner address: ${ownerAddress}`,
163
+ );
164
+ }
165
+
166
+ // Validate account name format
167
+ validateAccountName(accountName);
168
+
169
+ // Handle update phase
170
+ if (this.phase === "update" && this.output) {
171
+ // Check if owner changed (which would require replacement)
172
+ if (ownerAddress !== this.output.ownerAddress) {
173
+ this.replace();
174
+ }
175
+ // Check if name changed
176
+ if (accountName !== this.output.name) {
177
+ await cdp.evm.updateSmartAccount({
178
+ address: this.output.address,
179
+ owner: ownerAccount,
180
+ update: {
181
+ name: accountName,
182
+ },
183
+ });
184
+
185
+ return {
186
+ ...this.output,
187
+ name: accountName,
188
+ // faucet: props.faucet, not sure we need this?
189
+ };
190
+ }
191
+ // Update faucet configuration if changed
192
+ if (JSON.stringify(props.faucet) !== JSON.stringify(this.output.faucet)) {
193
+ // Only fund NEW combinations that weren't in the previous config
194
+ try {
195
+ // Get all old combinations
196
+ const oldCombinations = new Set(
197
+ Object.entries(this.output.faucet ?? {}).flatMap(
198
+ ([network, tokens]) =>
199
+ tokens.map((token) => `${network}:${token}`),
200
+ ),
201
+ );
202
+
203
+ // Get all new combinations
204
+ const newCombinations = Object.entries(props.faucet ?? {}).flatMap(
205
+ ([network, tokens]) =>
206
+ tokens.map((token) => ({
207
+ network: network as FaucetNetwork,
208
+ token: token as FaucetToken,
209
+ key: `${network}:${token}`,
210
+ })),
211
+ );
212
+
213
+ // Filter to only combinations that are actually new
214
+ const toFund = newCombinations.filter(
215
+ ({ key }) => !oldCombinations.has(key),
216
+ );
217
+
218
+ if (toFund.length > 0) {
219
+ await Promise.all(
220
+ toFund.map(({ network, token }) =>
221
+ cdp.evm
222
+ .requestFaucet({
223
+ address: this.output.address,
224
+ network,
225
+ token,
226
+ })
227
+ .catch((err) => {
228
+ console.warn(
229
+ `⚠️ Failed to request ${token} on ${network} for smart account ${this.output.address}: ${err.message}`,
230
+ );
231
+ return null;
232
+ }),
233
+ ),
234
+ );
235
+
236
+ console.log(
237
+ `💧 Requested ${toFund.length} new faucet fund${toFund.length === 1 ? "" : "s"} for smart account ${this.output.address}`,
238
+ );
239
+ }
240
+ } catch (error: any) {
241
+ console.warn(
242
+ `⚠️ Failed to request additional faucet funds for smart account ${this.output.address}: ${error.message}`,
243
+ );
244
+ // Continue - don't break the update
245
+ }
246
+
247
+ return {
248
+ ...this.output,
249
+ faucet: props.faucet,
250
+ };
251
+ }
252
+ return this.output;
253
+ }
254
+
255
+ // Handle delete phase
256
+ if (this.phase === "delete") {
257
+ // CDP SDK doesn't support deleting accounts
258
+ // Accounts remain in CDP but are no longer tracked by Alchemy
259
+ console.log(`🗑️ Untracking Smart Account: ${this.output?.address}`);
260
+ return this.destroy();
261
+ }
262
+
263
+ let smartAccount;
264
+
265
+ // Check for adoption or use getOrCreate pattern
266
+ const adopt = props.adopt ?? this.scope.adopt;
267
+ if (adopt) {
268
+ // With adoption, use getOrCreateSmartAccount which returns existing if it exists
269
+ smartAccount = await cdp.evm.getOrCreateSmartAccount({
270
+ name: accountName,
271
+ owner: ownerAccount,
272
+ });
273
+ } else {
274
+ // Without adoption, create a new smart account
275
+ try {
276
+ smartAccount = await cdp.evm.createSmartAccount({
277
+ name: accountName,
278
+ owner: ownerAccount,
279
+ });
280
+ } catch (error: any) {
281
+ // Provide helpful error message if smart account already exists
282
+ if (error.errorType === "already_exists") {
283
+ throw new Error(
284
+ `Smart account with name '${accountName}' already exists. Use adopt: true to use the existing smart account.`,
285
+ );
286
+ }
287
+ // Rethrow other errors unchanged
288
+ throw error;
289
+ }
290
+ }
291
+
292
+ // Fund the smart account if faucet configuration is provided
293
+ if (props.faucet) {
294
+ try {
295
+ const combinations = Object.entries(props.faucet).flatMap(
296
+ ([network, tokens]) =>
297
+ tokens.map((token) => ({
298
+ network: network as FaucetNetwork,
299
+ token: token as FaucetToken,
300
+ })),
301
+ );
302
+
303
+ await Promise.all(
304
+ combinations.map(({ network, token }) =>
305
+ cdp.evm
306
+ .requestFaucet({
307
+ address: smartAccount.address,
308
+ network,
309
+ token,
310
+ })
311
+ .catch((err) => {
312
+ console.warn(
313
+ `⚠️ Failed to request ${token} on ${network} for smart account ${smartAccount.address}: ${err.message}`,
314
+ );
315
+ return null;
316
+ }),
317
+ ),
318
+ );
319
+
320
+ console.log(
321
+ `💧 Requested faucet funds for smart account ${smartAccount.address}`,
322
+ );
323
+ } catch (error: any) {
324
+ console.warn(
325
+ `⚠️ Failed to request faucet funds for smart account ${smartAccount.address}: ${error.message}`,
326
+ );
327
+ // Continue - don't break account creation
328
+ }
329
+ }
330
+
331
+ // Return smart account details
332
+ return {
333
+ name: accountName,
334
+ address: smartAccount.address,
335
+ ownerAddress,
336
+ faucet: props.faucet,
337
+ } as EvmSmartAccount;
338
+ },
339
+ );