alchemy 0.69.1 → 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 (227) hide show
  1. package/bin/alchemy.js +3797 -3857
  2. package/bin/trpc.ts +4 -9
  3. package/lib/alchemy.d.ts +3 -3
  4. package/lib/alchemy.d.ts.map +1 -1
  5. package/lib/alchemy.js +2 -15
  6. package/lib/alchemy.js.map +1 -1
  7. package/lib/apply.js +24 -9
  8. package/lib/apply.js.map +1 -1
  9. package/lib/aws/utils.d.ts +2 -1
  10. package/lib/aws/utils.d.ts.map +1 -1
  11. package/lib/aws/utils.js.map +1 -1
  12. package/lib/clickhouse/api/client.gen.d.ts +13 -0
  13. package/lib/clickhouse/api/client.gen.d.ts.map +1 -0
  14. package/lib/clickhouse/api/client.gen.js +7 -0
  15. package/lib/clickhouse/api/client.gen.js.map +1 -0
  16. package/lib/clickhouse/api/sdk.gen.d.ts +322 -0
  17. package/lib/clickhouse/api/sdk.gen.d.ts.map +1 -0
  18. package/lib/clickhouse/api/sdk.gen.js +683 -0
  19. package/lib/clickhouse/api/sdk.gen.js.map +1 -0
  20. package/lib/clickhouse/api/types.gen.d.ts +5030 -0
  21. package/lib/clickhouse/api/types.gen.d.ts.map +1 -0
  22. package/lib/clickhouse/api/types.gen.js +2 -0
  23. package/lib/clickhouse/api/types.gen.js.map +1 -0
  24. package/lib/clickhouse/api-key.d.ts +29 -0
  25. package/lib/clickhouse/api-key.d.ts.map +1 -0
  26. package/lib/clickhouse/api-key.js +83 -0
  27. package/lib/clickhouse/api-key.js.map +1 -0
  28. package/lib/clickhouse/api.d.ts +36 -0
  29. package/lib/clickhouse/api.d.ts.map +1 -0
  30. package/lib/clickhouse/api.js +48 -0
  31. package/lib/clickhouse/api.js.map +1 -0
  32. package/lib/clickhouse/index.d.ts +4 -0
  33. package/lib/clickhouse/index.d.ts.map +1 -0
  34. package/lib/clickhouse/index.js +4 -0
  35. package/lib/clickhouse/index.js.map +1 -0
  36. package/lib/clickhouse/organization.d.ts +16 -0
  37. package/lib/clickhouse/organization.d.ts.map +1 -0
  38. package/lib/clickhouse/organization.js +21 -0
  39. package/lib/clickhouse/organization.js.map +1 -0
  40. package/lib/clickhouse/service.d.ts +275 -0
  41. package/lib/clickhouse/service.d.ts.map +1 -0
  42. package/lib/clickhouse/service.js +274 -0
  43. package/lib/clickhouse/service.js.map +1 -0
  44. package/lib/cloudflare/account-api-token.d.ts.map +1 -1
  45. package/lib/cloudflare/account-api-token.js +1 -1
  46. package/lib/cloudflare/account-api-token.js.map +1 -1
  47. package/lib/cloudflare/bun-spa/bun-spa.d.ts +15 -0
  48. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -0
  49. package/lib/cloudflare/bun-spa/bun-spa.js +95 -0
  50. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -0
  51. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  52. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  53. package/lib/cloudflare/container.d.ts +11 -1
  54. package/lib/cloudflare/container.d.ts.map +1 -1
  55. package/lib/cloudflare/container.js.map +1 -1
  56. package/lib/cloudflare/index.d.ts +1 -0
  57. package/lib/cloudflare/index.d.ts.map +1 -1
  58. package/lib/cloudflare/index.js +1 -0
  59. package/lib/cloudflare/index.js.map +1 -1
  60. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  61. package/lib/cloudflare/miniflare/build-worker-options.js +3 -3
  62. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  63. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  64. package/lib/cloudflare/miniflare/miniflare-controller.js +2 -0
  65. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  66. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +1 -1
  67. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  68. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +36 -49
  69. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  70. package/lib/cloudflare/permission-groups.d.ts +2 -3
  71. package/lib/cloudflare/permission-groups.d.ts.map +1 -1
  72. package/lib/cloudflare/permission-groups.js +10 -15
  73. package/lib/cloudflare/permission-groups.js.map +1 -1
  74. package/lib/cloudflare/redwood/redwood.d.ts +1 -1
  75. package/lib/cloudflare/redwood/redwood.js +1 -1
  76. package/lib/cloudflare/util/filter-env-bindings.d.ts +62 -0
  77. package/lib/cloudflare/util/filter-env-bindings.d.ts.map +1 -0
  78. package/lib/cloudflare/util/filter-env-bindings.js +81 -0
  79. package/lib/cloudflare/util/filter-env-bindings.js.map +1 -0
  80. package/lib/cloudflare/website.d.ts +2 -2
  81. package/lib/cloudflare/website.d.ts.map +1 -1
  82. package/lib/cloudflare/website.js +6 -18
  83. package/lib/cloudflare/website.js.map +1 -1
  84. package/lib/cloudflare/worker-metadata.d.ts +1 -0
  85. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  86. package/lib/cloudflare/worker-metadata.js +1 -0
  87. package/lib/cloudflare/worker-metadata.js.map +1 -1
  88. package/lib/cloudflare/worker.d.ts +11 -1
  89. package/lib/cloudflare/worker.d.ts.map +1 -1
  90. package/lib/cloudflare/worker.js.map +1 -1
  91. package/lib/cloudflare/wrangler.json.d.ts +8 -0
  92. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  93. package/lib/cloudflare/wrangler.json.js +3 -4
  94. package/lib/cloudflare/wrangler.json.js.map +1 -1
  95. package/lib/coinbase/client.d.ts +54 -0
  96. package/lib/coinbase/client.d.ts.map +1 -0
  97. package/lib/coinbase/client.js +89 -0
  98. package/lib/coinbase/client.js.map +1 -0
  99. package/lib/coinbase/evm-account.d.ts +118 -0
  100. package/lib/coinbase/evm-account.d.ts.map +1 -0
  101. package/lib/coinbase/evm-account.js +198 -0
  102. package/lib/coinbase/evm-account.js.map +1 -0
  103. package/lib/coinbase/evm-smart-account.d.ts +122 -0
  104. package/lib/coinbase/evm-smart-account.d.ts.map +1 -0
  105. package/lib/coinbase/evm-smart-account.js +213 -0
  106. package/lib/coinbase/evm-smart-account.js.map +1 -0
  107. package/lib/coinbase/faucet.d.ts +11 -0
  108. package/lib/coinbase/faucet.d.ts.map +1 -0
  109. package/lib/coinbase/faucet.js +146 -0
  110. package/lib/coinbase/faucet.js.map +1 -0
  111. package/lib/coinbase/index.d.ts +4 -0
  112. package/lib/coinbase/index.d.ts.map +1 -0
  113. package/lib/coinbase/index.js +4 -0
  114. package/lib/coinbase/index.js.map +1 -0
  115. package/lib/coinbase/types.d.ts +28 -0
  116. package/lib/coinbase/types.d.ts.map +1 -0
  117. package/lib/coinbase/types.js +4 -0
  118. package/lib/{util/telemetry → coinbase}/types.js.map +1 -1
  119. package/lib/coinbase/utils.d.ts +13 -0
  120. package/lib/coinbase/utils.d.ts.map +1 -0
  121. package/lib/coinbase/utils.js +17 -0
  122. package/lib/coinbase/utils.js.map +1 -0
  123. package/lib/neon/api/types.gen.d.ts +7 -2
  124. package/lib/neon/api/types.gen.d.ts.map +1 -1
  125. package/lib/planetscale/api/types.gen.d.ts +20 -4
  126. package/lib/planetscale/api/types.gen.d.ts.map +1 -1
  127. package/lib/planetscale/api.d.ts.map +1 -1
  128. package/lib/planetscale/api.js +1 -0
  129. package/lib/planetscale/api.js.map +1 -1
  130. package/lib/planetscale/password.d.ts.map +1 -1
  131. package/lib/planetscale/password.js +1 -13
  132. package/lib/planetscale/password.js.map +1 -1
  133. package/lib/resource.d.ts.map +1 -1
  134. package/lib/resource.js +7 -3
  135. package/lib/resource.js.map +1 -1
  136. package/lib/scope.d.ts +4 -9
  137. package/lib/scope.d.ts.map +1 -1
  138. package/lib/scope.js +16 -37
  139. package/lib/scope.js.map +1 -1
  140. package/lib/state/instrumented-state-store.d.ts +1 -3
  141. package/lib/state/instrumented-state-store.d.ts.map +1 -1
  142. package/lib/state/instrumented-state-store.js +19 -21
  143. package/lib/state/instrumented-state-store.js.map +1 -1
  144. package/lib/test/bun.d.ts.map +1 -1
  145. package/lib/test/bun.js +1 -2
  146. package/lib/test/bun.js.map +1 -1
  147. package/lib/test/vitest.d.ts.map +1 -1
  148. package/lib/test/vitest.js +1 -2
  149. package/lib/test/vitest.js.map +1 -1
  150. package/lib/util/diff.d.ts +5 -0
  151. package/lib/util/diff.d.ts.map +1 -0
  152. package/lib/util/diff.js +14 -0
  153. package/lib/util/diff.js.map +1 -0
  154. package/lib/util/telemetry.d.ts +52 -0
  155. package/lib/util/telemetry.d.ts.map +1 -0
  156. package/lib/util/telemetry.js +191 -0
  157. package/lib/util/telemetry.js.map +1 -0
  158. package/package.json +19 -1
  159. package/src/alchemy.ts +5 -20
  160. package/src/apply.ts +29 -12
  161. package/src/aws/utils.ts +2 -1
  162. package/src/clickhouse/api/client.gen.ts +28 -0
  163. package/src/clickhouse/api/sdk.gen.ts +1286 -0
  164. package/src/clickhouse/api/types.gen.ts +5898 -0
  165. package/src/clickhouse/api-key.ts +136 -0
  166. package/src/clickhouse/api.ts +84 -0
  167. package/src/clickhouse/index.ts +3 -0
  168. package/src/clickhouse/organization.ts +31 -0
  169. package/src/clickhouse/service.ts +668 -0
  170. package/src/cloudflare/account-api-token.ts +1 -4
  171. package/src/cloudflare/bun-spa/bun-spa.ts +139 -0
  172. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  173. package/src/cloudflare/container.ts +20 -1
  174. package/src/cloudflare/index.ts +1 -0
  175. package/src/cloudflare/miniflare/build-worker-options.ts +6 -4
  176. package/src/cloudflare/miniflare/miniflare-controller.ts +2 -0
  177. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +41 -58
  178. package/src/cloudflare/permission-groups.ts +21 -49
  179. package/src/cloudflare/redwood/redwood.ts +1 -1
  180. package/src/cloudflare/util/filter-env-bindings.ts +96 -0
  181. package/src/cloudflare/website.ts +11 -24
  182. package/src/cloudflare/worker-metadata.ts +2 -0
  183. package/src/cloudflare/worker.ts +12 -1
  184. package/src/cloudflare/wrangler.json.ts +12 -8
  185. package/src/coinbase/client.ts +146 -0
  186. package/src/coinbase/evm-account.ts +317 -0
  187. package/src/coinbase/evm-smart-account.ts +339 -0
  188. package/src/coinbase/faucet.ts +173 -0
  189. package/src/coinbase/index.ts +3 -0
  190. package/src/coinbase/types.ts +33 -0
  191. package/src/coinbase/utils.ts +19 -0
  192. package/src/neon/api/types.gen.ts +13 -1
  193. package/src/planetscale/api/types.gen.ts +22 -2
  194. package/src/planetscale/api.ts +1 -0
  195. package/src/planetscale/password.ts +1 -14
  196. package/src/resource.ts +11 -4
  197. package/src/scope.ts +22 -45
  198. package/src/state/instrumented-state-store.ts +27 -26
  199. package/src/test/bun.ts +1 -2
  200. package/src/test/vitest.ts +1 -2
  201. package/src/util/diff.ts +14 -0
  202. package/src/util/telemetry.ts +297 -0
  203. package/workers/tunnel-proxy.js +1 -1
  204. package/lib/util/telemetry/client.d.ts +0 -31
  205. package/lib/util/telemetry/client.d.ts.map +0 -1
  206. package/lib/util/telemetry/client.js +0 -121
  207. package/lib/util/telemetry/client.js.map +0 -1
  208. package/lib/util/telemetry/constants.d.ts +0 -5
  209. package/lib/util/telemetry/constants.d.ts.map +0 -1
  210. package/lib/util/telemetry/constants.js +0 -8
  211. package/lib/util/telemetry/constants.js.map +0 -1
  212. package/lib/util/telemetry/context.d.ts +0 -15
  213. package/lib/util/telemetry/context.d.ts.map +0 -1
  214. package/lib/util/telemetry/context.js +0 -128
  215. package/lib/util/telemetry/context.js.map +0 -1
  216. package/lib/util/telemetry/index.d.ts +0 -3
  217. package/lib/util/telemetry/index.d.ts.map +0 -1
  218. package/lib/util/telemetry/index.js +0 -3
  219. package/lib/util/telemetry/index.js.map +0 -1
  220. package/lib/util/telemetry/types.d.ts +0 -68
  221. package/lib/util/telemetry/types.d.ts.map +0 -1
  222. package/lib/util/telemetry/types.js +0 -1
  223. package/src/util/telemetry/client.ts +0 -156
  224. package/src/util/telemetry/constants.ts +0 -13
  225. package/src/util/telemetry/context.ts +0 -154
  226. package/src/util/telemetry/index.ts +0 -2
  227. package/src/util/telemetry/types.ts +0 -105
