compose-reference
Goldsky Compose Reference
Reference for the compose.yaml manifest, the full goldsky compose CLI surface, the TaskContext API, wallets, gas sponsorship, contract codegen, the dashboard, and pricing. For interactive build flows use /compose; for debugging use /compose-doctor.
Always validate the manifest before deploying.
goldsky compose devcatches schema errors fast.
Quick Reference
Most common lookups:
- Manifest top-level / task / trigger fields → compose.yaml Manifest
- CLI flags → CLI Commands
- TaskContext shape, IWallet, Collection → TaskContext API
- Smart wallet vs BYO EOA, gas sponsorship defaults → Wallets — Deep Dive
- Contract codegen workflow → Contract Codegen
--jsonoutput shapes → CLI JSON Schemas- Dashboard URL → Dashboard
compose.yaml Manifest
Top-level fields
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | RFC 1123: lowercase, letters/numbers/hyphens, letter-start |
api_version |
string | deploy-only | semver (e.g. 0.1.0) or stable / preview / canary |
tasks |
array | yes | Non-empty |
secrets |
string[] | no | Names only — values set via compose secret set |
env |
{ local?, cloud? } |
no | Each is Record<string, string>, flattened into context.env |
Task fields
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | /^([a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z0-9_]*$/ |
path |
string | yes | Relative path to the .ts task file |
triggers |
array | yes | One or more; at most one per type |
retry_config |
object | no | { max_attempts, initial_interval_ms, backoff_factor } — all three required when set |
Trigger types
cron
- type: cron
expression: "*/15 * * * *" # 5-field cron
http
- type: http
authentication: auth_token # or "none"
ip_whitelist: ["1.2.3.4", "10.0.0.0/8"] # optional, IPv4/IPv6/CIDR
onchain_event
- type: onchain_event
network: polygon_amoy # snake_case required
contract: "0xYourContractAddress" # 0x + 40 hex
events:
- "Transfer(address,address,uint256)" # viem signature strings, optional
dataset_version: "..." # optional
Full manifest example
name: my-app
api_version: stable
secrets:
- COINGECKO_API_KEY
- ORACLE_SIGNER_KEY
env:
cloud:
LOG_LEVEL: info
local:
LOG_LEVEL: debug
tasks:
- name: update_oracle
path: src/tasks/update-oracle.ts
retry_config:
max_attempts: 3
initial_interval_ms: 1000
backoff_factor: 2
triggers:
- type: cron
expression: "*/5 * * * *"
- name: manual_trigger
path: src/tasks/manual-trigger.ts
triggers:
- type: http
authentication: auth_token
CLI Commands
All commands accept -t/--token and --api-server; the -n/--name flag selects the app by name (falls back to -m/--manifest, then ./compose.yaml).
Lifecycle
| Command | Purpose | Key flags |
|---|---|---|
compose init [name] |
Scaffold new app | (interactive) |
compose dev |
Run locally (alias: start) |
--fork-chains, --cloud |
compose deploy |
Bundle + upload to cloud | -m, -t, -f, --sync-env |
compose status |
Show runtime status | -n, --json |
compose list |
List all apps | --json |
compose pause |
Pause | -n |
compose resume |
Resume | -n |
compose delete |
Delete (type-to-confirm; --force for CI) |
-n, --force, --delete-database |
compose logs |
View / tail logs | -f, --tail, --level, --search, --since, --max-lines, --json |
compose clean |
Wipe local .compose/stage.db |
-f |
compose update |
Re-download the compose binary | --preview |
compose callTask <name> <json> |
POST payload to a local task |
Secrets
| Command | Purpose |
|---|---|
compose secret set --name X --value Y [--env local|cloud] [--redeploy] |
Set a secret |
compose secret delete --name X [--env local|cloud] |
Delete |
compose secret list [--env local|cloud] |
List |
compose secret sync |
Upload all of .env to cloud |
Wallets
| Command | Purpose |
|---|---|
compose wallet create --name X [--env local|cloud] |
Create managed wallet; prints address |
compose wallet list |
Table: name, address, type (privy / private_key / tevm), created_at |
Codegen
compose codegen — parse all src/contracts/*.json ABIs, write .compose/generated/index.ts and .compose/types.d.ts. Runs automatically inside compose init, compose dev, and during deploy.
TaskContext API
main(context: TaskContext, params?: Record<string, unknown>): Promise<unknown> receives:
type TaskContext = {
env: Record<string, string>;
fetch: FetchFn;
callTask: <Args, T>(name: string, args: Args, retryConfig?: RetryConfig) => Promise<T>;
logEvent: (event: { code: string; message: string; data?: unknown }) => Promise<void>;
evm: {
chains: Record<string, Chain>; // re-exported from viem/chains
wallet: (config: WalletConfig) => Promise<IWallet>;
decodeEventLog: <T>(abi: AbiItem[], log: EventLog) => Promise<T>;
contracts: Record<string, ContractClass>; // populated by codegen
};
collection: <T>(name: string, indexes?: string[]) => Promise<Collection<T>>;
};
No logger or secrets namespace. Secrets flatten into context.env. Use console.log for free-form logging, logEvent for structured events.
fetch (overloads)
interface FetchFn {
<T>(url: string, retryConfig?: RetryConfig): Promise<T | undefined>;
<T>(url: string, body: unknown, retryConfig?: RetryConfig): Promise<T | undefined>;
}
bodyis serialized as JSON and sent as POST. Omitbodyfor GET.- Response is JSON-parsed; returns
undefinedwhen the body isn't JSON. - Not
window.fetch— use this, not nativefetch.
callTask
callTask<Args, T>(name: string, args: Args, retryConfig?: RetryConfig): Promise<T>
Tis whatever the callee returns. Avoid-returning task resolves toundefined.- Use for task-to-task invocation (parent/child patterns).
RetryConfig
type RetryConfig = {
max_attempts: number; // ≥0
initial_interval_ms: number; // >0
backoff_factor: number; // >0
};
No defaults — supply all three when you pass a retryConfig. Without it, the task runs once and any thrown error surfaces to the run record.
EventLog (for decodeEventLog and onchain_event triggers)
type EventLog = {
address: Address; // "0x…"
topics: Hex[]; // indexed topics
data: Hex; // non-indexed data
blockNumber?: bigint;
transactionHash?: Hex;
logIndex?: number;
};
For onchain_event-triggered tasks, params contains { log: EventLog } plus chain-specific metadata. decodeEventLog(abi, params.log) returns the decoded struct.
IWallet
interface IWallet {
readonly name: string;
readonly address: Address;
writeContract(
chain: Chain,
address: Address,
signatureOrAbi: string | AbiItem,
args?: unknown[],
options?: WriteOptions,
): Promise<TxResult>;
readContract(
chain: Chain,
address: Address,
signatureOrAbi: string | AbiItem,
args?: unknown[],
): Promise<unknown>;
sendTransaction(
chain: Chain,
to: Address,
value: bigint,
data?: Hex,
options?: WriteOptions,
): Promise<TxResult>;
simulate(
chain: Chain,
address: Address,
signatureOrAbi: string | AbiItem,
args?: unknown[],
): Promise<SimulateResult>;
getBalance(chain: Chain): Promise<bigint>;
}
type WriteOptions = {
confirmations?: number;
onReorg?: { action: { type: "replay" | "skip" }; depth: number };
retryConfig?: RetryConfig;
gas?: bigint;
gasPrice?: bigint;
};
type TxResult = {
hash: Hex;
userOpHash?: Hex; // only for sponsored transactions
chainId: number;
blockNumber?: bigint; // present after mining
};
type SimulateResult = {
success: boolean;
result?: unknown;
error?: string;
};
Collection
interface Collection<T> {
insertOne(doc: T): Promise<void>;
findOne(filter: Filter<T>): Promise<T | null>;
findMany(filter: Filter<T>, options?: { limit?: number; skip?: number }): Promise<T[]>;
getById(id: string): Promise<T | null>;
setById(id: string, doc: T, opts?: { upsert?: boolean }): Promise<void>; // upsert defaults true
deleteById(id: string): Promise<void>;
drop(): Promise<void>;
}
Filter operators: $gt, $gte, $lt, $lte, $in, $ne, $nin, $exists. Equality: { field: value }.
CLI JSON Schemas
For agents parsing --json output:
compose status -n <app> --json
{
"name": "my-app",
"status": "RUNNING",
"created_at": 1771630350411,
"updated_at": 1774473580871
}
status is one of RUNNING | PAUSED | ERROR | STARTING | STOPPED | PROVISIONING. Timestamps are ms epoch.
compose list --json
[
{ "name": "my-app", "status": "RUNNING", "created_at": 1771630350411, "updated_at": 1774473580871 }
]
compose logs --json
NDJSON (one object per line):
{"timestamp":"2026-04-20T10:00:00Z","level":"info","message":"...","dashboard_url":"https://app.goldsky.com/<project_id>/dashboard/compose/<app>/runs/<run_id>"}
compose secret list -n <app> --json
[{ "name": "MY_SECRET" }]
Values are never returned.
compose wallet list --json
[{ "name": "updater", "address": "0x...", "type": "privy", "created_at": 1771630350411 }]
type is one of privy (smart wallet), private_key (BYO EOA), tevm (local forked).
Wallets — Deep Dive
Smart wallet (managed, Privy-backed)
const w = await evm.wallet({ name: "my-oracle" }); // sponsorGas defaults TRUE
Created cloud-side by Privy. Address is persisted. Gas-sponsored by default. Cannot be used in plain local dev — throws "You cannot use a smart wallet in local dev unless you use chain forking." Use compose dev --fork-chains or switch to a BYO EOA for local iteration.
BYO EOA (private key)
const w = await evm.wallet({
privateKey: env.MY_KEY,
name: "my-pk-wallet", // optional; defaults to the derived address
sponsorGas: true, // DEFAULTS TO FALSE — opt in explicitly
});
Works in both cloud and local. When sponsorGas: true, the wallet configures EIP-7702 delegation per chain on first use, then submits UserOperations through a sponsored bundler.
Gas Sponsorship
Bundler fallback order: Alchemy → Pimlico → Gelato. Override via BUNDLER_PROVIDER=<alchemy|pimlico|gelato> env var.
Supported chains
See the Goldsky docs chains page for the current list (don't hardcode — this changes). Highlights include Ethereum, Base, Arbitrum, Optimism, Polygon, Unichain, Monad (mainnet + testnet), MegaETH testnet, Lisk, Linea, Scroll, Avalanche, Blast, BNB, Celo, Zora, Sonic, Worldchain, plus major Sepolia testnets and Polygon Amoy.
Error on unsupported chain
No bundler provider available for chain <id>. Providers: alchemy: chain not supported; pimlico: missing keys (PIMLICO_API_KEY); gelato: …
Either use a supported chain or set sponsorGas: false and fund the EOA manually.
Caveats
onReorgis not supported for gas-sponsored transactions (warning logged, not fatal).- Passing a custom
nonceto a sponsoredsendTransactionis ignored (ERC-4337 smart wallets use a different nonce structure).
Contract Codegen (full example)
Input
Drop ABI JSON files into src/contracts/:
src/contracts/
├── ERC20.json
└── PriceFeed.json
Accepted ABI shapes: bare ABI array ([{ "type": "function", ... }, ...]), or wrapped object ({ "abi": [...] }), or a Foundry/Hardhat artifact (the generator extracts the abi field). The filename (without extension) becomes the generated class name.
Generate
goldsky compose codegen
(Also runs automatically during init, dev, and deploy.)
Output
.compose/generated/index.ts exports a class per ABI. .compose/types.d.ts declares ambient types under the compose path alias (referenced in the scaffolded tsconfig.json).
Consume in a task
import type { TaskContext } from "compose";
export async function main({ evm, env }: TaskContext) {
const PriceFeed = evm.contracts.PriceFeed;
const feed = new PriceFeed(evm.chains.ethereum, env.FEED_ADDRESS);
const price = await feed.latestAnswer();
return { price: price.toString() };
}
Classes are exposed under context.evm.contracts.<Name>. Codegen names ending in Class (e.g. ERC20Class) are exposed as ERC20 at runtime.
Supported Chains
context.evm.chains is re-exported from viem/chains. Any chain viem knows, you can address as evm.chains.<name> (e.g. evm.chains.polygonAmoy, evm.chains.monadTestnet, evm.chains.baseSepolia). For gas sponsorship specifically, see the Gas Sponsorship section — sponsorship is a subset of viem's chain list.
Dashboard
URL pattern:
https://app.goldsky.com/<project_id>/dashboard/compose/<app-name>
https://app.goldsky.com/<project_id>/dashboard/compose/<app-name>/runs/<run_id>
The dashboard shows status, secrets, logs, and per-run traces. Log lines include a dashboard_url attribute in their metadata so agents can link the user directly to the relevant run.
Pricing
Compose is in Beta. Pricing is enterprise-only — schedule a call with Goldsky to discuss your use case. Source: https://goldsky.com/pricing (Compose section).
Internally, usage is tracked across three metered dimensions:
- Function calls (
compose_function_calls) — number of task invocations. - Worker hours (
compose_worker_hours) — runtime consumed. - Gas spend (
compose_gas_spend) — gas paid for sponsored transactions.
These metrics drive enterprise-tier billing; per-unit prices are set per contract, not published.
Related
/compose— Build a new app or explain what Compose is./compose-doctor— Diagnose and fix broken apps./auth-setup—goldsky loginhelp./secrets— General secret management.
More from goldsky-io/goldsky-agent
turbo-builder
Build and deploy new Goldsky Turbo pipelines from scratch. Triggers on: 'build a pipeline', 'index X on Y chain', 'set up a pipeline', 'track transfers to postgres', or any request describing data to move from a chain/contract to a destination (postgres, clickhouse, kafka, s3, webhook). Covers the full workflow: requirements → dataset selection → YAML generation → validation → deploy. Not for debugging (use /turbo-doctor) or syntax lookups (use /turbo-pipelines).
39turbo-pipelines
Turbo pipeline YAML reference and architecture guide. Covers: YAML field syntax (start_at, from, version, primary_key), source/transform/sink configuration, validation errors, resource sizing (xs–xxl), architecture decisions (dataset vs kafka, streaming vs job, fan-out vs fan-in, sink selection, pipeline splitting). Triggers on: 'what does field X do', 'what fields does a postgres sink need', 'what resource size', 'should I use kafka or dataset', 'how to structure my pipeline'. For writing transforms, use /turbo-transforms. For end-to-end building, use /turbo-builder.
39secrets
Use this skill when a user wants to store, manage, or work with Goldsky secrets — the named credential objects used by pipeline sinks. This includes: creating a new secret from a connection string or credentials, listing or inspecting existing secrets, updating or rotating credentials after a password change, and deleting secrets that are no longer needed. Trigger for any query where the user mentions 'goldsky secret', wants to securely store database credentials for a pipeline, or is working with sink authentication for PostgreSQL, Neon, Supabase, ClickHouse, Kafka, S3, Elasticsearch, DynamoDB, SQS, OpenSearch, or webhooks.
34datasets
Use this skill when the user needs to look up or verify Goldsky blockchain dataset names, chain prefixes, dataset types, or versions. Triggers on questions like 'what\\'s the dataset name for X?', 'what prefix does Goldsky use for chain Y?', 'what version should I use for Z?', or 'what datasets are available for Solana/Stellar/Arbitrum/etc?'. Also use for chain-specific dataset questions (e.g., polygon vs matic prefix, stellarnet balance datasets, solana token transfer dataset names). Do NOT trigger for questions about CLI commands, pipeline setup, or general Goldsky architecture unless the core question is about finding the right dataset name or chain prefix.
34turbo-doctor
Diagnose and fix broken Goldsky Turbo pipelines interactively. Triggers on: pipeline in error state, stuck starting, connection refused, not getting data, duplicate rows, missing fields, slow backfill, or any named pipeline misbehaving. Runs logs/status commands, identifies root cause, and offers fixes. For CLI syntax or error pattern lookup without an active problem, use /turbo-operations instead.
34turbo-transforms
Write SQL, TypeScript, and dynamic table transforms for Turbo pipelines. Covers: decoding EVM logs with _gs_log_decode, filtering/casting blockchain data, UNION ALL for combining events, TypeScript/WASM transforms (invoke function), dynamic lookup tables (dynamic_table_check), transform chaining, and Solana decoding. Triggers on: 'decode Transfer events', 'write a SQL transform', 'filter by contract', 'TypeScript transform', 'dynamic table', 'UNION ALL'. For pipeline YAML structure, use /turbo-pipelines. For end-to-end building, use /turbo-builder.
33