alchemy 0.69.1 → 0.70.1
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/bin/alchemy.js +3883 -3906
- package/bin/alchemy.ts +2 -0
- package/bin/commands/telemetry.ts +33 -0
- package/bin/services/execute-alchemy.ts +7 -0
- package/bin/trpc.ts +4 -9
- package/lib/alchemy.d.ts +7 -3
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +3 -15
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.js +24 -9
- package/lib/apply.js.map +1 -1
- package/lib/aws/utils.d.ts +2 -1
- package/lib/aws/utils.d.ts.map +1 -1
- package/lib/aws/utils.js.map +1 -1
- package/lib/clickhouse/api/client.gen.d.ts +13 -0
- package/lib/clickhouse/api/client.gen.d.ts.map +1 -0
- package/lib/clickhouse/api/client.gen.js +7 -0
- package/lib/clickhouse/api/client.gen.js.map +1 -0
- package/lib/clickhouse/api/sdk.gen.d.ts +322 -0
- package/lib/clickhouse/api/sdk.gen.d.ts.map +1 -0
- package/lib/clickhouse/api/sdk.gen.js +683 -0
- package/lib/clickhouse/api/sdk.gen.js.map +1 -0
- package/lib/clickhouse/api/types.gen.d.ts +5030 -0
- package/lib/clickhouse/api/types.gen.d.ts.map +1 -0
- package/lib/clickhouse/api/types.gen.js +2 -0
- package/lib/clickhouse/api/types.gen.js.map +1 -0
- package/lib/clickhouse/api-key.d.ts +29 -0
- package/lib/clickhouse/api-key.d.ts.map +1 -0
- package/lib/clickhouse/api-key.js +83 -0
- package/lib/clickhouse/api-key.js.map +1 -0
- package/lib/clickhouse/api.d.ts +36 -0
- package/lib/clickhouse/api.d.ts.map +1 -0
- package/lib/clickhouse/api.js +48 -0
- package/lib/clickhouse/api.js.map +1 -0
- package/lib/clickhouse/index.d.ts +4 -0
- package/lib/clickhouse/index.d.ts.map +1 -0
- package/lib/clickhouse/index.js +4 -0
- package/lib/clickhouse/index.js.map +1 -0
- package/lib/clickhouse/organization.d.ts +16 -0
- package/lib/clickhouse/organization.d.ts.map +1 -0
- package/lib/clickhouse/organization.js +21 -0
- package/lib/clickhouse/organization.js.map +1 -0
- package/lib/clickhouse/service.d.ts +275 -0
- package/lib/clickhouse/service.d.ts.map +1 -0
- package/lib/clickhouse/service.js +274 -0
- package/lib/clickhouse/service.js.map +1 -0
- package/lib/cloudflare/account-api-token.d.ts.map +1 -1
- package/lib/cloudflare/account-api-token.js +1 -1
- package/lib/cloudflare/account-api-token.js.map +1 -1
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +2 -0
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/bun-spa/bun-spa.d.ts +13 -0
- package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -0
- package/lib/cloudflare/bun-spa/bun-spa.js +95 -0
- package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -0
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/container.d.ts +11 -1
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +33 -6
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-controller.js +2 -0
- package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +36 -49
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
- package/lib/cloudflare/permission-groups.d.ts +2 -3
- package/lib/cloudflare/permission-groups.d.ts.map +1 -1
- package/lib/cloudflare/permission-groups.js +10 -15
- package/lib/cloudflare/permission-groups.js.map +1 -1
- package/lib/cloudflare/redwood/redwood.d.ts +1 -1
- package/lib/cloudflare/redwood/redwood.js +1 -1
- package/lib/cloudflare/util/filter-env-bindings.d.ts +62 -0
- package/lib/cloudflare/util/filter-env-bindings.d.ts.map +1 -0
- package/lib/cloudflare/util/filter-env-bindings.js +81 -0
- package/lib/cloudflare/util/filter-env-bindings.js.map +1 -0
- package/lib/cloudflare/website.d.ts +2 -2
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +6 -18
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +1 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +1 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +11 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +8 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +3 -4
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/coinbase/client.d.ts +54 -0
- package/lib/coinbase/client.d.ts.map +1 -0
- package/lib/coinbase/client.js +89 -0
- package/lib/coinbase/client.js.map +1 -0
- package/lib/coinbase/evm-account.d.ts +118 -0
- package/lib/coinbase/evm-account.d.ts.map +1 -0
- package/lib/coinbase/evm-account.js +198 -0
- package/lib/coinbase/evm-account.js.map +1 -0
- package/lib/coinbase/evm-smart-account.d.ts +122 -0
- package/lib/coinbase/evm-smart-account.d.ts.map +1 -0
- package/lib/coinbase/evm-smart-account.js +213 -0
- package/lib/coinbase/evm-smart-account.js.map +1 -0
- package/lib/coinbase/faucet.d.ts +11 -0
- package/lib/coinbase/faucet.d.ts.map +1 -0
- package/lib/coinbase/faucet.js +146 -0
- package/lib/coinbase/faucet.js.map +1 -0
- package/lib/coinbase/index.d.ts +4 -0
- package/lib/coinbase/index.d.ts.map +1 -0
- package/lib/coinbase/index.js +4 -0
- package/lib/coinbase/index.js.map +1 -0
- package/lib/coinbase/types.d.ts +28 -0
- package/lib/coinbase/types.d.ts.map +1 -0
- package/lib/coinbase/types.js +4 -0
- package/lib/{util/telemetry → coinbase}/types.js.map +1 -1
- package/lib/coinbase/utils.d.ts +13 -0
- package/lib/coinbase/utils.d.ts.map +1 -0
- package/lib/coinbase/utils.js +17 -0
- package/lib/coinbase/utils.js.map +1 -0
- package/lib/neon/api/types.gen.d.ts +7 -2
- package/lib/neon/api/types.gen.d.ts.map +1 -1
- package/lib/planetscale/api/types.gen.d.ts +20 -4
- package/lib/planetscale/api/types.gen.d.ts.map +1 -1
- package/lib/planetscale/api.d.ts.map +1 -1
- package/lib/planetscale/api.js +1 -0
- package/lib/planetscale/api.js.map +1 -1
- package/lib/planetscale/password.d.ts.map +1 -1
- package/lib/planetscale/password.js +1 -13
- package/lib/planetscale/password.js.map +1 -1
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js +7 -3
- package/lib/resource.js.map +1 -1
- package/lib/scope.d.ts +9 -9
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +18 -37
- package/lib/scope.js.map +1 -1
- package/lib/state/instrumented-state-store.d.ts +1 -3
- package/lib/state/instrumented-state-store.d.ts.map +1 -1
- package/lib/state/instrumented-state-store.js +19 -21
- package/lib/state/instrumented-state-store.js.map +1 -1
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +1 -2
- package/lib/test/bun.js.map +1 -1
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +1 -2
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/diff.d.ts +5 -0
- package/lib/util/diff.d.ts.map +1 -0
- package/lib/util/diff.js +14 -0
- package/lib/util/diff.js.map +1 -0
- package/lib/util/idempotent-spawn.js +1 -1
- package/lib/util/idempotent-spawn.js.map +1 -1
- package/lib/util/telemetry.d.ts +53 -0
- package/lib/util/telemetry.d.ts.map +1 -0
- package/lib/util/telemetry.js +215 -0
- package/lib/util/telemetry.js.map +1 -0
- package/package.json +19 -1
- package/src/alchemy.ts +10 -20
- package/src/apply.ts +29 -12
- package/src/aws/utils.ts +2 -1
- package/src/clickhouse/api/client.gen.ts +28 -0
- package/src/clickhouse/api/sdk.gen.ts +1286 -0
- package/src/clickhouse/api/types.gen.ts +5898 -0
- package/src/clickhouse/api-key.ts +136 -0
- package/src/clickhouse/api.ts +84 -0
- package/src/clickhouse/index.ts +3 -0
- package/src/clickhouse/organization.ts +31 -0
- package/src/clickhouse/service.ts +668 -0
- package/src/cloudflare/account-api-token.ts +1 -4
- package/src/cloudflare/api.ts +2 -0
- package/src/cloudflare/bun-spa/bun-spa.ts +136 -0
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/container.ts +20 -1
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/miniflare/build-worker-options.ts +38 -8
- package/src/cloudflare/miniflare/miniflare-controller.ts +2 -0
- package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +41 -58
- package/src/cloudflare/permission-groups.ts +21 -49
- package/src/cloudflare/redwood/redwood.ts +1 -1
- package/src/cloudflare/util/filter-env-bindings.ts +96 -0
- package/src/cloudflare/website.ts +11 -24
- package/src/cloudflare/worker-metadata.ts +2 -0
- package/src/cloudflare/worker.ts +12 -1
- package/src/cloudflare/wrangler.json.ts +12 -8
- package/src/coinbase/client.ts +146 -0
- package/src/coinbase/evm-account.ts +317 -0
- package/src/coinbase/evm-smart-account.ts +339 -0
- package/src/coinbase/faucet.ts +173 -0
- package/src/coinbase/index.ts +3 -0
- package/src/coinbase/types.ts +33 -0
- package/src/coinbase/utils.ts +19 -0
- package/src/neon/api/types.gen.ts +13 -1
- package/src/planetscale/api/types.gen.ts +22 -2
- package/src/planetscale/api.ts +1 -0
- package/src/planetscale/password.ts +1 -14
- package/src/resource.ts +11 -4
- package/src/scope.ts +29 -45
- package/src/state/instrumented-state-store.ts +27 -26
- package/src/test/bun.ts +1 -2
- package/src/test/vitest.ts +1 -2
- package/src/util/diff.ts +14 -0
- package/src/util/idempotent-spawn.ts +1 -1
- package/src/util/telemetry.ts +328 -0
- package/templates/tanstack-start/package.json +2 -2
- package/workers/tunnel-proxy.js +1 -1
- package/lib/util/telemetry/client.d.ts +0 -31
- package/lib/util/telemetry/client.d.ts.map +0 -1
- package/lib/util/telemetry/client.js +0 -121
- package/lib/util/telemetry/client.js.map +0 -1
- package/lib/util/telemetry/constants.d.ts +0 -5
- package/lib/util/telemetry/constants.d.ts.map +0 -1
- package/lib/util/telemetry/constants.js +0 -8
- package/lib/util/telemetry/constants.js.map +0 -1
- package/lib/util/telemetry/context.d.ts +0 -15
- package/lib/util/telemetry/context.d.ts.map +0 -1
- package/lib/util/telemetry/context.js +0 -128
- package/lib/util/telemetry/context.js.map +0 -1
- package/lib/util/telemetry/index.d.ts +0 -3
- package/lib/util/telemetry/index.d.ts.map +0 -1
- package/lib/util/telemetry/index.js +0 -3
- package/lib/util/telemetry/index.js.map +0 -1
- package/lib/util/telemetry/types.d.ts +0 -68
- package/lib/util/telemetry/types.d.ts.map +0 -1
- package/lib/util/telemetry/types.js +0 -1
- package/src/util/telemetry/client.ts +0 -156
- package/src/util/telemetry/constants.ts +0 -13
- package/src/util/telemetry/context.ts +0 -154
- package/src/util/telemetry/index.ts +0 -2
- package/src/util/telemetry/types.ts +0 -105
|
@@ -3,7 +3,6 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Exec } from "../os/index.ts";
|
|
5
5
|
import { Scope } from "../scope.ts";
|
|
6
|
-
import { isSecret } from "../secret.ts";
|
|
7
6
|
import { dedent } from "../util/dedent.ts";
|
|
8
7
|
import { logger } from "../util/logger.ts";
|
|
9
8
|
import { Assets } from "./assets.ts";
|
|
@@ -11,6 +10,10 @@ import type { Bindings } from "./bindings.ts";
|
|
|
11
10
|
import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.gen.ts";
|
|
12
11
|
import { unionCompatibilityFlags } from "./compatibility-presets.ts";
|
|
13
12
|
import { writeMiniflareSymlink } from "./miniflare/symlink-miniflare-state.ts";
|
|
13
|
+
import {
|
|
14
|
+
extractStringAndSecretBindings,
|
|
15
|
+
unencryptSecrets,
|
|
16
|
+
} from "./util/filter-env-bindings.ts";
|
|
14
17
|
import { type AssetsConfig, Worker, type WorkerProps } from "./worker.ts";
|
|
15
18
|
import { WranglerJson, type WranglerJsonSpec } from "./wrangler.json.ts";
|
|
16
19
|
|
|
@@ -67,7 +70,7 @@ export interface WebsiteProps<B extends Bindings>
|
|
|
67
70
|
/**
|
|
68
71
|
* The command to run to start the dev server
|
|
69
72
|
*/
|
|
70
|
-
command
|
|
73
|
+
command?: string;
|
|
71
74
|
/**
|
|
72
75
|
* Additional environment variables to set when running the dev command
|
|
73
76
|
*/
|
|
@@ -226,14 +229,7 @@ export async function Website<B extends Bindings>(
|
|
|
226
229
|
const env = {
|
|
227
230
|
...(process.env ?? {}),
|
|
228
231
|
...(props.env ?? {}),
|
|
229
|
-
...
|
|
230
|
-
Object.entries(props.bindings ?? {}).flatMap(([key, value]) => {
|
|
231
|
-
if (typeof value === "string" || (isSecret(value) && secrets)) {
|
|
232
|
-
return [[key, value]];
|
|
233
|
-
}
|
|
234
|
-
return [];
|
|
235
|
-
}),
|
|
236
|
-
),
|
|
232
|
+
...extractStringAndSecretBindings(props.bindings ?? {}, secrets),
|
|
237
233
|
};
|
|
238
234
|
const worker = {
|
|
239
235
|
...workerProps,
|
|
@@ -302,9 +298,10 @@ export async function Website<B extends Bindings>(
|
|
|
302
298
|
}
|
|
303
299
|
|
|
304
300
|
let url: string | undefined;
|
|
305
|
-
|
|
301
|
+
const devCommand = typeof dev === "string" ? dev : dev?.command;
|
|
302
|
+
if (devCommand && scope.local) {
|
|
306
303
|
url = await scope.spawn(name, {
|
|
307
|
-
cmd:
|
|
304
|
+
cmd: devCommand,
|
|
308
305
|
cwd: paths.cwd,
|
|
309
306
|
extract: (line) => {
|
|
310
307
|
const URL_REGEX =
|
|
@@ -317,17 +314,7 @@ export async function Website<B extends Bindings>(
|
|
|
317
314
|
}
|
|
318
315
|
},
|
|
319
316
|
env: {
|
|
320
|
-
...
|
|
321
|
-
Object.entries(env ?? {}).flatMap(([key, value]) => {
|
|
322
|
-
if (isSecret(value)) {
|
|
323
|
-
return [[key, value.unencrypted]];
|
|
324
|
-
}
|
|
325
|
-
if (typeof value === "string") {
|
|
326
|
-
return [[key, value]];
|
|
327
|
-
}
|
|
328
|
-
return [];
|
|
329
|
-
}),
|
|
330
|
-
),
|
|
317
|
+
...unencryptSecrets(env ?? {}),
|
|
331
318
|
...(typeof dev === "object" ? dev.env : {}),
|
|
332
319
|
FORCE_COLOR: "1",
|
|
333
320
|
...process.env,
|
|
@@ -371,7 +358,7 @@ export const spreadBuildProps = (
|
|
|
371
358
|
export const spreadDevProps = (
|
|
372
359
|
props: { dev?: WebsiteProps<Bindings>["dev"] } | undefined,
|
|
373
360
|
defaultCommand: string,
|
|
374
|
-
): WebsiteProps<Bindings>["dev"] => {
|
|
361
|
+
): Exclude<WebsiteProps<Bindings>["dev"], undefined> => {
|
|
375
362
|
if (typeof props?.dev === "object") {
|
|
376
363
|
return {
|
|
377
364
|
...props.dev,
|
|
@@ -183,6 +183,7 @@ export interface WorkerMetadata {
|
|
|
183
183
|
observability: {
|
|
184
184
|
enabled: boolean;
|
|
185
185
|
};
|
|
186
|
+
logpush?: boolean;
|
|
186
187
|
migrations?: SingleStepMigration;
|
|
187
188
|
main_module?: string;
|
|
188
189
|
body_part?: string;
|
|
@@ -325,6 +326,7 @@ export async function prepareWorkerMetadata(
|
|
|
325
326
|
observability: {
|
|
326
327
|
enabled: props.observability?.enabled !== false,
|
|
327
328
|
},
|
|
329
|
+
logpush: props.logpush ?? false,
|
|
328
330
|
// TODO(sam): base64 encode instead? 0 collision risk vs readability.
|
|
329
331
|
tags: [
|
|
330
332
|
// encode a mapping table of Durable Object stable ID -> binding name
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -179,6 +179,17 @@ export interface BaseWorkerProps<
|
|
|
179
179
|
enabled?: boolean;
|
|
180
180
|
};
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Enable Workers Logpush to export trace events (request/response metadata,
|
|
184
|
+
* console logs, and exceptions) to external destinations.
|
|
185
|
+
*
|
|
186
|
+
* Requires a separate Logpush job configuration via the Cloudflare API.
|
|
187
|
+
*
|
|
188
|
+
* @see https://developers.cloudflare.com/workers/observability/logging/logpush
|
|
189
|
+
* @default false
|
|
190
|
+
*/
|
|
191
|
+
logpush?: boolean;
|
|
192
|
+
|
|
182
193
|
/**
|
|
183
194
|
* Whether to adopt the Worker if it already exists when creating
|
|
184
195
|
*/
|
|
@@ -324,7 +335,7 @@ export interface BaseWorkerProps<
|
|
|
324
335
|
dev?:
|
|
325
336
|
| {
|
|
326
337
|
/**
|
|
327
|
-
* Port to use for local development
|
|
338
|
+
* Port to use for local development.
|
|
328
339
|
*/
|
|
329
340
|
port?: number;
|
|
330
341
|
/**
|
|
@@ -4,6 +4,7 @@ import type { Context } from "../context.ts";
|
|
|
4
4
|
import { Resource } from "../resource.ts";
|
|
5
5
|
import { Scope } from "../scope.ts";
|
|
6
6
|
import { isSecret } from "../secret.ts";
|
|
7
|
+
import { unencryptSecrets } from "./util/filter-env-bindings.ts";
|
|
7
8
|
import { assertNever } from "../util/assert-never.ts";
|
|
8
9
|
import type { Bindings, WorkerBindingRateLimit } from "./bindings.ts";
|
|
9
10
|
import type { R2BucketJurisdiction } from "./bucket.ts";
|
|
@@ -162,6 +163,7 @@ export async function WranglerJson(
|
|
|
162
163
|
: undefined,
|
|
163
164
|
placement: worker.placement,
|
|
164
165
|
limits: worker.limits,
|
|
166
|
+
logpush: worker.logpush,
|
|
165
167
|
};
|
|
166
168
|
|
|
167
169
|
// Process bindings if they exist
|
|
@@ -210,14 +212,7 @@ export async function WranglerJson(
|
|
|
210
212
|
// but do not modify `finalSpec` so that way secrets aren't written to state unencrypted.
|
|
211
213
|
const withSecretsUnwrapped = {
|
|
212
214
|
...finalSpec,
|
|
213
|
-
vars: {
|
|
214
|
-
...finalSpec.vars,
|
|
215
|
-
...Object.fromEntries(
|
|
216
|
-
Object.entries(finalSpec.vars ?? {}).map(([key, value]) =>
|
|
217
|
-
isSecret(value) ? [key, value.unencrypted] : [key, value],
|
|
218
|
-
),
|
|
219
|
-
),
|
|
220
|
-
},
|
|
215
|
+
vars: unencryptSecrets(finalSpec.vars ?? {}),
|
|
221
216
|
};
|
|
222
217
|
await writeJSON(filePath, withSecretsUnwrapped);
|
|
223
218
|
} else {
|
|
@@ -275,6 +270,15 @@ export interface WranglerJsonSpec {
|
|
|
275
270
|
cpu_ms?: number;
|
|
276
271
|
};
|
|
277
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Send Trace Events from this Worker to Workers Logpush.
|
|
275
|
+
*
|
|
276
|
+
* This will not configure a corresponding Logpush job automatically.
|
|
277
|
+
*
|
|
278
|
+
* @default false
|
|
279
|
+
*/
|
|
280
|
+
logpush?: boolean;
|
|
281
|
+
|
|
278
282
|
/**
|
|
279
283
|
* Whether to enable a workers.dev URL for this worker
|
|
280
284
|
*/
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { CdpClient } from "@coinbase/cdp-sdk";
|
|
2
|
+
import type { Secret } from "../secret.ts";
|
|
3
|
+
import { logger } from "../util/logger.ts";
|
|
4
|
+
import { importPeer } from "../util/peer.ts";
|
|
5
|
+
|
|
6
|
+
export interface CoinbaseClientOptions {
|
|
7
|
+
/**
|
|
8
|
+
* CDP API Key ID. If not provided, falls back to CDP_API_KEY_ID environment variable.
|
|
9
|
+
*/
|
|
10
|
+
apiKeyId?: Secret<string> | string;
|
|
11
|
+
/**
|
|
12
|
+
* CDP API Key Secret. If not provided, falls back to CDP_API_KEY_SECRET environment variable.
|
|
13
|
+
* Must be wrapped in alchemy.secret() for security.
|
|
14
|
+
*/
|
|
15
|
+
apiKeySecret?: Secret<string>;
|
|
16
|
+
/**
|
|
17
|
+
* CDP Wallet Secret. If not provided, falls back to CDP_WALLET_SECRET environment variable.
|
|
18
|
+
* Must be wrapped in alchemy.secret() for security.
|
|
19
|
+
*/
|
|
20
|
+
walletSecret?: Secret<string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create an authenticated CDP client
|
|
25
|
+
*
|
|
26
|
+
* The CDP SDK automatically looks for these environment variables:
|
|
27
|
+
* - CDP_API_KEY_ID
|
|
28
|
+
* - CDP_API_KEY_SECRET
|
|
29
|
+
* - CDP_WALLET_SECRET
|
|
30
|
+
*
|
|
31
|
+
* You can override them by passing values in the options.
|
|
32
|
+
*
|
|
33
|
+
* @param options Options for creating the client
|
|
34
|
+
* @returns An authenticated CDP client
|
|
35
|
+
*/
|
|
36
|
+
export async function createCdpClient(
|
|
37
|
+
options: CoinbaseClientOptions = {},
|
|
38
|
+
): Promise<CdpClient> {
|
|
39
|
+
const { CdpClient } = await importPeer(
|
|
40
|
+
import("@coinbase/cdp-sdk"),
|
|
41
|
+
"Coinbase resources",
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Build options object for CdpClient constructor
|
|
45
|
+
const clientOptions: {
|
|
46
|
+
apiKeyId?: string;
|
|
47
|
+
apiKeySecret?: string;
|
|
48
|
+
walletSecret?: string;
|
|
49
|
+
} = {};
|
|
50
|
+
|
|
51
|
+
// Only set values if explicitly provided, otherwise let CDP SDK use its defaults
|
|
52
|
+
if (options.apiKeyId) {
|
|
53
|
+
clientOptions.apiKeyId =
|
|
54
|
+
typeof options.apiKeyId === "string"
|
|
55
|
+
? options.apiKeyId
|
|
56
|
+
: options.apiKeyId.unencrypted;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (options.apiKeySecret) {
|
|
60
|
+
clientOptions.apiKeySecret = options.apiKeySecret.unencrypted;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (options.walletSecret) {
|
|
64
|
+
clientOptions.walletSecret = options.walletSecret.unencrypted;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// CDP SDK will automatically use environment variables if not provided:
|
|
68
|
+
// CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET
|
|
69
|
+
return new CdpClient(clientOptions);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handle CDP SDK deletion errors gracefully
|
|
74
|
+
*
|
|
75
|
+
* @param error The error from CDP SDK
|
|
76
|
+
* @param resourceType The type of resource being deleted
|
|
77
|
+
* @param resourceId The ID or name of the resource
|
|
78
|
+
*/
|
|
79
|
+
export function handleCdpDeleteError(
|
|
80
|
+
_error: any,
|
|
81
|
+
resourceType: string,
|
|
82
|
+
resourceId?: string,
|
|
83
|
+
): void {
|
|
84
|
+
// CDP SDK doesn't support deleting accounts, so we just log
|
|
85
|
+
logger.log(
|
|
86
|
+
`${resourceType} ${resourceId || "unknown"} cannot be deleted from CDP (CDP limitation)`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if an error indicates the resource already exists
|
|
92
|
+
*
|
|
93
|
+
* @param error The error from CDP SDK
|
|
94
|
+
* @returns True if the error indicates a conflict/already exists
|
|
95
|
+
*/
|
|
96
|
+
export function isCdpConflictError(error: any): boolean {
|
|
97
|
+
return (
|
|
98
|
+
error?.message?.toLowerCase()?.includes("already exists") ||
|
|
99
|
+
error?.message?.toLowerCase()?.includes("duplicate") ||
|
|
100
|
+
error?.code === "ALREADY_EXISTS" ||
|
|
101
|
+
error?.code === "CONFLICT"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Verifies CDP authentication and provides helpful error messages
|
|
107
|
+
*
|
|
108
|
+
* @param client CDP client
|
|
109
|
+
*/
|
|
110
|
+
export async function verifyCdpAuth(client: CdpClient): Promise<void> {
|
|
111
|
+
try {
|
|
112
|
+
// Make a test request to check authentication
|
|
113
|
+
// List accounts is a good test as it requires valid credentials
|
|
114
|
+
await client.evm.listAccounts();
|
|
115
|
+
} catch (error: any) {
|
|
116
|
+
if (error.errorType === "unauthorized") {
|
|
117
|
+
logger.error(
|
|
118
|
+
"\n⚠️ Coinbase CDP authentication failed. Please check your credentials:",
|
|
119
|
+
);
|
|
120
|
+
logger.error("1. Ensure CDP_API_KEY_ID is set with your API Key ID");
|
|
121
|
+
logger.error(
|
|
122
|
+
"2. Ensure CDP_API_KEY_SECRET is set with your API Key Secret",
|
|
123
|
+
);
|
|
124
|
+
logger.error(
|
|
125
|
+
"3. Ensure CDP_WALLET_SECRET is set with your Wallet Secret",
|
|
126
|
+
);
|
|
127
|
+
logger.error(
|
|
128
|
+
"\nTo create API credentials, visit: https://portal.cdp.coinbase.com/",
|
|
129
|
+
);
|
|
130
|
+
logger.error(
|
|
131
|
+
"Required permissions: Account management and transaction signing\n",
|
|
132
|
+
);
|
|
133
|
+
throw new Error("CDP authentication failed");
|
|
134
|
+
}
|
|
135
|
+
if (error.errorType === "unauthorized") {
|
|
136
|
+
logger.error(
|
|
137
|
+
"\n⚠️ Insufficient permissions. Your API key needs account management permissions.",
|
|
138
|
+
);
|
|
139
|
+
logger.error(
|
|
140
|
+
"Make sure your API key has the required scopes for creating and managing accounts\n",
|
|
141
|
+
);
|
|
142
|
+
throw new Error("Insufficient CDP permissions");
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -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
|
+
);
|