@@ -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
+ );
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Request testnet tokens from Coinbase faucet for accounts with faucet metadata
4
+ *
5
+ * Usage:
6
+ * bun node_modules/alchemy/src/coinbase/faucet.ts # Fund all accounts with faucet config
7
+ * bun node_modules/alchemy/src/coinbase/faucet.ts dev # Fund only accounts in dev scope
8
+ * bun node_modules/alchemy/src/coinbase/faucet.ts backend/dev # Fund specific scope
9
+ */
10
+
11
+ import { readFile, access } from "node:fs/promises";
12
+ import { join } from "node:path";
13
+ import { glob } from "glob";
14
+ import { createCdpClient } from "./client.ts";
15
+ import type { EvmAccount } from "./evm-account.ts";
16
+ import type { EvmSmartAccount } from "./evm-smart-account.ts";
17
+ import type { FaucetNetwork, FaucetToken } from "./types.ts";
18
+
19
+ // Track funded combinations to avoid duplicates within this run
20
+ const funded = new Set<string>();
21
+
22
+ // Get optional scope argument
23
+ const scope = process.argv[2];
24
+
25
+ // Initialize CDP client (uses CDP_* env vars automatically)
26
+ const cdp = await createCdpClient();
27
+
28
+ let stateFiles: string[];
29
+
30
+ if (scope) {
31
+ // If scope provided, find matching directories
32
+ const scopePath = join(".alchemy", scope);
33
+
34
+ let scopeExists = false;
35
+ try {
36
+ await access(scopePath);
37
+ scopeExists = true;
38
+ } catch {
39
+ scopeExists = false;
40
+ }
41
+
42
+ if (scopeExists) {
43
+ // Exact match found
44
+ console.log(`Using scope: ${scope}`);
45
+ stateFiles = await glob("*.json", {
46
+ cwd: scopePath,
47
+ absolute: true,
48
+ });
49
+ } else {
50
+ // Try to find directories that end with the provided scope
51
+ // We need to find directories, so we'll look for JSON files and extract the directory paths
52
+ const allFiles = await glob(`**/${scope}/*.json`, {
53
+ cwd: ".alchemy",
54
+ });
55
+
56
+ // Extract unique directory paths
57
+ const possibleScopes = [
58
+ ...new Set(allFiles.map((f) => f.replace(/\/[^/]+\.json$/, ""))),
59
+ ];
60
+
61
+ if (possibleScopes.length === 0) {
62
+ console.error(`No scope found matching: ${scope}`);
63
+ process.exit(1);
64
+ } else if (possibleScopes.length === 1) {
65
+ // Found exactly one match
66
+ const foundScope = possibleScopes[0];
67
+ console.log(`Using scope: ${foundScope}`);
68
+ stateFiles = await glob("*.json", {
69
+ cwd: join(".alchemy", foundScope),
70
+ absolute: true,
71
+ });
72
+ } else {
73
+ // Multiple matches found - use all of them
74
+ console.log(`Found ${possibleScopes.length} scopes matching '${scope}':`);
75
+ for (const s of possibleScopes) {
76
+ console.log(` - ${s}`);
77
+ }
78
+
79
+ // Collect state files from all matching scopes
80
+ stateFiles = [];
81
+ for (const foundScope of possibleScopes) {
82
+ const files = await glob("*.json", {
83
+ cwd: join(".alchemy", foundScope),
84
+ absolute: true,
85
+ });
86
+ stateFiles.push(...files);
87
+ }
88
+ }
89
+ }
90
+ } else {
91
+ // No scope provided, search all .alchemy subdirectories
92
+ console.log("Searching all scopes for accounts with faucet configuration...");
93
+ stateFiles = await glob("**/*.json", {
94
+ cwd: ".alchemy",
95
+ absolute: true,
96
+ });
97
+ }
98
+
99
+ const allStates = (
100
+ await Promise.all(
101
+ stateFiles.map(async (file) => {
102
+ try {
103
+ const content = await readFile(file, "utf-8");
104
+ return JSON.parse(content);
105
+ } catch {
106
+ return null;
107
+ }
108
+ }),
109
+ )
110
+ ).filter(Boolean);
111
+
112
+ const accounts = allStates
113
+ .filter(
114
+ (state) =>
115
+ state?.kind?.startsWith("coinbase::evm") &&
116
+ state.output &&
117
+ (state.output as EvmAccount | EvmSmartAccount).faucet,
118
+ )
119
+ .map((state) => {
120
+ const output = state.output as EvmAccount | EvmSmartAccount;
121
+ return {
122
+ address: output.address,
123
+ name: output.name,
124
+ faucet: output.faucet!,
125
+ };
126
+ });
127
+
128
+ if (accounts.length === 0) {
129
+ console.log("No accounts with faucet configuration found");
130
+ process.exit(0);
131
+ }
132
+
133
+ console.log(`Found ${accounts.length} accounts with faucet configuration\n`);
134
+
135
+ for (const account of accounts) {
136
+ console.log(`Account: ${account.name} (${account.address})`);
137
+
138
+ for (const [network, tokens] of Object.entries(account.faucet)) {
139
+ for (const token of tokens) {
140
+ const key = `${account.address}-${network}-${token}`;
141
+
142
+ // Skip if already processed in this run
143
+ if (funded.has(key)) {
144
+ console.log(` ⏭️ Skipping ${token} on ${network} (already requested)`);
145
+ continue;
146
+ }
147
+
148
+ // Request tokens from faucet
149
+ try {
150
+ console.log(` 💧 Requesting ${token} on ${network}...`);
151
+
152
+ const response = await cdp.evm.requestFaucet({
153
+ address: account.address,
154
+ network: network as FaucetNetwork,
155
+ token: token as FaucetToken,
156
+ });
157
+
158
+ console.log(` ✅ Funded: ${response.transactionHash}`);
159
+ funded.add(key);
160
+
161
+ // Small delay to avoid rate limits
162
+ await new Promise((resolve) => setTimeout(resolve, 1000));
163
+ } catch (error: any) {
164
+ console.log(
165
+ ` ❌ Error funding ${token} on ${network}: ${error.message}`,
166
+ );
167
+ }
168
+ }
169
+ }
170
+ console.log();
171
+ }
172
+
173
+ console.log("✨ Funding complete!");
@@ -0,0 +1,3 @@
1
+ export * from "./types.ts";
2
+ export * from "./evm-account.ts";
3
+ export * from "./evm-smart-account.ts";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Common types for Coinbase CDP resources
3
+ */
4
+
5
+ /**
6
+ * Ethereum address format (20 bytes, 40 hex chars)
7
+ */
8
+ export type Address = `0x${string}`;
9
+
10
+ /**
11
+ * Private key format (32 bytes, 64 hex chars)
12
+ */
13
+ export type PrivateKey = `0x${string}`;
14
+
15
+ /**
16
+ * Transaction hash format (32 bytes, 64 hex chars)
17
+ */
18
+ export type TransactionHash = `0x${string}`;
19
+
20
+ /**
21
+ * Supported testnet networks for faucet requests
22
+ */
23
+ export type FaucetNetwork = "base-sepolia" | "ethereum-sepolia";
24
+
25
+ /**
26
+ * Supported tokens for faucet requests
27
+ */
28
+ export type FaucetToken = "eth" | "usdc" | "eurc" | "cbbtc";
29
+
30
+ /**
31
+ * Faucet configuration for requesting testnet tokens
32
+ */
33
+ export type FaucetConfig = Partial<Record<FaucetNetwork, FaucetToken[]>>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Utility functions for Coinbase CDP resources
3
+ */
4
+
5
+ /**
6
+ * Validates that a CDP account name follows the required format.
7
+ * CDP only allows letters, numbers, and hyphens in account names.
8
+ * Names cannot start or end with a hyphen.
9
+ *
10
+ * @param name - The account name to validate
11
+ * @throws Error if the name contains invalid characters or starts/ends with hyphen
12
+ */
13
+ export function validateAccountName(name: string): void {
14
+ if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(name)) {
15
+ throw new Error(
16
+ `Invalid account name '${name}'. CDP only allows letters, numbers, and hyphens. Names cannot start or end with a hyphen.`,
17
+ );
18
+ }
19
+ }
@@ -1899,6 +1899,7 @@ export type BillingAccountState =
1899
1899
  export type BillingSubscriptionType =
1900
1900
  | "UNKNOWN"
1901
1901
  | "direct_sales"
1902
+ | "direct_sales_v3"
1902
1903
  | "aws_marketplace"
1903
1904
  | "free_v2"
1904
1905
  | "free_v3"
@@ -2527,6 +2528,11 @@ export type NeonAuthCreateIntegrationRequest = {
2527
2528
  role_name?: string;
2528
2529
  };
2529
2530
 
2531
+ export type EnableNeonAuthIntegrationRequest = {
2532
+ auth_provider: NeonAuthSupportedAuthProvider;
2533
+ database_name?: string;
2534
+ };
2535
+
2530
2536
  export type NeonAuthCreateIntegrationResponse = {
2531
2537
  auth_provider: NeonAuthSupportedAuthProvider;
2532
2538
  auth_provider_project_id: string;
@@ -2677,7 +2683,12 @@ export type RoleOperations = RoleResponse & OperationsResponse;
2677
2683
 
2678
2684
  export type JwksCreationOperation = JwksResponse & OperationsResponse;
2679
2685
 
2680
- export type SupportTicketSeverity = "low" | "normal" | "high" | "critical";
2686
+ export type SupportTicketSeverity =
2687
+ | "low"
2688
+ | "normal"
2689
+ | "high"
2690
+ | "critical"
2691
+ | "urgent";
2681
2692
 
2682
2693
  export type AnnotationData = {
2683
2694
  object: AnnotationObjectData;
@@ -2758,6 +2769,7 @@ export type Snapshot = {
2758
2769
  source_branch_id?: string;
2759
2770
  created_at: string;
2760
2771
  expires_at?: string;
2772
+ manual?: boolean;
2761
2773
  };
2762
2774
 
2763
2775
  export type SnapshotUpdateRequest = {
@@ -514,6 +514,10 @@ export type PaginatedPostgresClusterResizeRequest = {
514
514
  * The SKU representing the branch cluster for display
515
515
  */
516
516
  cluster_display_name: string;
517
+ /**
518
+ * Whether or not this is a metal database
519
+ */
520
+ cluster_metal: boolean;
517
521
  /**
518
522
  * The total number of replicas
519
523
  */
@@ -532,6 +536,10 @@ export type PaginatedPostgresClusterResizeRequest = {
532
536
  * The previous SKU representing the branch cluster for display
533
537
  */
534
538
  previous_cluster_display_name: string;
539
+ /**
540
+ * Whether or not the previous SKU was a metal database
541
+ */
542
+ previous_cluster_metal: boolean;
535
543
  /**
536
544
  * The previous total number of replicas
537
545
  */
@@ -652,6 +660,10 @@ export type PostgresClusterResizeRequest = {
652
660
  * The SKU representing the branch cluster for display
653
661
  */
654
662
  cluster_display_name: string;
663
+ /**
664
+ * Whether or not this is a metal database
665
+ */
666
+ cluster_metal: boolean;
655
667
  /**
656
668
  * The total number of replicas
657
669
  */
@@ -670,6 +682,10 @@ export type PostgresClusterResizeRequest = {
670
682
  * The previous SKU representing the branch cluster for display
671
683
  */
672
684
  previous_cluster_display_name: string;
685
+ /**
686
+ * Whether or not the previous SKU was a metal database
687
+ */
688
+ previous_cluster_metal: boolean;
673
689
  /**
674
690
  * The previous total number of replicas
675
691
  */
@@ -1301,7 +1317,8 @@ export type PostgresClusterExtension = {
1301
1317
  | "milliseconds"
1302
1318
  | "seconds"
1303
1319
  | "select"
1304
- | "string";
1320
+ | "string"
1321
+ | "time";
1305
1322
  /**
1306
1323
  * The default value of the parameter
1307
1324
  */
@@ -1408,7 +1425,8 @@ export type PostgresClusterParameter = {
1408
1425
  | "milliseconds"
1409
1426
  | "seconds"
1410
1427
  | "select"
1411
- | "string";
1428
+ | "string"
1429
+ | "time";
1412
1430
  /**
1413
1431
  * The default value of the parameter
1414
1432
  */
@@ -6943,6 +6961,7 @@ export type PaginatedDatabaseWebhook = {
6943
6961
  | "branch.anomaly"
6944
6962
  | "branch.schema_recommendation"
6945
6963
  | "branch.sleeping"
6964
+ | "branch.start_maintenance"
6946
6965
  | "cluster.storage"
6947
6966
  | "database.access_request"
6948
6967
  | "deploy_request.closed"
@@ -7004,6 +7023,7 @@ export type DatabaseWebhook = {
7004
7023
  | "branch.anomaly"
7005
7024
  | "branch.schema_recommendation"
7006
7025
  | "branch.sleeping"
7026
+ | "branch.start_maintenance"
7007
7027
  | "cluster.storage"
7008
7028
  | "database.access_request"
7009
7029
  | "deploy_request.closed"
@@ -67,6 +67,7 @@ export function createPlanetScaleClient(
67
67
  headers: {
68
68
  Authorization: token, // PlanetScale does not use the Bearer prefix
69
69
  },
70
+ throwOnError: true,
70
71
  }),
71
72
  );
72
73
  client.interceptors.error.use((error, response, request) => {