# Chainlink CRE Connect Source: https://docs.chain.link/crec Last Updated: 2026-08-31 **Chainlink CRE Connect (CREC)** is a managed API and Go SDK for applications that need verified on-chain observations and gas-less on-chain execution. Your application calls one API; Chainlink runs the oracle network and on-chain execution infrastructure behind it. CRE Connect uses the Chainlink Runtime Environment and Chainlink Decentralized Oracle Networks (DONs), which are groups of independent Chainlink nodes that observe chains, produce signed reports, and submit transactions. CRE Connect packages those capabilities into one developer interface. Use CRE Connect when your application needs to: - **Watch on-chain events**: subscribe to contract logs and verify that a Chainlink DON observed them. - **Execute gas-less operations**: submit an Operation, which is an EIP-712-signed batch of EVM transactions that a Smart Account, your on-chain execution account, runs atomically. - **Run one-shot chain queries**: request a read-only EVM call and receive a DON-backed result. You sign operations with keys you already use: local ECDSA, AWS KMS, HashiCorp Vault, Fireblocks, Privy, or a custom signer. CRE Connect delivers verifiable events and results, and the SDK gives you helpers to verify them locally before acting on them. ## What you can build | Capability | What it gives you | Where to start | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | **Verifiable events** | DON-signed on-chain events delivered through one channel event stream. | [Quickstart: Watch Events](/crec/getting-started/quickstart-watch-events) | | **Gas-less operations** | One EIP-712-signed payload becomes one or more EVM transactions executed atomically by your Smart Account. | [Quickstart: Send an Operation](/crec/getting-started/quickstart-send-operation) | | **Chain queries** | One-shot, DON-backed reads for EVM `eth_call` requests against a selected block. | [Execute a Chain Query](/crec/guides/queries/execute-a-query) | | **Protocol extensions** | Pre-packaged typed builders and watcher provisioning for supported partner protocols. The first available extension is [DTA](/crec/extensions/dta). | [Extensions](/crec/extensions) | ## Core model Most integrations use the same small set of resources: | Resource | What it does | Start here | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | **Channel** | Scopes watchers, wallets, operations, queries, and the ordered event stream for one application or environment. | [Channels](/crec/concepts/channels) | | **Watcher** | Monitors a contract on a supported chain and emits `watcher.event` records when matching logs appear. | [Watchers](/crec/concepts/watchers) | | **Event** | Carries watcher data, lifecycle updates, or query results through one poll/search API. Verifiable events include OCR proofs. | [Verifiable Events](/crec/concepts/verifiable-events) | | **Wallet / Smart Account** | Represents the on-chain account that executes operations and the signer set allowed to authorize them. | [Smart Accounts](/crec/concepts/smart-accounts) | | **Operation** | Packages one or more EVM transactions into an atomic, EIP-712-authorized write. | [Operations](/crec/concepts/operations) | | **Query** | Requests a one-shot, read-only EVM call and returns a signed result. | [Chain Queries](/crec/concepts/queries) | ## Pick your path Choose the entry point that matches what you're trying to do right now. ### I'm new: get me to a working integration Read these in order. 1. [Prerequisites](/crec/getting-started/prerequisites): request access, install Go, gather your API key, pick a signer. 2. [SDK Installation](/crec/getting-started/sdk-installation): `go get` the core SDK and optionally the DTA extension. 3. [Authentication](/crec/getting-started/authentication): initialize the client and run a `ListNetworks` smoke test. 4. [Quickstart: Watch On-Chain Events](/crec/getting-started/quickstart-watch-events): your first verifiable event. 5. [Quickstart: Send Your First Operation](/crec/getting-started/quickstart-send-operation): your first gas-less write. ### I want to understand the model Start with the [Architecture](/crec/concepts/architecture) overview, then follow the path that matches your integration: - **For event monitoring**: [Channels](/crec/concepts/channels), [Watchers](/crec/concepts/watchers), [Verifiable Events](/crec/concepts/verifiable-events), and [Confidence Levels](/crec/concepts/confidence-levels). - **For on-chain execution**: [Smart Accounts](/crec/concepts/smart-accounts), [Operations](/crec/concepts/operations), [EIP-712 Signing](/crec/concepts/eip712-signing), [Draft Operations](/crec/concepts/drafts), and [Multi-Event Finality](/crec/concepts/multi-event-finality). - **For on-chain reads**: [Chain Queries](/crec/concepts/queries). ### I'm integrating now: show me the code Jump straight to the relevant guide: - **Channels**: [Create and manage channels](/crec/guides/channels/manage-channels) - **Watchers**: [Predefined service](/crec/guides/watchers/create-with-service) · [Custom ABI](/crec/guides/watchers/create-with-abi) · [Lifecycle](/crec/guides/watchers/manage-lifecycle) - **Events**: [Poll and search](/crec/guides/events/poll-and-search) · [Verify signatures](/crec/guides/events/verify-signatures) · [Decode data](/crec/guides/events/decode-data) - **Operations**: [Build and sign](/crec/guides/operations/build-and-sign) · [Draft operations](/crec/guides/operations/drafts) · [Submit and track](/crec/guides/operations/submit-and-track) · [Batch](/crec/guides/operations/batch-transactions) · [Signing transparency](/crec/guides/operations/signing-transparency) - **Queries**: [Execute a query](/crec/guides/queries/execute-a-query) - **Wallets**: [Create and manage](/crec/guides/wallets/create-and-manage) · [Manage signers](/crec/guides/wallets/manage-signers) - **Signers**: [Local](/crec/guides/signers/local) · [AWS KMS](/crec/guides/signers/aws-kms) · [HashiCorp Vault](/crec/guides/signers/hashicorp-vault) · [Fireblocks](/crec/guides/signers/fireblocks) · [Privy](/crec/guides/signers/privy) · [Custom](/crec/guides/signers/custom) ### I just need to look up a fact - [REST API Reference](/crec/reference/rest-api): narrative companion to the interactive Swagger explorer. - [Go SDK Reference](/crec/reference/go-sdk): every public sub-package with links to `pkg.go.dev`. - [SDK Configuration Options](/crec/reference/sdk-configuration): every functional option on `crec.NewClient`. - [Lifecycles](/crec/reference/lifecycles): every status enum and state transition. - [Event Types and Payloads](/crec/reference/event-payloads): the five `Event_Payload` shapes. - [Error Handling](/crec/reference/error-handling): sentinel errors, REST envelope, retry policy. - [Service Limits](/crec/reference/service-limits): hard limits and quotas. - [Supported Networks](/crec/supported-networks): discoverable at runtime via `ListNetworks`. ## Where to go next? - [Private Beta](/crec/private-beta): access, scope, and current limitations. - [Getting Started](/crec/getting-started/prerequisites): follow the recommended onboarding sequence. - [Architecture](/crec/concepts/architecture): understand how the SDK, REST API, DON workflows, and Smart Accounts fit together. - [REST API Reference](/crec/reference/rest-api) · [Go SDK Reference](/crec/reference/go-sdk): look up endpoint and SDK details. - [Release Notes](/crec/release-notes): see current versions and changes. --- # Private Beta Source: https://docs.chain.link/crec/private-beta Last Updated: 2026-09-02 CRE Connect is in **private beta**: the product is complete enough to build real integrations, but access is permissioned and some capabilities are still evolving. This page explains what that means in practice, so you can decide what to build today and what to plan around. ## How access works During the beta, CRE Connect is not self-serve. Your organization must be provisioned by Chainlink Labs before you can create API keys or call the API: 1. [Contact Chainlink](https://chain.link/contact) to request access or schedule a demo. 2. Share your **Organization ID** with your Chainlink Labs contact so they can enable CRE Connect for your organization. 3. Once enabled, generate an API key from the **Organization → APIs** tab on [app.chain.link](https://app.chain.link). See [Prerequisites](/crec/getting-started/prerequisites) for the detailed onboarding flow, and the [Overview](/crec) for what you can build with CRE Connect. ## What the beta includes The core product surface is available and supported: - **Verifiable on-chain events**: subscribe to contract events on [supported networks](/crec/supported-networks); every event is signed by the Chainlink DON and verifiable end-to-end. See [Verifiable Events](/crec/concepts/verifiable-events). - **Gas-less, account-abstracted operations**: sign one EIP-712 payload; CRE Connect executes one or more EVM transactions atomically through your Smart Account. See [Operations & Transactions](/crec/concepts/operations). - **Chain queries**: one-shot, DON-backed, verifiable EVM reads. See [Chain Queries](/crec/concepts/queries). - **Signer integrations**: local ECDSA/RSA, AWS KMS, HashiCorp Vault, Fireblocks, Privy, and a documented [custom signer interface](/crec/guides/signers/custom). - **Extensions**: the [DTA (Digital Transfer Agent)](/crec/extensions/dta) extension is available at launch. ## Current limitations The following limitations apply today: ### Signer sets are fixed at wallet creation A wallet's signer set cannot be modified after creation. To rotate signers today, archive the old wallet and provision a new one with the updated signer lists. See [Manage Wallet Signers](/crec/guides/wallets/manage-signers). ### Per-environment quotas are negotiated The number of watchers per channel, rate limits, and other per-environment quotas are soft limits negotiated with the Chainlink team rather than fixed published numbers. See [Service Limits](/crec/reference/service-limits) for the hard limits that are enforced server-side. ## Support and feedback During the beta, support flows through your Chainlink Labs contact: - **Issues and bugs**: include the operation or event ID where possible. - **Feature requests**: the beta is the time to shape the product: share your use cases and gaps. - **Quota changes**: rate limits and per-environment quotas can be adjusted on request. ## See also - [Prerequisites](/crec/getting-started/prerequisites): the detailed onboarding flow. - [Release Notes](/crec/release-notes): version history for the SDK, REST API, and extensions. - [Service Limits](/crec/reference/service-limits): hard limits enforced by the API. --- # Supported Networks Source: https://docs.chain.link/crec/supported-networks Last Updated: 2026-08-31 ## Mainnet networks ## Testnet networks ## Discovering networks at runtime ### Go SDK ```go import ( "context" "fmt" "os" crec "github.com/smartcontractkit/crec-sdk" ) client, err := crec.NewClient( os.Getenv("CREC_BASE_URL"), os.Getenv("CREC_API_KEY"), ) if err != nil { return err } networks, hasMore, err := client.ListNetworks(ctx) if err != nil { return err } _ = hasMore for _, n := range networks { netType := "unknown" if n.Type != nil { netType = string(*n.Type) // "mainnet" | "testnet" } defaultCL := "—" if n.DefaultConfidenceLevel != nil { defaultCL = string(*n.DefaultConfidenceLevel) // e.g. "finalized" } fmt.Printf("%-25s family=%-6s type=%-7s chain_id=%-10s selector=%s default_cl=%s\n", n.Name, n.ChainFamily, netType, n.ChainId, n.ChainSelector, defaultCL) } ``` ### REST ```bash curl https://cre-connect.api.chain.link/v1/networks \ -H "Authorization: Apikey $CREC_API_KEY" \ ``` Response: ```json { "data": [ { "id": "320efa6d-af83-4dae-89f1-5a124404a54d", "name": "Ethereum Sepolia", "chain_selector": "16015286601757825753", "chain_id": "11155111", "chain_family": "evm", "type": "testnet", "default_confidence_level": "finalized", "created_at": 1775503594, "updated_at": 1775503594 } ], "has_more": false } ``` ## Network record fields Each `Network` record exposes the fields you need to wire into other CRE Connect resources: | Field | Used by | Notes | | --------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `id` | Diagnostic / display | Internal UUID for the network row. | | `name` | UI / logs | Human-readable name (e.g. "Ethereum Mainnet"). | | `chain_selector` | `wallets`, `watchers`, `operations` | The CCIP chain selector: pass to every chain-aware request. | | `chain_id` | EIP-712 domain (`chainId`) | Pass into `eip712.Domain` for typed-data signing. | | `chain_family` | Application logic | Currently `"evm"`; future families may include `"solana"`. | | `type` | Application logic | `"mainnet"` or `"testnet"`. Optional; absent on environments that haven't classified the network. | | `default_confidence_level` | Watcher / event defaults | The confidence level the backend uses when none is supplied (e.g. `"finalized"`). Optional. See [Confidence Levels](/crec/concepts/confidence-levels). | | `created_at` / `updated_at` | Diagnostic | Unix seconds. | ## Where chain selectors are required | Resource | Field | Notes | | ------------------------------------------------------- | ---------------------------------------------- | --------------------------------------------------- | | `CreateWallet` | `chain_selector` | The chain on which the Smart Account is deployed. | | `Watchers.CreateWithService` / `Watchers.CreateWithABI` | `ChainSelector` | The chain whose logs the watcher subscribes to. | | `Operation` (via SDK) | `ChainSelector` argument to `ExecuteOperation` | The chain on which the operation will be broadcast. | | `events.SearchEvents` | `chain_selector` filter (optional) | Narrow historical search to one chain. | ## Confidence levels Network-level **confidence levels** (`latest`, `safe`, `finalized`) are configured per network and surfaced via `default_confidence_level`. See [Confidence Levels](/crec/concepts/confidence-levels) for the semantic and latency trade-offs. Operation status events can progress through [Multi-Event Finality](/crec/concepts/multi-event-finality) as the underlying block matures. ## Related - [Authentication](/crec/getting-started/authentication): uses `ListNetworks` as the canonical smoke test. - [REST API Reference](/crec/reference/rest-api): `/networks` endpoint contract. - [Confidence Levels](/crec/concepts/confidence-levels): pick the right finality for your workload. - [Multi-Event Finality](/crec/concepts/multi-event-finality): understand progressive operation confirmations. --- # Release Notes Source: https://docs.chain.link/crec/release-notes Last Updated: 2026-09-01 ## 2026-09-01 — SDK v0.8.0 — DON Configuration, Structured Conflict Errors **Versions** - Go SDK: `github.com/smartcontractkit/crec-sdk` **v0.8.0** - REST API: **0.8.0** (`https://cre-connect.api.chain.link/v1`) **What's new** - **DON configuration**: configure event verification for your organisation's DON as one atomic unit (tenant ID, signature threshold, signer set) with the new `WithDONConfig` option. See [SDK Configuration Options](/crec/reference/sdk-configuration). - **Structured conflict errors**: HTTP `409` responses now carry machine-readable codes, mapped by the SDK to sentinel errors that work with `errors.Is`. See [Error Handling](/crec/reference/error-handling). - **More precise verification failures**: verification now distinguishes a workflow-owner mismatch from an insufficient signature count, instead of returning a generic error. See [Event Verification](/crec/concepts/event-verification). - **Optional wallet configuration**: the `configuration` field on wallet creation is now optional. - **DON signer rotation completed**: the default signer set now counts 10 production signers. ## 2026-08-06 — SDK v0.7.14 — Draft Operations, Chain Queries, Multi-Event Finality **Versions** - Go SDK: `github.com/smartcontractkit/crec-sdk` **v0.7.14** - REST API: **0.7.0** (`https://cre-connect.api.chain.link/v1`) **What's new since initial release** - **Draft operations**: create unsigned operations and finalize them later with an external signer. Enables deferred-signing workflows: MPC (Fireblocks, Privy, AWS KMS with approval), human approval gates, preview-before-sign, and cancellation before execution. See [Draft Operations](/crec/concepts/drafts). - **Chain queries**: submit asynchronous, DON-backed, verifiable EVM `eth_call` reads and receive OCR-signed results with resolved block metadata. See [Chain Queries](/crec/concepts/queries). - **Multi-event finality**: operations now report progressive on-chain confirmations (`confirmed_latest` → `confirmed_safe` → `confirmed`) as the block matures through finality stages. See [Multi-Event Finality](/crec/concepts/multi-event-finality). - **RSA local signer**: in-memory RSA signing for development and testing, alongside the existing ECDSA local signer. - **Structured error codes**: the REST API now returns machine-readable `ApplicationError` with `type` and `code` fields (e.g. `OPERATION_NOT_FINALIZABLE`, `IDEMPOTENCY_KEY_MISMATCH`). ## 2026-05-01 — Initial release Chainlink CRE Connect is now available as a **private beta**. **Versions** - Go SDK: `github.com/smartcontractkit/crec-sdk` **v0.6.7** - DTA extension: `github.com/smartcontractkit/crec-sdk-ext-dta/v2` **v0.0.13** - REST API: **0.4.0** (`https://cre-connect.api.chain.link/v1`) **Capabilities** - **Verifiable on-chain events**: subscribe to contract events on supported networks; every event is signed by the Chainlink DON and verifiable end-to-end via `client.Events.Verify` (and `client.Events.VerifyOperationStatus` for operation lifecycle events). - **Gas-less, account-abstracted operations**: sign one EIP-712 payload; CRE Connect executes one or more EVM transactions atomically through your Smart Account, with no gas, nonce, or relayer management on your side. - **Extensions**: typed Go modules layered on the core SDK. Available at launch: [DTA](/crec/extensions/dta). **Signer integrations** Local ECDSA, AWS KMS, HashiCorp Vault, Fireblocks, Privy, and a documented [custom signer interface](/crec/guides/signers/custom). **Supported networks** See [Supported Networks](/crec/supported-networks). --- # Prerequisites Source: https://docs.chain.link/crec/getting-started/prerequisites Last Updated: 2026-08-31 CRE Connect is a permissioned service. Before you can use the SDK or call the API, your organization must exist on the Chainlink Platform and be provisioned by Chainlink Labs for CRE Connect. Steps 1–3 cover the account and API key; steps 4–6 cover the local toolchain and integration choices. ## 1. Create your organization Go to app.chain.link and create an account or sign in. Once signed in, click on **your organization's name** in the bottom-left corner of the sidebar to open its **Organization** page. Your **Organization ID** is displayed in the page header: copy it. It is a short prefixed string, for example `org_example00000000000` (not a UUID). ## 2. Share your Organization ID Share your Organization ID with your Chainlink Labs contact so they can enable CRE Connect for your organization. **You cannot create API keys or use CRE Connect until this step is complete.** ## 3. Create an API key Once your account has been provisioned, create an API key for authentication: 1. Sign in to the Chainlink Platform, click on **your organization's name** in the bottom-left corner of the sidebar to open its **Organization** page, then select the **"APIs"** tab. 2. Click **"+ Organization API"**. 3. Enter a name for the key and select an expiration period (1 day, 1 month, or 1 year). 4. Click **"Generate"**. The API key is displayed once: copy it immediately. You will use this key in the `Authorization` header for all CRE Connect REST calls and as the `apiKey` argument to the Go SDK constructor: ```bash curl https://cre-connect.api.chain.link/v1/ \ -H "Authorization: Apikey " ``` ```go client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", "", crec.WithOrgID(""), ) ``` For local development, store the key, the base URL, **and your Organization ID** in environment variables so they never land in source control: ```bash export CREC_BASE_URL="https://cre-connect.api.chain.link/v1" export CREC_API_KEY="" export CREC_ORG_ID="" # the Organization ID from step 1, e.g. org_example00000000000 ``` ## 4. Go toolchain The CRE Connect SDK targets **Go 1.25.3 or higher**, and the DTA extension targets **Go 1.25.5 or higher**. Use **Go 1.25.5+** to cover both. Check your version: ```bash go version ``` If your installed Go is older, you have two options: - **Upgrade your toolchain**: follow the official install instructions at go.dev/dl. - **Let Go fetch the right toolchain on demand**: `GOTOOLCHAIN=auto` is the default since Go 1.21, so as long as your `go.mod` declares `go 1.25.5` (or higher) and a `toolchain` directive, Go will download the matching toolchain transparently the first time you run `go build`, `go run`, or `go mod tidy`. For a fresh project: ```bash go mod init go mod edit -go=1.25.5 -toolchain=go1.25.9 ``` ## 5. A supported network and chain selector Watchers and wallets are pinned to a network. CRE Connect identifies networks by their **chain selector** (a `uint64` represented as a string in API payloads, for example `5009297550715157269` for Ethereum Mainnet). You can list supported networks at runtime once authenticated (see [Authentication](/crec/getting-started/authentication)) or consult the static [Supported Networks](/crec/supported-networks) reference. The quickstarts in this section target Sepolia (`16015286601757825753`). ## 6. A signer Operations are signed off-chain before being submitted to CRE Connect. You will need a key (or a key-management service) to produce ECDSA signatures. The fastest option for the quickstarts is the **local ECDSA signer** in `crec-sdk/transact/signer/local`, which loads a `*ecdsa.PrivateKey` directly into memory: ```go import ( "github.com/ethereum/go-ethereum/crypto" "github.com/smartcontractkit/crec-sdk/transact/signer/local" ) pk, _ := crypto.HexToECDSA("ab12...") signer := local.NewSigner(pk) ``` The SDK ships first-class adapters for managed signers: - AWS KMS: `crec-sdk/transact/signer/kms` - HashiCorp Vault: `crec-sdk/transact/signer/vault` - Fireblocks: `crec-sdk/transact/signer/fireblocks` - Privy: `crec-sdk/transact/signer/privy` You can also implement your own by satisfying the `signer.Signer` interface; see [Implement a Custom Signer](/crec/guides/signers/custom). ## Checklist - Organization created on app.chain.link; Organization ID copied. - Organization ID shared with Chainlink Labs and CRE Connect enablement confirmed. - API key generated in your org's **Organization → APIs** tab, stored securely, and exposed via env var. - `CREC_BASE_URL`, `CREC_API_KEY`, and `CREC_ORG_ID` exported in your shell. - `go version` reports **1.25.5** or higher (or your `go.mod` has `go 1.25.5` + a `toolchain` directive). - You have picked a supported network and know its chain selector. - You have a signer (local key for the quickstarts; managed signer for production). When all of these are checked, continue to [SDK Installation](/crec/getting-started/sdk-installation). ## Related - [SDK Installation](/crec/getting-started/sdk-installation): install the modules you'll use in the next step. - [Authentication](/crec/getting-started/authentication): initialize the client with the API key you just created. - [Supported Networks](/crec/supported-networks): pick a chain and grab its chain selector. --- # SDK Installation Source: https://docs.chain.link/crec/getting-started/sdk-installation Last Updated: 2026-08-31 The CRE Connect SDK is distributed as a Go module. There is one core module and one optional extension per supported protocol. ## Install the core SDK ```bash go get github.com/smartcontractkit/crec-sdk ``` This pulls the unified client and every sub-client (`channels`, `events`, `transact`, `wallets`, `watchers`) along with their dependencies. ## Install protocol extensions (optional) Extensions sit on top of the core SDK and supply typed Operation builders, decoded event structs, and pre-packaged watcher provisioning for a given protocol. Install only the extensions you need. ### DTA (Digital Transfer Agent) ```bash go get github.com/smartcontractkit/crec-sdk-ext-dta/v2 ``` Use the **v2** module path explicitly. v1 is **not** documented or supported by the current `dta.v2` service. See the [DTA Overview](/crec/extensions/dta) for the full surface. ## Canonical import paths These are the imports you will reach for most often. Each Go file should import only what it needs: there is no umbrella import that pulls everything in. ### Unified client ```go import "github.com/smartcontractkit/crec-sdk" ``` This package exposes `crec.NewClient`, `crec.NewAPIClient`, all `crec.Option` constructors, and the sentinel errors (`ErrBaseURLRequired`, `ErrAPIKeyRequired`, `ErrInvalidEventVerificationConfig`, `ErrListNetworks`). ### Sub-clients ```go import ( "github.com/smartcontractkit/crec-sdk/channels" "github.com/smartcontractkit/crec-sdk/events" "github.com/smartcontractkit/crec-sdk/transact" "github.com/smartcontractkit/crec-sdk/wallets" "github.com/smartcontractkit/crec-sdk/watchers" ) ``` ### Operation types and signing ```go import ( "github.com/smartcontractkit/crec-sdk/transact/types" "github.com/smartcontractkit/crec-sdk/transact/eip712" "github.com/smartcontractkit/crec-sdk/transact/signer" ) ``` ### Signer adapters Pick whichever matches your key store: ```go import "github.com/smartcontractkit/crec-sdk/transact/signer/local" import "github.com/smartcontractkit/crec-sdk/transact/signer/kms" import "github.com/smartcontractkit/crec-sdk/transact/signer/vault" import "github.com/smartcontractkit/crec-sdk/transact/signer/fireblocks" import "github.com/smartcontractkit/crec-sdk/transact/signer/privy" ``` ### DTA extension ```go import ( dtaops "github.com/smartcontractkit/crec-sdk-ext-dta/v2/operations" dtaevents "github.com/smartcontractkit/crec-sdk-ext-dta/v2/events" dtawatcher "github.com/smartcontractkit/crec-sdk-ext-dta/v2/watcher/bundle" ) ``` ## Use the unified client or per-package clients Two construction patterns are supported. ### Unified (recommended) ```go client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", apiKey, crec.WithOrgID(orgID), // required for Events.Verify and Events.VerifyOperationStatus ) // client.Channels, client.Events, client.Transact, client.Wallets, client.Watchers ``` This wires every sub-client with the same authenticated transport, logger, and event-verification configuration. Pass `crec.WithOrgID(...)` so the events client can derive your tenant's workflow owner address; see [Prerequisites](/crec/getting-started/prerequisites) for where to find your Organization ID. ### Per-package (for small footprints) If you only need one or two sub-clients (for example a service that only polls events), share the underlying API client to avoid redundant configuration. The events client takes the same `OrgID` (or `WorkflowOwner`) you would otherwise pass to `crec.NewClient`, so verification still works: ```go api, err := crec.NewAPIClient( "https://cre-connect.api.chain.link/v1", apiKey, ) if err != nil { return err } eventsClient, err := events.NewClient(&events.Options{ CRECClient: api, OrgID: orgID, // required for Verify / VerifyOperationStatus }) ``` Both patterns talk to the same backend. Pick whichever makes your dependency surface smaller. ## Verify the install Compile a one-line program to confirm modules resolve: ```go package main import ( "fmt" "github.com/smartcontractkit/crec-sdk" ) func main() { fmt.Printf("ErrAPIKeyRequired = %v\n", crec.ErrAPIKeyRequired) } ``` ```bash go run main.go # ErrAPIKeyRequired = API key is required ``` If that prints without errors, you are ready for [Authentication](/crec/getting-started/authentication). ## Related - [Authentication](/crec/getting-started/authentication): initialize the client you just installed. - [Go SDK Reference](/crec/reference/go-sdk): every package this section imports. - [Extensions](/crec/extensions): when to install the optional DTA module. --- # Authentication Source: https://docs.chain.link/crec/getting-started/authentication Last Updated: 2026-08-31 CRE Connect uses a single header, `Authorization: Apikey `, for every request. The SDK attaches that header automatically once you construct a client. ## Environment variables Throughout these docs, code samples and `curl` snippets read from three environment variables. Export them once and the rest of the examples will work as-is: ```bash export CREC_BASE_URL="https://cre-connect.api.chain.link/v1" export CREC_API_KEY="" export CREC_ORG_ID="" # e.g. org_example00000000000, see Prerequisites ``` ## Construct a client ```go package main import ( "log" "os" "github.com/smartcontractkit/crec-sdk" ) func main() { apiKey := os.Getenv("CREC_API_KEY") if apiKey == "" { log.Fatal("CREC_API_KEY must be set") } orgID := os.Getenv("CREC_ORG_ID") if orgID == "" { log.Fatal("CREC_ORG_ID must be set") } client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", apiKey, crec.WithOrgID(orgID), ) if err != nil { log.Fatalf("failed to construct CREC client: %v", err) } _ = client } ``` `NewClient` validates required inputs and applies these defaults: | Concern | Default | | ------------------------------------------------ | ----------------------------------------------------------------------------------------------- | | HTTP client | `http.DefaultClient` | | Logger | `slog.Default()` | | Off-Chain Reporting (OCR) signature verification | Enabled, with the production DON signer set and `DefaultMinRequiredSignatures` (4) | | Event-hash binding | **Off by default: you must opt in** with `crec.WithOrgID(...)` or `crec.WithWorkflowOwner(...)` | | Watcher polling | `PollInterval` 2s, `EventualConsistencyWindow` 2s (override with `WithWatcherPolling`) | It returns sentinel errors for the two unrecoverable misconfigurations: - `crec.ErrBaseURLRequired`: the base URL was empty. - `crec.ErrAPIKeyRequired`: the API key was empty. ## Apply options Options are applied in order; later options override earlier ones. ```go import ( "log/slog" "net/http" "os" "time" "github.com/smartcontractkit/crec-sdk" ) logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithOrgID(os.Getenv("CREC_ORG_ID")), crec.WithLogger(logger), crec.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), crec.WithWatcherPolling(5*time.Second, 10*time.Second), ) ``` The full set of options is documented in the [SDK Configuration](/crec/reference/sdk-configuration) reference. For event verification specifically, see [Verify Event Signatures](/crec/guides/events/verify-signatures). ## Smoke test: `ListNetworks` `ListNetworks` performs a single authenticated `GET /networks` and returns the list of networks your tenant can use. It confirms in one call that your client, your API key, and the CRE Connect endpoint are all healthy. ```go package main import ( "context" "fmt" "log" "os" "github.com/smartcontractkit/crec-sdk" ) func main() { client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithOrgID(os.Getenv("CREC_ORG_ID")), ) if err != nil { log.Fatal(err) } networks, hasMore, err := client.ListNetworks(context.Background()) if err != nil { log.Fatalf("ListNetworks failed: %v", err) } for _, n := range networks { fmt.Printf("- %-30s family=%s chainID=%s selector=%s\n", n.Name, n.ChainFamily, n.ChainId, n.ChainSelector) } if hasMore { fmt.Println("(more results available: paginate)") } } ``` A successful run prints something like: ```text - Ethereum Mainnet family=evm chainID=1 selector=5009297550715157269 - Base Sepolia family=evm chainID=84532 selector=... ``` If you instead see an authentication error, double-check: - The base URL points to the environment your key was issued for. - The key is the **full** value (no leading/trailing whitespace, no missing characters). - Your machine has outbound network access to the API host. If the request reaches the server but fails with a non-200 status, the SDK wraps the error with `crec.ErrListNetworks`: ```go if errors.Is(err, crec.ErrListNetworks) { // network reachable, but request was rejected: inspect logs } ``` Once `ListNetworks` returns at least one network, you have everything needed to move on to [Quickstart: Watch On-Chain Events](/crec/getting-started/quickstart-watch-events). ## Related - [SDK Configuration Options](/crec/reference/sdk-configuration): every functional option on `crec.NewClient`. - [Verify Event Signatures](/crec/guides/events/verify-signatures): what `WithOrgID` enables. - [Quickstart: Watch On-Chain Events](/crec/getting-started/quickstart-watch-events): the next step. --- # Quickstart: Watch On-Chain Events Source: https://docs.chain.link/crec/getting-started/quickstart-watch-events Last Updated: 2026-08-31 A **watcher** is a CRE Connect resource that monitors a contract on a chain and turns matching logs into verifiable events on your channel. In this quickstart you attach a watcher to the **Sepolia WETH token** (the WETH9 contract used by Uniswap V3) using a custom ABI, then poll the channel, cryptographically verify each event, and decode it into a Go map. You never sign a transaction: the watcher only reads. **Success looks like this**: once the watcher is `active`, your terminal prints a "listening for Transfer events" line, then **3 verified WETH `Transfer` events** with their `from`, `to`, and `value` fields, then the program exits cleanly. End-to-end runtime depends mostly on how often the WETH contract emits Transfer events, which is bursty by nature. The loop prints `...waiting for the next event` and `...no new events` heartbeats so you can tell it hasn't frozen. Sections 0-8 build a single `main.go` piece by piece: construct the client, create a channel, attach the watcher, wait for it to become active, then poll, verify, and decode. The complete file is in [9. Full program](#9-full-program) if you prefer to copy-paste-and-go. ## 0. Set up your project If you already have a Go module (for example by following [SDK Installation](/crec/getting-started/sdk-installation)), skip ahead to [Part 1](#1-imports-and-configuration). Otherwise, scaffold a fresh project from your terminal: ```bash mkdir crec-watch-events && cd crec-watch-events go mod init crec-watch-events go mod edit -go=1.25.5 -toolchain=go1.25.9 touch main.go ``` Open `main.go` in your editor. You will paste the snippets from Parts 1-8 into it in order (or, if you'd rather skip ahead, paste the full program from [Part 9](#9-full-program)). Dependencies are fetched at the run step in [Part 9](#9-full-program), once the file has imports for `go mod tidy` to resolve. ## 1. Imports and configuration The first snippet is the `package` declaration, the imports, and the constants that drive the rest of the file. Paste it at the very top of `main.go`: ```go package main import ( "context" "errors" "fmt" "log" "os" "time" "github.com/smartcontractkit/crec-sdk" "github.com/smartcontractkit/crec-sdk/channels" "github.com/smartcontractkit/crec-sdk/events" "github.com/smartcontractkit/crec-sdk/watchers" ) const ( chainSelector = "16015286601757825753" // ethereum-testnet-sepolia contractAddr = "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" // Sepolia WETH (Uniswap V3 deployment) watcherName = "quickstart-watcher" targetEvents = 3 // exit after this many verified events ) ``` The chain selector is the same `string` you would pass anywhere else in the SDK. Use `client.ListNetworks(...)` to discover the value for any other supported network. ## 2. Construct the client Everything from here on goes inside `func main() { ... }`. Add the function body and start with the client constructor: ```go client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithOrgID(os.Getenv("CREC_ORG_ID")), ) if err != nil { log.Fatal(err) } ``` The SDK ships with the production DON signer addresses baked in, so the **Off-Chain Reporting (OCR) signature** check is configured automatically. You still need to tell the client **which org's events you trust**: that's what `crec.WithOrgID(...)` does. It derives your unique workflow-owner address from the org ID and binds every event hash to your tenant. Skip it and `client.Events.Verify(...)` returns [`events.ErrOrgIDOrWorkflowOwnerReq`](/crec/reference/error-handling) on every call. Your Organization ID looks like `org_example00000000000`; see [Prerequisites](/crec/getting-started/prerequisites) for where to find it. ## 3. Create a channel A channel is a logical grouping for the watchers and events that belong to a single application. Pick a descriptive name: channel names must be unique within your tenant. ```go ctx := context.Background() ch, err := client.Channels.Create(ctx, channels.CreateInput{ Name: "quickstart-watch-events", }) if err != nil { log.Fatalf("create channel: %v", err) } fmt.Printf("created channel %s\n", ch.ChannelId) ``` If you already have a channel you want to reuse, list and pick one: ```go existing, _, _ := client.Channels.List(ctx, channels.ListInput{}) for _, c := range existing { fmt.Println(c.ChannelId, c.Name) } ``` ## 4. Attach a watcher with a custom ABI For arbitrary contracts (anything not covered by a CRE Connect extension) use `CreateWithABI`. You provide the contract address, the chain selector, the list of event names to monitor, and the matching ABI fragments. ```go abi := []watchers.EventABI{ { Type: "event", Name: "Transfer", Inputs: []watchers.EventABIInput{ {Indexed: true, Name: "from", Type: "address", InternalType: "address"}, {Indexed: true, Name: "to", Type: "address", InternalType: "address"}, {Indexed: false, Name: "value", Type: "uint256", InternalType: "uint256"}, }, }, } w, err := client.Watchers.CreateWithABI(ctx, ch.ChannelId, watchers.CreateWithABIInput{ Name: watcherName, ChainSelector: chainSelector, Address: contractAddr, Events: []string{"Transfer"}, ABI: abi, }) if err != nil { log.Fatalf("create watcher: %v", err) } fmt.Printf("watcher %s created (status=%s)\n", w.WatcherId, w.Status) ``` The SDK validates locally that: - the channel and chain selector are non-empty; - every entry in `ABI` has `Type: "event"` (`watchers.ErrInvalidABIType` otherwise); - every name in `Events` exists in `ABI` (`watchers.ErrEventNotInABI` otherwise); - the watcher name is at least 4 characters after trimming. ## 5. Wait for the watcher to become active A watcher is created `pending` and transitions to `active` once the underlying CRE workflow has been deployed. `WaitForActive` polls until the watcher reaches a terminal state or the deadline expires. ```go active, err := client.Watchers.WaitForActive(ctx, ch.ChannelId, w.WatcherId, 2*time.Minute) if err != nil { log.Fatalf("wait for active: %v", err) } fmt.Printf("watcher %s is %s\n", active.WatcherId, active.Status) ``` If the workflow deployment fails, `WaitForActive` returns one of the documented sentinel errors so you can react appropriately: | Error | Meaning | | -------------------------------------------------------------- | ---------------------------------------------- | | `watchers.ErrWaitForActiveTimeout` | The deadline elapsed while still in `pending`. | | `watchers.ErrWatcherDeploymentFailed` | The workflow failed to deploy. | | `watchers.ErrWatcherIsArchiving` / `ErrWatcherAlreadyArchived` | Someone archived the watcher concurrently. | ## 6. Poll, verify, and decode Once active, poll the channel. The watcher emits `watcher.event` envelopes for every observed WETH Transfer. This quickstart exits after `targetEvents` verified events so you have a clear "done" moment; in production you would loop indefinitely or use [`SearchEvents`](/crec/guides/events/poll-and-search) for historical queries. ```go seen := 0 processed := map[string]bool{} const pollEvery = 5 * time.Second fmt.Printf("listening for Transfer events on Sepolia WETH — waiting for the first event...\n") for seen < targetEvents { polled, _, err := client.Events.Poll(ctx, ch.ChannelId, nil) if err != nil { log.Fatalf("poll: %v", err) } progressBefore := seen for _, event := range polled { if processed[event.EventId.String()] { continue } // 1. Verify. Newly-active watchers can briefly return events // before the DON has produced an OCR proof. Treat that as // "not yet, try again" rather than a hard failure: don't // mark the event as processed, so the next poll sees it // again with the proof attached. verified, err := client.Events.Verify(&event) if errors.Is(err, events.ErrNoOCRProofs) { continue } // From here on the event is final — accepted or permanently // rejected, we won't reconsider it. processed[event.EventId.String()] = true if err != nil { log.Printf("verify failed for %s: %v", event.EventId, err) continue } if !verified { log.Printf("event %s did not verify — skipping", event.EventId) continue } // 2. Unwrap the polymorphic Event.Payload to the watcher payload. watcherPayload, err := event.Payload.AsWatcherEventPayload() if err != nil { log.Printf("not a watcher payload: %v", err) continue } // 3. Decode the base64 VerifiableEvent into a structured form. verifiable, err := client.Events.DecodeVerifiableEvent(&watcherPayload) if err != nil { log.Printf("decode verifiable: %v", err) continue } // 4. Unwrap the chain-event union to its EVM-specific form and // read the decoded log params (from, to, value). evmEvent, err := verifiable.ChainEvent.AsEVMEvent() if err != nil { log.Printf("not an EVM event: %v", err) continue } if evmEvent.Params == nil { continue } decoded := *evmEvent.Params seen++ fmt.Printf("[%d/%d] Transfer from=%v to=%v value=%v\n", seen, targetEvents, decoded["from"], decoded["to"], decoded["value"]) if seen >= targetEvents { break } } if seen < targetEvents { if seen == progressBefore { fmt.Printf(" ...no new events this cycle, still listening (%d/%d so far) — re-polling in %s\n", seen, targetEvents, pollEvery) } else { fmt.Printf(" ...waiting for the next event (%d/%d so far) — re-polling in %s\n", seen, targetEvents, pollEvery) } time.Sleep(pollEvery) } } fmt.Printf("done — verified %d Transfer events\n", seen) ``` Don't kill the program during one of those quiet stretches: the watcher is healthy and connected, it's just waiting for the next on-chain Transfer to land. `Verify` does two things: it checks the **OCR signatures** against the SDK's built-in production DON signer set (no extra config needed), and it confirms the **event hash** is bound to your tenant's workflow owner. That's why we passed `crec.WithOrgID(...)` in step 2. For multi-org or per-event verification flows, see [Verify Event Signatures](/crec/guides/events/verify-signatures). The decode pipeline has four steps because CRE Connect events are deliberately polymorphic: the same `Event` envelope carries watcher events today and will carry other payload kinds (e.g. operation status, future non-EVM chain events) in the future. To pull out the EVM log params (`from`, `to`, `value`) you walk: `Event.Payload → AsWatcherEventPayload() → DecodeVerifiableEvent() → ChainEvent.AsEVMEvent() → *Params`. The `Params` map is keyed by the input names you declared in the ABI you passed to `CreateWithABI`. ## 7. Expected output A representative end-to-end run looks like this. Most of the wall-clock time is spent waiting on Sepolia WETH to actually emit the next Transfer, with the `...no new events` heartbeat confirming the loop is healthy in between: ```bash ❯ go run . 2026/04/27 11:38:06 INFO Channel created successfully channel_id=2bf7840e-3301-427d-af79-38047fc3657b name=quickstart-watch-events created channel 2bf7840e-3301-427d-af79-38047fc3657b 2026/04/27 11:38:06 INFO Watcher created successfully watcher_id=41b6b6cc-5393-442f-904e-c9af2eb4d4b8 watcher 41b6b6cc-5393-442f-904e-c9af2eb4d4b8 created (status=pending) 2026/04/27 11:38:09 INFO Watcher is now active watcher 41b6b6cc-5393-442f-904e-c9af2eb4d4b8 is active listening for Transfer events on Sepolia WETH — waiting for the first event... ...no new events this cycle, still listening (0/3 so far) — re-polling in 5s ...no new events this cycle, still listening (0/3 so far) — re-polling in 5s ...no new events this cycle, still listening (0/3 so far) — re-polling in 5s ...(many more `...no new events` lines elided — about 2 minutes of polling) [1/3] Transfer from=0x4eBDcF7071191eE0Cc8386C8F4799Ca468619C67 to=0x3Ee4db9dD1f563fFf53b7919CD48803668b9FF6f value=2242341932209194 ...waiting for the next event (1/3 so far) — re-polling in 5s ...no new events this cycle, still listening (1/3 so far) — re-polling in 5s ...(another quiet stretch) [2/3] Transfer from=0x3498c861362f0868CC6AAfC25Bf3cBf9277e2Da9 to=0x3Ee4db9dD1f563fFf53b7919CD48803668b9FF6f value=88988505494225 ...waiting for the next event (2/3 so far) — re-polling in 5s ...no new events this cycle, still listening (2/3 so far) — re-polling in 5s ...(another quiet stretch) [3/3] Transfer from=0x8E97C8cD857FFB7f90c6075ce7C56398998C25D9 to=0x171Fab1099EAa24dF9738De7F235994a48b83BDF value=407 done — verified 3 Transfer events 2026/04/27 11:43:48 INFO Watcher archive initiated (async) watcher_id=41b6b6cc-5393-442f-904e-c9af2eb4d4b8 2026/04/27 11:43:48 INFO Channel archived successfully channel_id=2bf7840e-3301-427d-af79-38047fc3657b ``` ## 8. Verify it worked Cross-check the events you just printed against public Sepolia WETH token activity: - Open the WETH token on Sepolia Etherscan and confirm the most recent `Transfer` events match the `from`, `to`, and `value` triplets your program printed. - The DON-signed events your loop verified are the same logs Etherscan is rendering, but yours arrived with a cryptographic proof you re-checked locally with `Events.Verify`. If the loop hasn't reached 3 events yet: - That's almost always fine. Sepolia WETH Transfer activity is bursty: quiet stretches between events are normal. The `...waiting for the next event` and `...no new events` log lines are the loop telling you "still healthy, just nothing new on chain". - If the loop only prints `...no new events this cycle` for an extended period and the Etherscan link above shows recent transfers, the watcher likely isn't `active`. Inspect it with `client.Watchers.Get` and look at `status_reason`; see [Manage Watcher Lifecycle](/crec/guides/watchers/manage-lifecycle) and [Common symptoms](/crec/reference/error-handling#common-symptoms-what-to-do). ## What just happened End-to-end, every `Transfer` event traveled this path: 1. **The Sepolia WETH contract** emitted a `Transfer` log on-chain, paid for by some random user, completely independent of your code. 2. **The Chainlink DON** observed the log on Sepolia and produced an Off-Chain Reporting (OCR) signature attesting that f+1 nodes saw exactly this event. 3. **The CRE Connect API** stored the event with its OCR proof attached, then served it on your watcher's channel. 4. **`client.Events.Poll`** pulled a page of events for the channel using offset-based pagination; your in-memory dedupe map skipped anything already processed. 5. **`client.Events.Verify`** re-derived the event hash, looked up the OCR proof, and checked f+1 DON signatures (bound to your tenant via `WithOrgID`). Events without proofs surfaced as `ErrNoOCRProofs` so the loop could re-poll instead of trusting them. 6. **Your app** decoded the verified envelope into an `EVMEvent` and unpacked the typed `Transfer(from, to, value)` parameters for printing. In production you would either keep polling or use [`SearchEvents`](/crec/guides/events/poll-and-search) for historical reads. ## 9. Full program Here is everything wired together as a single `main.go`. The trailing `Archive` calls clean up the watcher and channel so they stop consuming DON capacity for your tenant. Drop them if you want to keep the watcher running between runs. ```go package main import ( "context" "errors" "fmt" "log" "os" "time" "github.com/smartcontractkit/crec-sdk" "github.com/smartcontractkit/crec-sdk/channels" "github.com/smartcontractkit/crec-sdk/events" "github.com/smartcontractkit/crec-sdk/watchers" ) const ( chainSelector = "16015286601757825753" // ethereum-testnet-sepolia contractAddr = "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" // Sepolia WETH (Uniswap V3 deployment) watcherName = "quickstart-watcher" targetEvents = 3 // exit after this many verified events ) func main() { client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithOrgID(os.Getenv("CREC_ORG_ID")), ) if err != nil { log.Fatal(err) } ctx := context.Background() ch, err := client.Channels.Create(ctx, channels.CreateInput{ Name: "quickstart-watch-events", }) if err != nil { log.Fatalf("create channel: %v", err) } fmt.Printf("created channel %s\n", ch.ChannelId) abi := []watchers.EventABI{ { Type: "event", Name: "Transfer", Inputs: []watchers.EventABIInput{ {Indexed: true, Name: "from", Type: "address", InternalType: "address"}, {Indexed: true, Name: "to", Type: "address", InternalType: "address"}, {Indexed: false, Name: "value", Type: "uint256", InternalType: "uint256"}, }, }, } w, err := client.Watchers.CreateWithABI(ctx, ch.ChannelId, watchers.CreateWithABIInput{ Name: watcherName, ChainSelector: chainSelector, Address: contractAddr, Events: []string{"Transfer"}, ABI: abi, }) if err != nil { log.Fatalf("create watcher: %v", err) } fmt.Printf("watcher %s created (status=%s)\n", w.WatcherId, w.Status) active, err := client.Watchers.WaitForActive(ctx, ch.ChannelId, w.WatcherId, 2*time.Minute) if err != nil { log.Fatalf("wait for active: %v", err) } fmt.Printf("watcher %s is %s\n", active.WatcherId, active.Status) seen := 0 processed := map[string]bool{} const pollEvery = 5 * time.Second fmt.Printf("listening for Transfer events on Sepolia WETH — waiting for the first event...\n") for seen < targetEvents { polled, _, err := client.Events.Poll(ctx, ch.ChannelId, nil) if err != nil { log.Fatalf("poll: %v", err) } progressBefore := seen for _, event := range polled { if processed[event.EventId.String()] { continue } verified, err := client.Events.Verify(&event) if errors.Is(err, events.ErrNoOCRProofs) { continue } processed[event.EventId.String()] = true if err != nil { log.Printf("verify failed for %s: %v", event.EventId, err) continue } if !verified { log.Printf("event %s did not verify — skipping", event.EventId) continue } watcherPayload, err := event.Payload.AsWatcherEventPayload() if err != nil { log.Printf("not a watcher payload: %v", err) continue } verifiable, err := client.Events.DecodeVerifiableEvent(&watcherPayload) if err != nil { log.Printf("decode verifiable: %v", err) continue } evmEvent, err := verifiable.ChainEvent.AsEVMEvent() if err != nil { log.Printf("not an EVM event: %v", err) continue } if evmEvent.Params == nil { continue } decoded := *evmEvent.Params seen++ fmt.Printf("[%d/%d] Transfer from=%v to=%v value=%v\n", seen, targetEvents, decoded["from"], decoded["to"], decoded["value"]) if seen >= targetEvents { break } } if seen < targetEvents { if seen == progressBefore { fmt.Printf(" ...no new events this cycle, still listening (%d/%d so far) — re-polling in %s\n", seen, targetEvents, pollEvery) } else { fmt.Printf(" ...waiting for the next event (%d/%d so far) — re-polling in %s\n", seen, targetEvents, pollEvery) } time.Sleep(pollEvery) } } fmt.Printf("done — verified %d Transfer events\n", seen) if _, err := client.Watchers.Archive(ctx, ch.ChannelId, w.WatcherId); err != nil { log.Printf("archive watcher: %v", err) } if _, err := client.Channels.Archive(ctx, ch.ChannelId); err != nil { log.Printf("archive channel: %v", err) } } ``` Run it from inside your project folder. Fetch dependencies, export your API key and Organization ID (the latter is in the header of your org's **Organization** page on app.chain.link), then run: ```bash go mod tidy export CREC_API_KEY= export CREC_ORG_ID= # e.g. org_example00000000000 go run . ``` ## Next steps - **Send a transaction**: continue with [Quickstart: Send Your First Operation](/crec/getting-started/quickstart-send-operation). - Watch your **own** contract instead of WETH by changing `contractAddr` and the ABI fragments; see [Create a Watcher with a Custom ABI](/crec/guides/watchers/create-with-abi). - Filter to specific addresses or replay history with `client.Events.SearchEvents`; covered in [Poll and Search Events](/crec/guides/events/poll-and-search). - Learn the lifecycle states in [Concepts → Watchers](/crec/concepts/watchers) and [Reference → Lifecycles](/crec/reference/lifecycles). --- # Quickstart: Send Your First Operation Source: https://docs.chain.link/crec/getting-started/quickstart-send-operation Last Updated: 2026-08-31 An **Operation** is a batch of EVM transactions executed atomically by a **Smart Account**, an on-chain contract that runs authorized transactions on your behalf. In this quickstart you build a one-call `Operation`, sign it with a local ECDSA key, and submit it through CRE Connect. The Chainlink DON broadcasts the transaction on Sepolia and pays the gas: your signing key never holds ETH, and you never manage a nonce or a relayer. **Success looks like this**: the program creates a channel, provisions a Smart Account on Sepolia, builds and signs a single-call `Operation` against your target contract, and prints the operation status as it walks through relay and confirmation. The two slowest stretches are the Smart Account deploy and on-chain inclusion of the operation; their duration depends on Sepolia network conditions. Sections 1–8 build a single `main.go` piece by piece: construct the client, create a channel, provision the wallet, build the calldata, assemble the operation, sign and submit, then track it to a confirmation status. The complete file is in [Part 11 Full program](#11-full-program) if you prefer to copy-paste-and-go. ## 0. Set up your project If you already have a Go module (for example by following [SDK Installation](/crec/getting-started/sdk-installation)), skip ahead to [Part 1](#1-imports-and-configuration). Otherwise, scaffold a fresh project from your terminal: ```bash mkdir crec-send-operation && cd crec-send-operation go mod init crec-send-operation go mod edit -go=1.25.5 -toolchain=go1.25.9 touch main.go ``` Open `main.go` in your editor. You will paste the snippets from Parts 1-8 into it in order (or, if you'd rather skip ahead, paste the full program from [Part 11](#11-full-program)). Dependencies are fetched at the run step in [Part 11](#11-full-program), once the file has imports for `go mod tidy` to resolve. ## 1. Imports and configuration The first snippet is the `package` declaration, the imports, and the constants that drive the rest of the file. Paste it at the very top of `main.go`: ```go package main import ( "context" "fmt" "log" "math/big" "os" "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" apiClient "github.com/smartcontractkit/crec-api-go/client" "github.com/smartcontractkit/crec-sdk" "github.com/smartcontractkit/crec-sdk/channels" "github.com/smartcontractkit/crec-sdk/transact/signer/local" "github.com/smartcontractkit/crec-sdk/transact/types" "github.com/smartcontractkit/crec-sdk/wallets" ) const ( chainSelector = "16015286601757825753" // ethereum-testnet-sepolia counterAddr = "0xD64EaB779f61EF99C19D892eeC8cBD0534df9374" // CrecQuickstartCounter, verified on Sepolia ) ``` The signing key is read at runtime from `CREC_SIGNER_PRIVATE_KEY` (Part 4 below). It is the same key you list as the wallet's `AllowedEcdsaSigners`: the Smart Account contract only accepts operations signed by one of its allowed signers. **Never hardcode it in source.** ## 2. Construct the client Everything from here on goes **inside `func main() { ... }`**: append each snippet to the bottom of `main.go`'s `main` function in order. ```go ctx := context.Background() client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithOrgID(os.Getenv("CREC_ORG_ID")), ) if err != nil { log.Fatal(err) } ``` `WithOrgID` is not required to *send* an operation, but without it `client.Events.VerifyOperationStatus(...)` returns `events.ErrOrgIDOrWorkflowOwnerReq` on every call. Set it once at construction so the rest of this quickstart and the [watch-events quickstart](/crec/getting-started/quickstart-watch-events) stay consistent. Your Organization ID looks like `org_example00000000000`; see [Prerequisites](/crec/getting-started/prerequisites) for where to find it. ## 3. Create a channel ```go ch, err := client.Channels.Create(ctx, channels.CreateInput{ Name: "quickstart-send-operation", }) if err != nil { log.Fatalf("create channel: %v", err) } ``` ## 4. Provision an ECDSA Smart Account A Smart Account is a contract deployed on the target chain. Creating one through the SDK queues an on-chain deploy and returns the wallet record immediately. ```go signerKeyHex := strings.TrimPrefix(os.Getenv("CREC_SIGNER_PRIVATE_KEY"), "0x") if signerKeyHex == "" { log.Fatal("CREC_SIGNER_PRIVATE_KEY is not set") } pk, err := crypto.HexToECDSA(signerKeyHex) if err != nil { log.Fatalf("invalid CREC_SIGNER_PRIVATE_KEY: %v", err) } ownerAddr := crypto.PubkeyToAddress(pk.PublicKey).Hex() allowedSigners := []string{ownerAddr} walletType := apiClient.WalletType("ecdsa") statusChannelID := ch.ChannelId // reuse the channel created in step 3 for status events w, err := client.Wallets.Create(ctx, wallets.CreateInput{ Name: "quickstart-wallet", ChainSelector: chainSelector, WalletOwnerAddress: ownerAddr, WalletType: walletType, AllowedEcdsaSigners: &allowedSigners, StatusChannelId: &statusChannelID, // optional; receives wallet.status events }) if err != nil { log.Fatalf("create wallet: %v", err) } fmt.Printf("wallet %s status=%s\n", w.WalletId, w.Status) ``` The wallet starts in `pending`; the deploy proceeds asynchronously. For this quickstart, poll until the on-chain account is ready: ```go deadline := time.Now().Add(3 * time.Minute) for time.Now().Before(deadline) { cur, err := client.Wallets.Get(ctx, w.WalletId) if err != nil { log.Fatal(err) } fmt.Printf(" ...wallet status=%s\n", cur.Status) if cur.Status == "deployed" { w = cur break } if cur.Status == "failed" { log.Fatalf("wallet deploy failed (status=%s): subscribe to the wallet's status_channel_id for the StatusReason", cur.Status) } time.Sleep(5 * time.Second) } if w.Status != "deployed" { log.Fatal("wallet did not deploy in time") } fmt.Printf("Smart Account = %s\n", w.Address) ``` `w.Address` is the deployed contract address; this is the `Operation.Account` for every operation you submit through this wallet. ## 5. Build the calldata Using `go-ethereum/accounts/abi`, encode the call to `increment()` on the counter contract. This produces standard EVM calldata, the same bytes you would pass to `eth_sendTransaction.data` for a direct call: ```go const counterABI = `[{"type":"function","name":"increment","inputs":[],"outputs":[]}]` parsed, err := abi.JSON(strings.NewReader(counterABI)) if err != nil { log.Fatal(err) } calldata, err := parsed.Pack("increment") if err != nil { log.Fatal(err) } ``` For a function with arguments, pass them positionally to `Pack(...)` after the function name (for example `parsed.Pack("transfer", recipient, amount)`). ## 6. Build the operation An `Operation` is a list of `Transaction`s plus the Smart Account it executes against. Here we build the Operation manually and use `time.Now().Unix()` as the ID, which is unique as long as you submit at most one operation per second. (`ExecuteTransactions` generates a random 128-bit ID for you instead.) `Deadline` is required (`big.NewInt(0)` means no expiration). ```go op := &types.Operation{ ID: big.NewInt(time.Now().Unix()), Account: common.HexToAddress(w.Address), Deadline: big.NewInt(0), Transactions: []types.Transaction{{ To: common.HexToAddress(counterAddr), Value: big.NewInt(0), Data: calldata, }}, } ``` Multiple `Transaction`s in one `Operation` execute atomically; see [Batch Transactions](/crec/guides/operations/batch-transactions). ## 7. Sign and execute `ExecuteOperation` performs both EIP-712 signing and submission in one call. ```go opSigner := local.NewSigner(pk) submitted, err := client.Transact.ExecuteOperation(ctx, ch.ChannelId, opSigner, op, chainSelector) if err != nil { log.Fatalf("execute: %v", err) } fmt.Printf("operation %s accepted (status=%s)\n", submitted.OperationId, submitted.Status) ``` If you need to inspect or audit the signed payload before sending, split this into two calls instead: ```go hash, sig, err := client.Transact.SignOperation(ctx, op, opSigner, chainSelector) // ... display hash, log sig, persist for compliance ... sent, err := client.Transact.SendSignedOperation(ctx, ch.ChannelId, op, sig, chainSelector) ``` See [Build and Sign Operations](/crec/guides/operations/build-and-sign) for the breakdown. ## 8. Track until confirmed Operations move through relay statuses (`accepted`, `sending`, `sent`, `broadcasting`) and then into confirmation statuses such as `confirmed_latest`, `confirmed_safe`, or `confirmed`. Poll `GetOperation` until you reach the confirmation level your application needs: ```go for { op, err := client.Transact.GetOperation(ctx, ch.ChannelId, submitted.OperationId) if err != nil { log.Fatal(err) } fmt.Printf(" status=%s\n", op.Status) if op.Status == "confirmed_latest" || op.Status == "confirmed_safe" || op.Status == "confirmed" { break } if op.Status == "failed" { log.Fatalf("operation failed (status=%s): subscribe to the channel for the operation.status event with StatusReason", op.Status) } time.Sleep(3 * time.Second) } fmt.Println("done: operation confirmed on-chain") ``` For this quickstart, any confirmation status is enough to prove the operation landed. Production workflows should choose the right status for the action they take next; see [Multi-Event Finality](/crec/concepts/multi-event-finality). ## 9. Expected output When you run the program (see [Part 11 Full program](#11-full-program) for the complete file and the run command), you should see something like this. Exact UUIDs, addresses, and timings will differ, but the shape will match: ```bash 2026/04/27 12:04:11 INFO Channel created successfully channel_id=51c0f0c9-7a9d-4b32-9f9b-6c4d2c3a4e7d name=quickstart-send-operation created channel 51c0f0c9-7a9d-4b32-9f9b-6c4d2c3a4e7d 2026/04/27 12:04:11 INFO Wallet created successfully wallet_id=2f8c9a31-5b8e-4d12-90fb-1d3a7c0b6e22 status=pending wallet 2f8c9a31-5b8e-4d12-90fb-1d3a7c0b6e22 status=pending ...wallet status=pending ...wallet status=pending ...wallet status=deploying ...wallet status=deploying ...wallet status=deploying ...wallet status=deploying ...wallet status=deploying ...wallet status=deployed Smart Account = 0xC4f7DE4dB6Ea02C5dC37c60B8a3E81F4Cf5A1234 2026/04/27 12:05:32 INFO Operation accepted operation_id=8d5b9c1e-3f4a-42b8-a01c-67d9b2f4e3a1 operation 8d5b9c1e-3f4a-42b8-a01c-67d9b2f4e3a1 accepted (status=accepted) status=accepted status=sending status=sent status=broadcasting status=broadcasting status=confirmed_latest done: operation confirmed on-chain 2026/04/27 12:06:18 INFO Wallet archive initiated (async) wallet_id=2f8c9a31-5b8e-4d12-90fb-1d3a7c0b6e22 2026/04/27 12:06:18 INFO Channel archived successfully channel_id=51c0f0c9-7a9d-4b32-9f9b-6c4d2c3a4e7d ``` ## 10. Verify it worked There are two complementary cross-checks: looking at the **Counter contract** to confirm your call actually landed, and looking at the **Smart Account** to see the account-abstracted gas flow. ### Check the Counter Open the deployed `CrecQuickstartCounter` on Sepolia Etherscan: On the **Events** tab, filter by your Smart Account address (the `Smart Account = 0x...` line from your run). You should see one new `Incremented` event from the time of your run, with: | Topic / data field | Expected value | | ------------------------ | --------------------------------------------------------------------------------------- | | `caller` (indexed topic) | Your Smart Account address, **not** the EOA derived from `CREC_SIGNER_PRIVATE_KEY` | | `newGlobalCount` | The Counter's global `count` after your call (whatever it had crept up to + 1) | | `newCallerCount` | `1` if this is your Smart Account's first call to the Counter; otherwise `previous + 1` | You can also confirm directly on the **Read Contract** tab: `countOf()` should return `1` (or whatever number of times your specific Smart Account has called `increment`). ### Check the gas flow on your Smart Account Now paste the Smart Account address into the same explorer's search bar. Open the most recent transaction. In the call trace you should see: - **From** (`tx.origin`): an EOA you don't recognize. This is the DON's writer EOA paying gas. **Not** the EOA derived from your `CREC_SIGNER_PRIVATE_KEY`. - **To**: a Chainlink-operated writer contract that the DON uses to dispatch the call into your Smart Account. - **Internal txns**: the Smart Account calling `CrecQuickstartCounter.increment()` at `0xD64EaB779f61EF99C19D892eeC8cBD0534df9374`. - **Status**: `Success` ✓ That `From` field is the whole point of this quickstart: gas came from the DON, not from your wallet, even though the call effectively executed `increment()` on your behalf. See [Concepts: Account Abstraction](/crec/concepts/account-abstraction) for the full picture of the trust path. ## What just happened End-to-end, one `increment()` call traveled this path: 1. **Your app** built an `Operation` (target contract, calldata, deadline) and handed it to `client.Transact.ExecuteOperation`. 2. **The SDK** hashed it as EIP-712 typed data, signed the digest with your local signer, and POSTed `(operation, signature)` to the CRE Connect API. 3. **The CRE Connect API** queued the request and handed it off to a CRE workflow on the Chainlink DON. 4. **The DON** signed and broadcast the underlying transaction on-chain, paying gas from its own writer EOA, not yours. 5. **Your Smart Account** (deployed at `w.Address`) verified your EIP-712 signature against its allow-list of approved signers, then made a vanilla EVM `CALL` into `CrecQuickstartCounter.increment()`. 6. **The DON** observed the on-chain confirmation and emitted an `operation.status` event back into your channel. Your `GetOperation` polling loop saw that confirmation status on its last iteration. ## 11. Full program Here is everything wired together as a single `main.go`. The trailing `Archive` calls clean up the wallet and channel so they stop consuming DON capacity for your tenant. Drop them if you want to keep the Smart Account around for follow-up operations. ```go package main import ( "context" "fmt" "log" "math/big" "os" "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" apiClient "github.com/smartcontractkit/crec-api-go/client" "github.com/smartcontractkit/crec-sdk" "github.com/smartcontractkit/crec-sdk/channels" "github.com/smartcontractkit/crec-sdk/transact/signer/local" "github.com/smartcontractkit/crec-sdk/transact/types" "github.com/smartcontractkit/crec-sdk/wallets" ) const ( chainSelector = "16015286601757825753" // ethereum-testnet-sepolia counterAddr = "0xD64EaB779f61EF99C19D892eeC8cBD0534df9374" // CrecQuickstartCounter, verified on Sepolia ) const counterABI = `[{"type":"function","name":"increment","inputs":[],"outputs":[]}]` func main() { ctx := context.Background() client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithOrgID(os.Getenv("CREC_ORG_ID")), ) if err != nil { log.Fatal(err) } ch, err := client.Channels.Create(ctx, channels.CreateInput{ Name: "quickstart-send-operation", }) if err != nil { log.Fatalf("create channel: %v", err) } fmt.Printf("created channel %s\n", ch.ChannelId) signerKeyHex := strings.TrimPrefix(os.Getenv("CREC_SIGNER_PRIVATE_KEY"), "0x") if signerKeyHex == "" { log.Fatal("CREC_SIGNER_PRIVATE_KEY is not set") } pk, err := crypto.HexToECDSA(signerKeyHex) if err != nil { log.Fatalf("invalid CREC_SIGNER_PRIVATE_KEY: %v", err) } ownerAddr := crypto.PubkeyToAddress(pk.PublicKey).Hex() allowedSigners := []string{ownerAddr} walletType := apiClient.WalletType("ecdsa") statusChannelID := ch.ChannelId w, err := client.Wallets.Create(ctx, wallets.CreateInput{ Name: "quickstart-wallet", ChainSelector: chainSelector, WalletOwnerAddress: ownerAddr, WalletType: walletType, AllowedEcdsaSigners: &allowedSigners, StatusChannelId: &statusChannelID, }) if err != nil { log.Fatalf("create wallet: %v", err) } fmt.Printf("wallet %s status=%s\n", w.WalletId, w.Status) deadline := time.Now().Add(3 * time.Minute) for time.Now().Before(deadline) { cur, err := client.Wallets.Get(ctx, w.WalletId) if err != nil { log.Fatal(err) } fmt.Printf(" ...wallet status=%s\n", cur.Status) if cur.Status == "deployed" { w = cur break } if cur.Status == "failed" { log.Fatalf("wallet deploy failed (status=%s): subscribe to the wallet's status_channel_id for the StatusReason", cur.Status) } time.Sleep(5 * time.Second) } if w.Status != "deployed" { log.Fatal("wallet did not deploy in time") } fmt.Printf("Smart Account = %s\n", w.Address) parsed, err := abi.JSON(strings.NewReader(counterABI)) if err != nil { log.Fatal(err) } calldata, err := parsed.Pack("increment") if err != nil { log.Fatal(err) } op := &types.Operation{ ID: big.NewInt(time.Now().Unix()), Account: common.HexToAddress(w.Address), Deadline: big.NewInt(0), Transactions: []types.Transaction{{ To: common.HexToAddress(counterAddr), Value: big.NewInt(0), Data: calldata, }}, } opSigner := local.NewSigner(pk) submitted, err := client.Transact.ExecuteOperation(ctx, ch.ChannelId, opSigner, op, chainSelector) if err != nil { log.Fatalf("execute: %v", err) } fmt.Printf("operation %s accepted (status=%s)\n", submitted.OperationId, submitted.Status) for { cur, err := client.Transact.GetOperation(ctx, ch.ChannelId, submitted.OperationId) if err != nil { log.Fatal(err) } fmt.Printf(" status=%s\n", cur.Status) if cur.Status == "confirmed_latest" || cur.Status == "confirmed_safe" || cur.Status == "confirmed" { break } if cur.Status == "failed" { log.Fatalf("operation failed (status=%s): subscribe to the channel for the operation.status event with StatusReason", cur.Status) } time.Sleep(3 * time.Second) } fmt.Println("done: operation confirmed on-chain") if err := client.Wallets.Archive(ctx, w.WalletId); err != nil { log.Printf("archive wallet: %v", err) } if _, err := client.Channels.Archive(ctx, ch.ChannelId); err != nil { log.Printf("archive channel: %v", err) } } ``` Run it from inside your project folder. Fetch dependencies, export your API key and Organization ID (the latter is in the header of your org's **Organization** page on app.chain.link), and your signer private key, then run: ```bash go mod tidy export CREC_API_KEY= export CREC_ORG_ID= # e.g. org_example00000000000 export CREC_SIGNER_PRIVATE_KEY= # `0x` prefix optional go run . ``` ## Next steps - **Watch your own operation as a verifiable event**: point the [watch-events quickstart](/crec/getting-started/quickstart-watch-events) at the same `CrecQuickstartCounter` address (`0xD64EaB779f61EF99C19D892eeC8cBD0534df9374`) with the `Incremented` ABI. Filter by your Smart Account address as the indexed `caller` topic and you'll see your own gas-free `increment` flow back through the events pipeline, signed by the production DON and decoded. - Production-grade signing: replace `local.NewSigner` with [AWS KMS](/crec/guides/signers/aws-kms), [HashiCorp Vault](/crec/guides/signers/hashicorp-vault), [Fireblocks](/crec/guides/signers/fireblocks), or [Privy](/crec/guides/signers/privy). - Atomic batches and approve-then-call patterns: [Batch Transactions](/crec/guides/operations/batch-transactions). - Auditable signing: [Signing Transparency](/crec/guides/operations/signing-transparency). - Use the **DTA extension** for typed Operation builders: [DTA Overview](/crec/extensions/dta). --- # CRE Connect Architecture Source: https://docs.chain.link/crec/concepts/architecture Last Updated: 2026-08-31 **CRE Connect (CREC)** gives applications one API and one Go SDK for three chain-facing tasks: watch on-chain events, execute gas-less operations, and run one-shot chain queries. Chainlink runs the DON infrastructure behind that API. A Chainlink Decentralized Oracle Network (DON) is a group of independent Chainlink nodes. In CRE Connect, DONs observe contract logs, produce signed reports, and submit transactions through Smart Accounts. CRE Connect uses CRE (Chainlink Runtime Environment) to coordinate that work. Your application only talks to the **CRE Connect Go SDK** or the **CRE Connect REST API**. ## How CRE Connect fits together CRE Connect is a closed loop. Your application sends requests to the SDK or REST API. Chainlink DONs watch chains, execute operations, and send signed events or query results back through the same API. The diagram below shows the shared components: - **Events**: a Watcher monitors contract logs and returns `watcher.event` records with OCR proofs. - **Operations**: your app submits an EIP-712-signed Operation, and the DON broadcasts the resulting transaction through your Smart Account. - **Queries**: your app submits a read-only EVM call, and the DON returns a signed query result. How to read it: - The top layers, **App**, **SDK**, and **REST API**, are the same for every interaction. - **Your Smart Account** is the on-chain account that executes Operations. - **Watched contracts** are contracts you point a [Watcher](/crec/concepts/watchers) at. They can be your contracts, partner contracts, or public protocol contracts. - **CRE workflows** run inside the Chainlink Runtime Environment. CRE Connect manages them for you. ## The Go SDK (`crec-sdk`) The SDK is the application's entry point. A single `crec.Client` exposes resource-oriented sub-clients: - `client.Channels`: create and manage **Channels**. - `client.Watchers`: create and manage **Watchers**. - `client.Events`: poll, search, and verify **Events**. - `client.Transact`: build, sign, and submit **Operations**. - `client.Wallets`: provision and manage **Smart Accounts**. - `client.Queries`: submit one-shot, DON-backed **Chain Queries**. Construction is a single call: ```go client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithOrgID(os.Getenv("CREC_ORG_ID")), ) ``` By default, the client is configured with **`DefaultMinRequiredSignatures = 4`** and the **`DefaultValidSigners`** set published by Chainlink Labs. Both are configurable through SDK [options](/crec/reference/sdk-configuration). ## The CRE Connect REST API The CRE Connect REST API is the public entry point for every interaction with the platform. The SDK (or any direct REST client) talks to it over HTTPS at `https://cre-connect.api.chain.link/v1`, authenticated with `Authorization: Apikey `. The full surface is documented in the [REST API Reference](/crec/reference/rest-api) and the [Swagger explorer](/api/crec/docs). ## How operations, events, and queries work CRE Connect manages the CRE workflows that interact with Chainlink DONs. You do not write or deploy those workflows yourself. | Task | What happens | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Watch events** | A Watcher monitors a contract on a supported chain. When a matching log reaches the configured confidence level, CRE Connect returns a `watcher.event` with an OCR proof. | | **Execute operations** | Your application submits an Operation through the SDK or REST API. The DON broadcasts the transaction through your Smart Account, and CRE Connect returns `operation.status` events as the operation progresses. | | **Run queries** | Your application submits an `evm_call` query. The DON executes the read against the selected block and returns a `query.status` result with the resolved block metadata. | [Extensions](/crec/concepts/extensions) such as DTA contribute pre-packaged operation builders and watcher definitions for supported protocols. See [Watchers](/crec/concepts/watchers), [Operations](/crec/concepts/operations), and [Chain Queries](/crec/concepts/queries) for the resource-specific models. ## Smart Accounts on-chain Operations execute through a **Smart Account** contract deployed per tenant, per chain, per wallet. This contract: - Verifies the EIP-712 signature attached to each Operation against an allow-list of approved signer keys (ECDSA or RSA), configured at wallet creation time. - Executes each `Transaction` (`to`, `value`, `data`) atomically. The Operation succeeds or fails as a single unit. - Emits an `OperationExecuted` log that CRE Connect picks up and turns into a verifiable `operation.status` event you can read through `client.Events`. The Smart Account is **not an ERC-4337 account**. It is a Chainlink-native contract whose authorization model is rooted in the EIP-712 domain `CLLSmartAccount` (see [EIP-712 Signing](/crec/concepts/eip712-signing)). The DON acts as the relayer and pays gas for the on-chain execution, giving applications a fully gas-less developer experience (see [Account Abstraction & Gas Sponsorship](/crec/concepts/account-abstraction)). ## Resources and lifecycles Every interaction in CRE Connect is scoped to one of these resource types: | Resource | Purpose | Lifecycle states | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | [Channel](/crec/concepts/channels) | Logical scope for watchers, wallets, operations, queries, and the event stream. | `active`, `archived` | | [Watcher](/crec/concepts/watchers) | On-chain event monitor backed by CRE Connect. | `pending`, `active`, `failed`, `archiving`, `archived` | | [Wallet](/crec/concepts/smart-accounts) | Smart Account configuration with allowed signers. | `pending`, `deploying`, `deployed`, `archived`, `failed` | | [Event](/crec/concepts/verifiable-events) | Immutable record for watcher events, lifecycle updates, and query results. | n/a | | [Operation](/crec/concepts/operations) | EIP-712-authorized batch of EVM transactions. Draft operations can wait for a signature before execution. | `pending_signature`, `accepted`, `sending`, `sent`, `broadcasting`, `confirmed_latest`, `confirmed_safe`, `confirmed`, terminal states | | [Query](/crec/concepts/queries) | One-shot, read-only EVM call against a selected block. | `accepted`, `sending`, `sent`, `completed`, `failed`, `expired` | Every state transition in the table is observable through `client.Events`, which means your application can react to any lifecycle change with the same verifiable event pipeline it uses for on-chain data. See [Lifecycles](/crec/reference/lifecycles) for the full state machines. ## Related - [Channels](/crec/concepts/channels) · [Watchers](/crec/concepts/watchers) · [Smart Accounts](/crec/concepts/smart-accounts) · [Operations](/crec/concepts/operations): every component in detail. - [Verifiable Events](/crec/concepts/verifiable-events): what makes an event "verifiable" in this architecture. - [Chain Queries](/crec/concepts/queries): one-shot, DON-backed reads. - [Getting Started](/crec/getting-started/prerequisites): turn the model into running code. --- # Channels Source: https://docs.chain.link/crec/concepts/channels Last Updated: 2026-08-31 A **channel** is the top-level scoping unit in CRE Connect. Every other resource, including watchers, wallets, operations, queries, and events, lives inside exactly one channel. Channels give you: - **Isolation.** Two unrelated business flows, for example a regulated-fund subscription pipeline and a treasury operations pipeline, run side-by-side without sharing event streams or watcher state. - **A single, ordered event stream.** All events produced inside a channel, including watcher events, operation status updates, wallet status updates, watcher status updates, and query status updates, arrive through one paginated API and one SDK polling loop. - **A simple lifecycle.** A channel is either `active` or `archived`. Archiving a channel disables it for future writes; the immutable event history remains queryable. ## When to create a separate channel Use a separate channel whenever you want a separate audit trail, a separate set of subscribers, or a separate set of watchers. Common patterns: - **One channel per environment**: a `staging` channel for testnets and a `production` channel for mainnets. - **One channel per business line**: a `dta-fund-A` channel for one tokenized fund and a `dta-fund-B` channel for another. - **One channel per integration**: useful when integrating CRE Connect into multiple downstream services that should not see each other's events. There is no hard limit on the number of channels per tenant; create as many as your operational model needs. ## What lives in a channel Each channel owns: - A set of **Watchers** that monitor on-chain contracts (see [Watchers](/crec/concepts/watchers)). - A set of **Wallets** (Smart Accounts) authorized to execute operations (see [Smart Accounts](/crec/concepts/smart-accounts)). - A set of **Chain Queries**: one-shot, DON-backed blockchain reads (see [Chain Queries](/crec/concepts/queries)). - An ordered, immutable stream of **Events** in five shapes: `watcher.event`, `watcher.status`, `wallet.status`, `operation.status`, and `query.status`. Each carries an Off-Chain Reporting (OCR) proof for [verification](/crec/concepts/event-verification). - A history of submitted **Operations** and their lifecycle transitions. ## Channel lifecycle Channels move through two states only: | State | Meaning | Allowed actions | | ---------- | ------------------------------------------------------------- | ------------------------------------------------------------------------- | | `active` | The channel can accept new watchers, wallets, and operations. | Create / Update watchers, create wallets, submit operations, poll events. | | `archived` | The channel is read-only. | Get channel, poll historical events. | A channel **cannot be archived while it has active watchers**. Archive every watcher in the channel first (see [Manage Watcher Lifecycle](/crec/guides/watchers/manage-lifecycle)). ## Channel fields When you create a channel via the SDK or REST API, you provide: | Field | Required | Constraints | | ------------- | -------- | ---------------------------------------------------- | | `name` | Yes | 1–255 characters. Must be unique within your tenant. | | `description` | No | Free-form text describing the channel's purpose. | The server returns a UUID `id` that you use everywhere downstream, when creating watchers, wallets, and operations. ## Listing and filtering When listing channels, you can filter by name (partial match) and status, and paginate through the results. The SDK's `channels.ListInput` exposes `Limit` (1–50, default 20) and `Offset` parameters; see [Create and Manage Channels](/crec/guides/channels/manage-channels) for examples. ## Related - [Create and Manage Channels](/crec/guides/channels/manage-channels): the SDK and REST workflow. - [Watchers](/crec/concepts/watchers) and [Smart Accounts](/crec/concepts/smart-accounts): the resources that live inside a channel. - [Poll and Search Events](/crec/guides/events/poll-and-search): how the channel-scoped event stream is consumed. --- # Watchers Source: https://docs.chain.link/crec/concepts/watchers Last Updated: 2026-08-31 A **watcher** is the resource that turns raw on-chain logs into [verifiable events](/crec/concepts/verifiable-events) inside a channel. Each watcher is bound to: - **One channel**: the scope into which the verifiable events are emitted. - **One chain**: identified by a CCIP chain selector (see [Supported Networks](/crec/supported-networks)). - **One contract address**: the contract being observed. - **One or more event signatures**: the specific logs you care about. When a matching log is observed and reaches the configured [confidence level](/crec/concepts/confidence-levels), the underlying CRE workflow signs a verifiable record with the DON's Off-Chain Reporting (OCR) keys and posts it back to CRE Connect, where it becomes available through the SDK as a `watcher.event` event. ## Two ways to create a watcher CRE Connect supports two creation paths. You pick the one that best fits your use case. ### Service-backed watcher A **service-backed** watcher uses a pre-packaged **CRE Connect extension** that already knows how to monitor a class of contracts. You name the service (for example, `dta.v2`), point at a contract address, and pick which of the service's published events you want to receive. The extension ships: - A protocol-aware monitoring pipeline (provisioned for you by CRE Connect). - The ABIs of every contract in scope. - Decoded event types so you do not need to write ABI-decoding glue (see [Decode Event Data](/crec/guides/events/decode-data)). When a CRE Connect extension exists for your protocol, this path lets you reference the service by name (`Service: "dta.v2"`) instead of supplying the contract ABI yourself. See [Create a Watcher with a Predefined Service](/crec/guides/watchers/create-with-service). ```go watcher, err := client.Watchers.CreateWithService( ctx, channelID, watchers.CreateWithServiceInput{ Name: "DTA fund alpha", ChainSelector: "16015286601757825753", // Ethereum Sepolia Address: "0xFundAddress...", Service: "dta.v2", Events: []string{"SubscriptionRequested", "RedemptionRequested"}, }, ) ``` ### ABI-backed watcher (custom) An **ABI-backed** watcher accepts a raw ABI and one or more event names. CRE Connect generates the watching pipeline on the fly. Use this path for any contract that is not covered by a predefined service. See [Create a Watcher with a Custom ABI](/crec/guides/watchers/create-with-abi). ```go watcher, err := client.Watchers.CreateWithABI( ctx, channelID, watchers.CreateWithABIInput{ Name: "USDC transfers on Sepolia", ChainSelector: "16015286601757825753", Address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", ABI: usdcABI, // []watchers.EventABIInput Events: []string{"Transfer"}, }, ) ``` The SDK validates that every requested event name exists in the ABI and that every supplied entry is of type `event` before the request is sent. ## Lifecycle Watchers are stateful resources. The `WatcherStatus` enum has these values: |
Status
| Meaning | What you can do | | --------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `pending` | The watcher request was accepted; CRE is provisioning the workflow. | Use `WaitForActive(ctx, channelID, watcherID, timeout)` to block until ready. | | `active` | The watcher is observing chain state and emitting events. | Poll events with `client.Events.Poll(...)`. | | `archiving` | An archive request is in progress (operation is async: `202 Accepted`). | Use `WaitForArchived(ctx, channelID, watcherID, timeout)`. | | `archived` | The watcher is fully torn down. | The watcher is read-only; its historical events remain accessible. | | `failed` | An unrecoverable error occurred during create or archive. | Inspect the latest `watcher.status` event for the error reason; archive and recreate. | A separate `WatcherEventStatus` enum is carried inside `watcher.status` events. It includes everything in `WatcherStatus` plus an explicit `archive_failed` value, so subscribers can distinguish a failed deployment from a failed teardown. ## DON family (`don_family`) Every `Watcher` and `WatcherSummary` returned by the API carries a **`don_family`** field (e.g. `"zone-a"`). It identifies the DON whose nodes provisioned the watcher's workflow and signed its events. You do **not** set `don_family` on creation: the backend assigns it based on your channel's deployment. Surface it in dashboards and use it to **verify event signatures**: match the signers reported in `watcher.event` payloads against the keys announced by that DON family. See [Verify Signatures](/crec/guides/events/verify-signatures). ```go w, _ := client.Watchers.Get(ctx, channelID, watcherID) fmt.Println("watcher", w.WatcherId, "is signed by DON family", w.DonFamily) ``` ## Defaults and limits (SDK) | Setting | Default | Configurable via | | --------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------- | | Watcher name minimum length | 4 runes (after trimming whitespace) | n/a: enforced client-side | | Polling interval (events) | 2 seconds | `crec.WithWatcherPolling(...)` | | Eventual-consistency window | 2 seconds | `crec.WithWatcherPolling(...)` | | Confidence level | Per-network default returned by `ListNetworks` | Currently set by the platform; see [Confidence Levels](/crec/concepts/confidence-levels) | ## Updating a watcher Updates after creation are intentionally narrow: only the watcher **name** can be changed via `Update`. To change the chain, address, ABI, or event list, archive the watcher and create a new one. ## Related - [Create a Watcher with a Predefined Service](/crec/guides/watchers/create-with-service) · [Create a Watcher with a Custom ABI](/crec/guides/watchers/create-with-abi) · [Manage Watcher Lifecycle](/crec/guides/watchers/manage-lifecycle): the implementation guides. - [Verifiable Events](/crec/concepts/verifiable-events): what a watcher emits. - [Confidence Levels](/crec/concepts/confidence-levels): how a watcher decides when an event is "ready". - [Channels](/crec/concepts/channels): the container a watcher lives in. --- # Verifiable Events Source: https://docs.chain.link/crec/concepts/verifiable-events Last Updated: 2026-08-31 A **verifiable event** is the unit of observation that CRE Connect delivers to your application. Every event a channel emits, whether it represents a contract log, a watcher state change, a wallet state change, an operation status, or a query status, is signed by the Chainlink **Decentralized Oracle Network (DON)** using the **Off-Chain Reporting (OCR)** protocol. Your application can re-verify that signature locally before acting on the data. This is the property that makes the events *verifiable*: the SDK does not ask you to trust CRE Connect's delivery infrastructure. CRE Connect is just a delivery mechanism. The cryptographic proof of authenticity is generated by the DON and travels with the event. ## Event envelope Every event you receive from `client.Events.Poll(...)` or `client.Events.SearchEvents(...)` shares the same envelope: | Field | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------ | | `id` | Server-assigned UUID. | | `channel_id` | The channel that produced this event. | | `headers.type` | One of the [event types](#event-types) listed below. | | `headers.proofs` | The list of cryptographic proofs attached to the event. CRE Connect events always carry exactly **one OCR proof**. | | `payload` | The typed payload, decoded according to `headers.type`. | | `created_at` | Timestamp CRE Connect received the event from the DON. | The `headers.proofs` array is what makes the event verifiable. Each entry is an `OCRProof` containing: | Sub-field | Description | | ------------- | --------------------------------------------------------------------------------------------------- | | `ocr_report` | The raw OCR3 report bytes produced by the workflow. The DON signers signed the hash of this report. | | `ocr_context` | Replay-protection context (epoch / round / config hash). | | `signatures` | An array of OCR signatures over `keccak256(keccak256(report) ‖ context)`. | When you call `client.Events.Verify(event)` (for `watcher.event`) or `client.Events.VerifyOperationStatus(event)` (for `operation.status`), the SDK: 1. Checks that the report binds the event to the **correct workflow owner** and that its embedded payload hash matches `keccak256(payload.verifiable_event)`. 2. Recomputes `reportHash = keccak256(keccak256(ocr_report) ‖ ocr_context)`. 3. Recovers the signer address from each signature in `signatures`. 4. Checks that each recovered address belongs to the configured set of valid DON signers. 5. Checks that the count of unique valid signatures meets or exceeds `MinRequiredSignatures` (default **4**). The full algorithm is documented in [Event Verification](/crec/concepts/event-verification). ## Event types CRE Connect emits five kinds of events. All of them share the verifiable envelope above; they differ only in what `payload` contains. | `headers.type` | Source | Typical payload | | ------------------ | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `watcher.event` | A [watcher](/crec/concepts/watchers) observed a matching log on-chain. | The decoded event from the underlying contract (parameters in raw or extension-decoded form), plus the on-chain transaction hash, block number, and confidence level. | | `watcher.status` | The lifecycle state of a watcher changed. | The new `WatcherEventStatus` (`pending`, `active`, `archiving`, `archived`, `failed`, `archive_failed`) and an optional human-readable reason. | | `wallet.status` | The lifecycle state of a Smart Account wallet changed. | The new `WalletEventStatus` (`pending`, `deploying`, `deployed`, `archived`, `failed`) and metadata about the deployment chain. | | `operation.status` | The on-chain Smart Account emitted an `OperationExecuted` log for one of your operations. | The wallet operation ID, the new `OperationStatus`, and, on confirmation statuses, the chain transaction hash and per-transaction execution result. | | `query.status` | A [chain query](/crec/concepts/queries) moved through its lifecycle. | The query ID, query status, target contract, and terminal verifiable result or error. | The full structural definition of each payload is in [Event Types and Payloads](/crec/reference/event-payloads). ## What "verifiable" really means The verifiable property gives you three concrete guarantees: - **Authenticity.** A passing `Verify(event)` proves that an OCR-signed quorum of DON nodes attested to the underlying observation. A malicious or compromised delivery layer cannot forge an event because it does not have the DON's signing keys. - **Integrity.** Any tampering with `payload`, `ocr_report`, `ocr_context`, or `signatures` causes verification to fail. The OCR report binds the event hash, and the signatures bind the report. - **Replay protection.** The OCR context (epoch / round / config hash) is part of the signed message, so signatures are not reusable across rounds or configurations. It does **not** give you: - **Non-repudiation against your tenant.** Other tenants of CRE Connect cannot verify *for whom* an event was generated unless they have access to the same Smart Account / wallet metadata. Verification is about authenticity, not authorization. - **Real-time finality.** Verifiability is independent of the [confidence level](/crec/concepts/confidence-levels) at which the underlying observation was made. A `latest` event can be just as cryptographically valid as a `finalized` one, but the chain can still reorganize it away. Operation confirmations make this visible through [Multi-Event Finality](/crec/concepts/multi-event-finality): `confirmed_latest`, `confirmed_safe`, then `confirmed`. ## Verification configuration The SDK configures the verifier as follows by default: | Setting | Default | Configurable via | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MinRequiredSignatures` | `4` | `crec.WithDONConfig(tenantID, min, signers)` (recommended; sets the whole unit at once) or `crec.WithEventVerification(min, signers)` (deprecated) | | `ValidSigners` | `DefaultValidSigners`: the production DON signer set (10 keys) | `crec.WithDONConfig(...)` or `crec.WithEventVerification(...)` (deprecated) | | Workflow owner | **No default: you must set it.** Verification returns `ErrOrgIDOrWorkflowOwnerReq` if neither `OrgID` nor `WorkflowOwner` is configured. | `crec.WithOrgID(...)` derives the workflow-owner address from your Org ID; `crec.WithWorkflowOwner(...)` sets it directly. Per-call: `VerifyWithOrgID` / `VerifyWithWorkflowOwner`. | | CRE tenant ID | `events.CreMainlineTenantID` (`"1"`): used only when deriving a workflow owner from an Org ID | `crec.WithDONConfig(...)` (recommended) or `crec.WithCRETenantID(...)` (deprecated) | | Verification enabled | `true` | `crec.WithoutEventVerification()` skips the default signer-set backfill; explicitly configured signers still apply. | The DON configuration values (tenant ID, threshold, signer set) are provided at onboarding and are not SDK constants. See [SDK Configuration Options](/crec/reference/sdk-configuration) for the full list. ## Related - [Event Verification](/crec/concepts/event-verification): the algorithm `Events.Verify` runs. - [Verify Event Signatures](/crec/guides/events/verify-signatures): the runnable SDK recipe. - [Event Types and Payloads](/crec/reference/event-payloads): the five payload shapes you'll receive. - [Multi-Event Finality](/crec/concepts/multi-event-finality): how operation confirmations gain stronger finality. - [Confidence Levels](/crec/concepts/confidence-levels): the orthogonal property: chain finality vs cryptographic authenticity. --- # Event Verification Source: https://docs.chain.link/crec/concepts/event-verification Last Updated: 2026-08-31 This page describes exactly what happens when you call the verification helpers on `client.Events` from the CRE Connect Go SDK. The algorithm is implemented in `events/events.go` (`crec-sdk`). Use this page as a conceptual reference, and see the [Verify Event Signatures](/crec/guides/events/verify-signatures) guide for runnable code. ## Entry points, one algorithm The SDK exposes type-specific verifiers; each handles exactly one event family and uses the same underlying algorithm: | Method | Event type accepted | Returns when called on the wrong type | | -------------------------------------------- | ------------------- | ------------------------------------- | | `client.Events.Verify(event)` | `watcher.event` | `ErrOnlyWatcherEventsSupported` | | `client.Events.VerifyOperationStatus(event)` | `operation.status` | `ErrOnlyOperationStatusSupported` | | `client.Events.VerifyQueryStatus(event)` | `query.status` | `ErrOnlyQueryStatusSupported` | Per-call variants (`VerifyWithOrgID`, `VerifyWithWorkflowOwner`, `VerifyOperationStatusWithOrgID`, `VerifyOperationStatusWithWorkflowOwner`, `VerifyQueryStatusWithOrgID`, `VerifyQueryStatusWithWorkflowOwner`) let you override the workflow-owner identity for a single call without rebuilding the client. The `watcher.status` and `wallet.status` event families are not covered by these helpers; for those, use the lower-level `VerifyOCRSignatures(...)` directly (see [Lower-level entry point](#lower-level-entry-point)). ## Inputs Both verifiers require three things from the SDK configuration plus the event itself: - **The event** with its `headers.proofs` populated (exactly one `OCRProof`). - **A workflow owner address.** The SDK derives this either from a configured `OrgID` (passed via `crec.WithOrgID(...)`) or directly from a configured `WorkflowOwner` (`crec.WithWorkflowOwner(...)`). There is no default: at least one of the two must be set. - **A signer set.** The SDK uses `DefaultValidSigners` unless overridden. - **A signature threshold.** The SDK uses `DefaultMinRequiredSignatures = 4` unless overridden. If neither `OrgID` nor `WorkflowOwner` is configured, both verifiers return the sentinel `ErrOrgIDOrWorkflowOwnerReq` on every call. ## Algorithm: step by step ### 1. Type and payload extraction Each verifier first checks `event.Headers.Type` matches the type it accepts (and returns the corresponding sentinel above otherwise). It then extracts the typed payload, `WatcherEventPayload` for `Verify` and `OperationStatusPayload` for `VerifyOperationStatus`, which carries the `verifiable_event` bytes used in step 3. ### 2. Off-Chain Reporting (OCR) proof extraction The proofs array must contain exactly one `OCRProof`. The SDK enforces this with two sentinels: - `ErrNoOCRProofs`: empty proofs array. - `ErrMultipleOCRProofs`: more than one proof present (other proof types are skipped, but multiple OCR proofs are an error). `parseOCRProofData` then hex-decodes both `ocr_report` and `ocr_context` and validates that the report is at least `109 + 32 = 141` bytes long (the offset of the embedded payload hash). ### 3. Local event hash The SDK computes the **local event hash** from the payload received in the envelope: ```text eventHash = keccak256(payload.verifiable_event) ``` This is the application-visible payload, the bytes you would actually consume, turned into a 32-byte digest for comparison against the on-chain-attested hash. ### 4. Bind report, workflow owner, and event hash (`verifyEventHash`) `verifyEventHash` enforces two invariants on the OCR report: | Bytes | Meaning | Check | | -------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `ocr_report[87:107]` | The 20-byte address of the **workflow owner** that produced the report. | Must equal the configured `workflowOwner` (derived from `OrgID` or set directly). | | `ocr_report[109:]` | The 32-byte hash the workflow signed over. | Must equal the locally-computed `eventHash`. | Either check failing aborts verification: the event was either signed by a different workflow or for different bytes than what the SDK observed. ### 5. OCR signature verification (`verifySignatures`) Once the report is bound to the right workflow and payload, the SDK validates the OCR signatures: ```text reportHash = keccak256(keccak256(ocr_report) ‖ ocr_context) ``` For each entry in `proof.signatures`: 1. Decode the hex signature into a 65-byte (`r ‖ s ‖ v`) buffer. 2. Normalize the `v` byte: Ethereum-style values (`27` / `28`) are decremented by `27` to match the `secp256k1` `0` / `1` convention. 3. Recover the public key with `crypto.SigToPub(reportHash, sig)`. 4. Convert the public key to an Ethereum address with `crypto.PubkeyToAddress`. 5. If that address is in the configured **valid signers map** *and* has not already been used in this round, increment the unique signature count. Verification short-circuits as soon as the count reaches `MinRequiredSignatures` (default 4). ### 6. Decision | Result | Meaning | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `(true, nil)` | All checks passed: the event is authentic and untampered. | | `(false, ErrInvalidEventHash)` | Workflow-owner / event-hash binding (`verifyEventHash`) failed: bytes `87:107` did not equal the configured workflow owner, or bytes `109+` did not equal `keccak256(payload.verifiable_event)`. | | `(false, ErrVerifyEvent)` | Lower-level error wrapping the underlying cause (malformed report, bad signature length, signer recovery failure, etc.). | | `(false, ErrVerificationNotConfigured)` | The valid-signers map is empty (configure `crec.WithEventVerification`). | | `(false, ErrOrgIDOrWorkflowOwnerReq)` / `ErrWorkflowOwnerRequired` | Verifier identity context not configured. | | `(false, ErrOnlyWatcherEventsSupported)` / `ErrOnlyOperationStatusSupported` | Wrong helper called for the event type. | | `(false, nil)` | Signatures parsed cleanly but fewer than `MinRequiredSignatures` recovered to known signers. | The full sentinel list is documented in [Error Handling](/crec/reference/error-handling). ## Worked example Below is the conceptual flow when an application receives a `watcher.event` and verifies it. Add the `events` import, `import "github.com/smartcontractkit/crec-sdk/events"`, to use the sentinel errors. ```go polled, _, err := client.Events.Poll(ctx, channelID, nil) if err != nil { /* handle */ } for _, ev := range polled { ok, err := client.Events.Verify(&ev) switch { case errors.Is(err, events.ErrNoOCRProofs): // The event arrived before its OCR proof: re-poll later. continue case err != nil: log.Printf("verification error: %v", err) continue case !ok: log.Printf("event %s failed verification", ev.EventId) continue } handle(ev) } ``` `Verify` and `VerifyOperationStatus` are purely local: they do no network I/O. `Poll` makes a single HTTP call to the events endpoint and surfaces transport errors to the caller without retry, so wrap it in your own retry logic if you need automatic retries. When verification returns `ErrNoOCRProofs` for an event, skip that event with `errors.Is(err, events.ErrNoOCRProofs)` and re-poll on the next cycle. ## Lower-level entry point If you only have an OCR report, an OCR context, and a list of signatures (for example, when bridging events out of one CRE Connect tenant and verifying them in another system), call `VerifyOCRSignatures(ocrReport, ocrContext, signatures)`. This entry point performs steps 5–6 above without the workflow-owner / event-hash binding from step 4. ## Related - [Verifiable Events](/crec/concepts/verifiable-events): what `Verify` actually proves. - [Verify Event Signatures](/crec/guides/events/verify-signatures): the runnable SDK recipe. - [SDK Configuration Options](/crec/reference/sdk-configuration): `WithEventVerification`, `WithOrgID`, `WithWorkflowOwner`. - [Error Handling](/crec/reference/error-handling#events): the verification-related sentinel errors. --- # Confidence Levels Source: https://docs.chain.link/crec/concepts/confidence-levels Last Updated: 2026-08-31 A **confidence level** specifies how deep into a chain's consensus an event must be before CRE Connect emits it as a verifiable event. The trade-off is the same on every chain: lower confidence means lower latency but a higher chance of being reorganized away; higher confidence means longer waits but stronger guarantees. ## The three levels | Level | Semantics | When to use | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | `latest` | The event is included in the most recent block from the chain's perspective. Reorganizations can still drop it. | Read-only dashboards, tail-following loggers, low-stakes notifications. | | `safe` | The event is included in a "safe" block under the chain's pre-finality definition (post-Merge `safe` for Ethereum, finalized-by-fast-track on rollups). Reorgs are very unlikely. | Operational alerting, non-financial side effects. | | `finalized` | The event is in a block the chain treats as economically final. It cannot be reorganized away under honest-majority assumptions. | Anything that triggers irreversible business action: fund flows, regulatory reporting, downstream API calls. | The exact mapping of `safe` and `finalized` depends on the chain. CRE Connect uses the chain's own definition (e.g. Ethereum's `safe` and `finalized` block tags, the equivalent rollup-specific tags). Consult the chain's documentation for precise semantics and the latency each level implies on that chain. ## Where confidence levels live Confidence levels appear in several places in CRE Connect, each for a different purpose: | Location | Field | Purpose | | --------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------- | | Network metadata (returned by `client.ListNetworks(...)`) | `Network.DefaultConfidenceLevel` | The platform-managed default for a network. New watchers on this network start with this level. | | Watcher resource | `Watcher.ConfidenceLevel` | The level the watcher is currently using, returned by `Get` and `List`. | | Wallet resource | `Wallet.ConfidenceLevel` (optional) | The level applied to wallet-related on-chain observations. | Service-backed watchers (those created with `CreateWithService`) carry a confidence level chosen by the extension. Read it back from `Watcher.ConfidenceLevel` after creation. ## Multi-event finality for operations Confidence levels also shape [Operation](/crec/concepts/operations) confirmation. An Operation can produce multiple `operation.status` events as its block matures: `confirmed_latest`, then `confirmed_safe`, then `confirmed`. This progression lets your application react quickly to low-risk updates while still waiting for stronger finality before irreversible actions. See [Multi-Event Finality](/crec/concepts/multi-event-finality) for the operation-specific model. ## What clients can and cannot configure today - **Clients receive the chosen confidence level back in every Watcher and Network response.** Use `client.ListNetworks(...)` to enumerate the per-network defaults at runtime. - **The SDK does not currently send a `confidence_level` field when creating a watcher.** `CreateWithService` and `CreateWithABI` accept the chain selector, address, ABI/service, and event list. Confidence is set server-side based on the network default and the service's own default (when applicable). - **Event polling and search APIs do not accept a confidence-level filter today.** Confidence is a property of the event's source, not a query parameter on the read path. If your integration requires a non-default confidence level for a specific watcher, contact the CRE Connect team. ## How confidence interacts with verification Confidence and [verification](/crec/concepts/event-verification) are independent properties: | Property | Provides | Sensitive to | | ---------------- | ----------------------------------------------------- | --------------- | | **Verification** | Cryptographic authenticity ("the DON observed this") | Tampering | | **Confidence** | Chain-level finality ("the chain has agreed on this") | Reorganizations | A `latest`-confidence event can pass verification and still be invalidated by a chain reorganization. Similarly, a `finalized`-confidence event that fails verification is suspect regardless of chain finality. Critical workflows should require **both**: verification passes *and* confidence ≥ `finalized`. ## Related - [Watchers](/crec/concepts/watchers): the resource that emits events at a chosen confidence level. - [Multi-Event Finality](/crec/concepts/multi-event-finality): how operations report progressive confirmations. - [Verifiable Events](/crec/concepts/verifiable-events): the cryptographic property that is *independent* of confidence. - [Supported Networks](/crec/supported-networks): the per-network defaults exposed via `ListNetworks`. --- # Smart Accounts Source: https://docs.chain.link/crec/concepts/smart-accounts Last Updated: 2026-08-31 A **Smart Account** is the on-chain identity that executes your [Operations](/crec/concepts/operations). Each Smart Account is: - **Tenant-owned.** The Account is provisioned by your CRE Connect tenant and configured with an explicit list of authorized signer keys. - **Per-chain.** A Wallet is created on a specific chain (identified by its CCIP chain selector). To operate on multiple chains, create one Wallet per chain. - **Signature-verifying.** Every Operation submitted to the Account must carry a valid EIP-712 signature from one of the Account's authorized signers. In the SDK and REST API, the resource that represents a Smart Account is called a **Wallet**. The two terms refer to the same thing: a Wallet is the off-chain record, and the Smart Account is its on-chain instantiation. ## Account abstraction model CRE Connect Smart Accounts implement a Chainlink-native account abstraction model. They are **not ERC-4337 accounts**: there is no EntryPoint, UserOperation, paymaster, or bundler in the picture. The model is: - **Account creation** is performed through an on-chain factory contract that deterministically deploys a Smart Account per (tenant, wallet ID) pair. - **Authorization** lives entirely inside the Account contract. It accepts a payload `(operation, signature)` and verifies the EIP-712 signature against its configured signer list before executing. - **Execution** is invoked by the Chainlink DON. A Chainlink-operated writer EOA broadcasts the transaction on the Account's behalf, so on-chain `tx.origin` is the DON writer (not the user). The Account contract authorizes the call by recovering the EIP-712 signer from the supplied signature and checking it against the wallet's allow-list. `msg.sender` is only used to enforce that the call comes from one of the approved Chainlink writer addresses. This keeps the on-chain footprint small (one factory + one Account contract per wallet) and removes the need for any 4337 infrastructure on the chains you operate on. ## Signer model A Wallet's authorization rules are fixed at creation time. You declare them through one of two `WalletType` values: | `WalletType` | Allowed signer field | Signer payload | | ------------ | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | `ecdsa` | `AllowedEcdsaSigners`: `[]string` of EVM addresses | The recovered EIP-712 signer address must be in this list. | | `rsa` | `AllowedRsaSigners`: list of `{ E, N }` RSA public-key components | Used by Smart Accounts that verify RSA-signed payloads (selected workflows / KMS integrations). | A Wallet is **either ECDSA or RSA**, never both. The SDK enforces this at create time: - For `ecdsa`, `AllowedRsaSigners` must be `nil` and `AllowedEcdsaSigners` must contain at least one valid hex address. - For `rsa`, `AllowedEcdsaSigners` must be `nil` and every entry in `AllowedRsaSigners` must have non-empty `E` and `N`. The signer set is fixed once the Wallet is created. There is currently no SDK method to add or remove signers after the fact. To rotate keys, create a new Wallet and migrate. ## Wallet lifecycle A Wallet is provisioned asynchronously: the SDK returns immediately and the on-chain Smart Account is deployed in the background. |
Status
| Meaning | | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `pending` | The Wallet record was created in CRE Connect; on-chain deployment has not started yet. | | `deploying` | The deployment workflow is awaiting on-chain inclusion. | | `deployed` | The on-chain Account is live and able to execute Operations. The deployed address appears in the Wallet response. | | `archived` | The Wallet is read-only; new Operations are rejected. | | `failed` | Deployment failed. The latest `wallet.status` event includes the reason. | Lifecycle transitions are emitted as `wallet.status` events into the channel that owns the Wallet. Subscribe to them to drive your provisioning state machine; see [Create and Manage Wallets](/crec/guides/wallets/create-and-manage). ## Constraints and limits | Setting | Value | Source | | ------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | Wallet name maximum length | 255 characters | `wallets.MaxWalletNameLength`. | | Maximum allowed signers per list (ECDSA *or* RSA) | 10 | `maxItems: 10` on `ECDSASignersList` / `RSASignersList` in the OpenAPI spec. | | Wallet types per Wallet | One (`ecdsa` *xor* `rsa`) | SDK validation. | | Status update channel | Optional `StatusChannelId` at create time | The channel that receives `wallet.status` events. The SDK rejects a zero-UUID value with `wallets.ErrStatusChannelIDZero`. | For a comprehensive list of service-side limits, see [Service Limits](/crec/reference/service-limits). ## Predicting addresses The on-chain Account address is derived deterministically from the factory, the unique account ID (the Wallet's UUID), the initial owner, and config data. The Wallet record exposes the predicted address before deployment finishes, so applications can reference the address (e.g. for funding or RBAC setup) without waiting for the `deployed` event. ## Related - [Operations & Transactions](/crec/concepts/operations) · [EIP-712 Signing](/crec/concepts/eip712-signing): what the Smart Account executes and verifies. - [Create and Manage Wallets](/crec/guides/wallets/create-and-manage) · [Manage Wallet Signers](/crec/guides/wallets/manage-signers): the SDK and REST workflows. - [Account Abstraction & Gas Sponsorship](/crec/concepts/account-abstraction): why these accounts cost no gas to drive. --- # Operations and Transactions Source: https://docs.chain.link/crec/concepts/operations Last Updated: 2026-08-31 An **Operation** is the unit of write in CRE Connect. It packages one or more EVM transactions into a single, atomic, EIP-712-signed batch that a Smart Account executes on behalf of your application without your application managing gas, nonces, or relayers. If any transaction in the batch reverts, the entire Operation reverts. For deferred-signing workflows, see [Draft Operations](/crec/concepts/drafts). For the underlying signing mechanism and the on-chain execution model, see [EIP-712 Signing](/crec/concepts/eip712-signing) and [Smart Accounts](/crec/concepts/smart-accounts). ## Data model An Operation is composed of two structs that live in `transact/types`: ```go type Transaction struct { To common.Address `json:"to"` Value *big.Int `json:"value,string"` Data hexutil.Bytes `json:"data"` } type Operation struct { ID *big.Int `json:"id"` Account common.Address `json:"account"` Deadline *big.Int `json:"deadline"` Transactions []Transaction `json:"transactions"` } ``` | Field | Meaning | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ID` | The **wallet operation ID**. A nonce-like value chosen by the client. When you call `ExecuteTransactions`, the SDK generates a random 128-bit ID; when you construct an Operation manually, you choose the ID yourself. | | `Account` | The Smart Account address (the wallet) that will execute the Operation. This must equal the `verifyingContract` of the EIP-712 domain. | | `Deadline` | A Unix timestamp after which the Operation must not be executed. The DON ignores Operations whose deadline has passed. | | `Transactions` | An ordered list of `Transaction` structs. They execute in order, atomically. | Each `Transaction` is just `(to, value, data)`, exactly what you would pass to `eth_sendTransaction`, except the *sender* is the Smart Account, not the EOA that signed the Operation. ## End-to-end flow ## Signing The SDK signs the Operation client-side using EIP-712 typed data. The domain is fixed across CRE Connect: | Field | Value | | ------------------- | ----------------------------------------------------------------------- | | `name` | `CLLSmartAccount` | | `version` | `1` | | `chainId` | Derived from the `chainSelector` argument via `GetChainIDFromSelector`. | | `verifyingContract` | The Smart Account address (`Operation.Account`). | The signing key can be any implementation of the [`signer.Signer`](/crec/concepts/eip712-signing#signers) interface: local ECDSA, AWS KMS, HashiCorp Vault, Fireblocks, Privy, or your own custom adapter. ## Submission helpers The SDK exposes two entry points on `client.Transact`: ### `ExecuteTransactions` The high-level helper. Builds the Operation, signs it, and submits it in one call. ```go op, err := client.Transact.ExecuteTransactions( ctx, channelID, // uuid.UUID operationSigner, // signer.Signer smartAccountAddress, // common.Address []types.Transaction{tx1, tx2}, big.NewInt(time.Now().Add(15*time.Minute).Unix()), // deadline chainSelector, // string ) ``` `ExecuteTransactions` generates a random 128-bit wallet operation ID, so two calls in the same second never collide. If you construct the Operation manually instead, you own the ID: make it unique per Smart Account, because the Smart Account rejects re-used IDs. ### `ExecuteOperation` Lower-level: takes a fully-formed `*types.Operation` (so you can override `ID` or assemble a complex multi-transaction batch yourself), signs it, and submits it. Use this when you need precise nonce control or want to construct the Operation through extension builders such as the [DTA `Prepare*Operation`](/crec/extensions/dta) helpers. ```go op, err := client.Transact.ExecuteOperation(ctx, channelID, signer, builtOperation, chainSelector) ``` ## Draft operations Operations can also be created without a signature. CRE Connect stores these as **draft operations** in `pending_signature` state until your application finalizes them with a digest and signature, cancels them, or lets their deadline expire. Drafts are useful when the signer is not available synchronously: MPC policy approval, human review, KMS approval workflows, or UIs that show decoded transaction previews before the user signs. See [Draft Operations](/crec/concepts/drafts) for the model and [Draft Operations: Create, Finalize, Cancel](/crec/guides/operations/drafts) for the SDK flow. ## Lifecycle The submitted Operation is observable through the channel's event stream. A signed operation starts at `accepted`; a draft starts at `pending_signature` and must be finalized before relay. From there, CRE Connect reports relay progress, progressive on-chain confirmation, cancellation, expiration, or failure through `operation.status` events. For the complete state diagram and status table, see [Lifecycles](/crec/reference/lifecycles#operation-lifecycle). For the confirmation model, see [Multi-Event Finality](/crec/concepts/multi-event-finality). For polling and event verification patterns, see [Submit and Track Operations](/crec/guides/operations/submit-and-track). Draft lifecycle events (`pending_signature`, `cancelled`, `expired`) are operational notifications without DON proofs. Confirmation events (`confirmed_latest`, `confirmed_safe`, `confirmed`) carry DON proofs and can be verified with `client.Events.VerifyOperationStatus`. ## Atomicity All transactions in a single Operation execute atomically: the Smart Account either executes every transaction successfully, or it reverts the entire batch. This guarantee makes Operations useful for **multi-step on-chain flows**, for example, "approve token + call DTA contract + emit auxiliary log", that would otherwise require careful retry handling on partial failure. If you need *all-or-some* semantics (e.g. several independent token transfers that should succeed independently), submit them as **separate Operations**, one Operation per transaction. See [Batch Transactions](/crec/guides/operations/batch-transactions) for guidance on choosing. ## Related - [EIP-712 Signing](/crec/concepts/eip712-signing): what gets signed and why. - [Smart Accounts](/crec/concepts/smart-accounts): the on-chain executor. - [Account Abstraction & Gas Sponsorship](/crec/concepts/account-abstraction): the gas-less execution model. - [Build and Sign Operations](/crec/guides/operations/build-and-sign) · [Submit and Track Operations](/crec/guides/operations/submit-and-track) · [Batch Transactions](/crec/guides/operations/batch-transactions): the implementation guides. --- # Draft Operations Source: https://docs.chain.link/crec/concepts/drafts Last Updated: 2026-08-31 A **draft operation** is an [Operation](/crec/concepts/operations) created without a signature. CRE Connect stores it in `pending_signature` until your application finalizes it with a digest and signature, cancels it, or lets its deadline expire. Drafts support workflows where the system that builds an Operation cannot sign it immediately. Common examples include MPC approval queues, human review, KMS-based approval, preview-before-sign interfaces, and cancellation before execution. ## Signed operations vs drafts | Capability | Signed operation | Draft operation | | --------------------------------- | -------------------- | --------------------------------------- | | Created with `signature` | Yes | No | | Initial status | `accepted` | `pending_signature` | | Relayed to the DON immediately | Yes | No. CRE Connect waits for finalization. | | Can include transaction previews | No | Yes | | Can be cancelled before execution | No | Yes, while still `pending_signature`. | | Uses the EIP-712 digest | Yes, during signing. | Yes, during finalization. | Use a signed Operation when your signer is available synchronously. Use a draft when another system or person must approve the Operation before CRE Connect relays it. ## Draft lifecycle A draft starts in `pending_signature`. From there, only three outcomes matter: | Status | How it happens | Meaning | | ----------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | `accepted` | `PATCH /channels/{channel_id}/operations/{operation_id}` with `status: "accepted"`, `signature`, and `digest`. | The draft has been finalized and can now follow the normal operation lifecycle. | | `cancelled` | `PATCH /channels/{channel_id}/operations/{operation_id}` with `status: "cancelled"`. | The draft was cancelled before finalization. Terminal. | | `expired` | The operation deadline elapsed before finalization. | The draft can no longer be finalized. Terminal. | See [Lifecycles](/crec/reference/lifecycles#operation-lifecycle) for the complete `OperationStatus` reference. ## Digest and finalization CRE Connect computes the EIP-712 digest when you create the Operation. This is the same 32-byte digest that the SDK computes with `client.Transact.HashOperation(op, chainSelector)`. When you finalize a draft, you send both values: - `digest`: the EIP-712 operation hash. - `signature`: a 65-byte signature over that digest. The digest acts as an integrity check. It binds the signature to the exact Operation that CRE Connect stored as the draft: same wallet operation ID, [Smart Account](/crec/concepts/smart-accounts), deadline, chain, and transactions. ## Deadlines `Operation.Deadline` is part of the EIP-712 payload. You choose it before draft creation, and you cannot change it later without creating and signing a different operation. - `deadline = 0`: no expiration. - `deadline > 0`: Unix timestamp after which the operation can no longer execute. For drafts, the deadline controls how long the operation can wait for signature. If the deadline passes before finalization, the operation moves to `expired`. A finalize attempt near or after the deadline fails with `OPERATION_DEADLINE_ELAPSED`. ## Transaction previews Drafts can include optional transaction previews. A preview captures decoded calldata metadata for a transaction, such as the function signature and UI-friendly metadata your application wants to show to an approver. Previews help teams build approval screens that say what the user is about to sign instead of only showing raw calldata. They do not change the signed EIP-712 operation. The signed payload remains the operation fields: ID, account, deadline, and transactions. ## Events and verification Draft lifecycle events are operational notifications, not DON-verified attestations. Events for `pending_signature`, `cancelled`, and `expired` do not carry OCR proofs. After a draft reaches `accepted`, it follows the normal operation path. On-chain confirmation events, such as `confirmed`, carry DON proofs and can be verified with `client.Events.VerifyOperationStatus`. ## Wallet operation ID reuse The `wallet_operation_id` must be unique for active operations on the same wallet and chain. After a draft reaches a terminal state such as `cancelled` or `expired`, you can reuse that ID for a new operation. In most integrations, a fresh ID is simpler and safer. Reuse mainly helps approval systems that want to preserve a business reference after a user cancels and recreates a draft. ## Common use cases | Use case | Draft flow | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | MPC or policy approval | Create a draft, route the digest to Fireblocks, Privy, KMS, or another approval system, then finalize with the returned signature. | | Human review | Create a draft with previews, show the decoded transaction intent, then finalize or cancel based on the approver's decision. | | Preview before signing | Store transaction preview metadata with the draft so the UI can render what the signer will approve. | | Cancel before execution | Cancel a `pending_signature` draft before it becomes executable. | ## Related - [Draft Operations: Create, Finalize, Cancel](/crec/guides/operations/drafts): step-by-step SDK and REST flow. - [Operations and Transactions](/crec/concepts/operations): the operation data model. - [EIP-712 Signing](/crec/concepts/eip712-signing): how CRE Connect computes and signs operation digests. - [Lifecycles](/crec/reference/lifecycles#operation-lifecycle): complete operation status reference. --- # Multi-Event Finality Source: https://docs.chain.link/crec/concepts/multi-event-finality Last Updated: 2026-08-31 **Multi-event finality** is how CRE Connect reports an [Operation](/crec/concepts/operations)'s on-chain confirmation as the underlying block becomes harder to reorganize. Instead of treating confirmation as a single moment, CRE Connect can emit multiple `operation.status` events for the same Operation: | Status | Meaning | Example use cases | | ------------------ | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | | `confirmed_latest` | The Operation appeared in the latest observed block. The block can still be reorganized. | Read-only dashboards, low-stakes notifications, and fast UX updates. | | `confirmed_safe` | The Operation appeared in a block the chain considers safe. A reorganization is very unlikely. | Operational alerting and reversible downstream side effects. | | `confirmed` | The Operation appeared in a finalized block. The chain cannot reorganize it under its normal finality rules. | Irreversible business actions, settlement, and compliance workflows. | These examples are guidance, not policy. Your application owns the decision about which finality level is appropriate for each action. These statuses are part of the public `OperationStatus` enum. See [Lifecycles](/crec/reference/lifecycles#operation-lifecycle) for the complete operation state machine. ## Why operations can emit multiple status events An Operation completes on chain when the [Smart Account](/crec/concepts/smart-accounts) emits `OperationExecuted`. That same on-chain event can be observed at different [confidence levels](/crec/concepts/confidence-levels) as the block matures. CRE Connect reports those observations as progressive confirmation statuses. Your application might first see `confirmed_latest`, then `confirmed_safe`, then `confirmed` for the same operation. Each event gives you a stronger finality guarantee than the previous one. ## Statuses only move forward Treat the confirmation statuses as a ladder: `confirmed_latest` to `confirmed_safe` to `confirmed`. Each step represents a stronger finality guarantee for the same on-chain Operation. Once an Operation reaches a stronger confirmation status, it does not return to a weaker one. ## Verification and finality are separate `confirmed_latest`, `confirmed_safe`, and `confirmed` operation status events are DON-verified. You can verify them with `client.Events.VerifyOperationStatus`. Verification proves that the DON signed the observation. Finality describes how stable the underlying chain block is. A `confirmed_latest` event can pass cryptographic verification and still be affected by a chain reorganization. A `confirmed` event provides the strongest chain-level guarantee. ## Choosing which status to wait for Choose the status based on what your application will do next: | Application behavior | Recommended status | | ----------------------------------- | ------------------ | | Show progress in a UI | `confirmed_latest` | | Trigger reversible operations | `confirmed_safe` | | Trigger irreversible business logic | `confirmed` | Some networks may not emit every confirmation status. Testnets often favor faster feedback, while mainnets can support stronger finality stages. Use the statuses your channel actually receives on the target network, and design your waiting logic with a timeout. ## Related - [Lifecycles](/crec/reference/lifecycles#operation-lifecycle): complete operation state machine and status enum. - [Confidence Levels](/crec/concepts/confidence-levels): how `latest`, `safe`, and `finalized` differ. - [Submit and Track Operations](/crec/guides/operations/submit-and-track): polling and event patterns for operations. - [Event Verification](/crec/concepts/event-verification): how to verify `operation.status` events. --- # EIP-712 Signing Source: https://docs.chain.link/crec/concepts/eip712-signing Last Updated: 2026-08-31 CRE Connect authorizes every [Operation](/crec/concepts/operations) with an **EIP-712 typed-data signature**. The [Smart Account](/crec/concepts/smart-accounts) verifies the signature on-chain before any transaction in the Operation runs. With EIP-712, wallet UIs and key-management systems can show a human-readable approval prompt instead of an opaque hash. ## Domain The EIP-712 domain is constant across CRE Connect: | Field | Value | Source | | ------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------- | | `name` | `CLLSmartAccount` | `transact/types` constant `EIP712DomainName`. | | `version` | `1` | `transact/types` constant `EIP712DomainVersion`. | | `chainId` | The numeric chain ID, derived from the **CCIP chain selector** passed to the SDK. | Resolved with `GetChainIDFromSelector(selector)`. EVM-only. | | `verifyingContract` | The Smart Account address (`Operation.Account`). | Built by `SmartAccountEIP712Domain(chainId, account)`. | Because `verifyingContract` is the Smart Account itself, two wallets on the same chain produce *different* domain hashes. A signature for wallet A is unusable on wallet B even if both contracts speak the same protocol. This is what makes the EIP-712 binding *per-account*. ## Typed-data schema The primary type is `Operation`. It references one dependent type, `Transaction`: ```text Operation { id uint256 account address deadline uint256 transactions Transaction[] } Transaction { to address value uint256 data bytes } ``` These are the same fields documented in [Operations](/crec/concepts/operations). EIP-712 hashing follows the standard rules: - `bytes` and `string` fields are hashed with `keccak256` before being included. - Dynamic arrays are hashed by concatenating the hashes of each element and `keccak256`-ing the result. - The struct hash is `keccak256(typeHash ‖ encoded fields)`. - The final digest is `keccak256("\x19\x01" ‖ domainSeparator ‖ structHash)`. The SDK builds this digest with `go-ethereum`'s `apitypes.TypedDataAndHash`, so the bytes are bit-for-bit compatible with any EIP-712 implementation in Solidity, Ethers, viem, or rust-ethereum. ## Hashing pipeline (in code) Most application code does not call the `Handler` directly. Use the high-level entry points on the unified client: - `client.Transact.HashOperation(op, chainSelector)`: returns the 32-byte digest without signing. Use this for [draft operations](/crec/concepts/drafts) and external approval workflows. - `client.Transact.SignOperation(ctx, op, signer, chainSelector)`: returns the 32-byte digest and the signature. - `client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector)`: signs and submits in one call. - `client.Transact.ExecuteTransactions(ctx, channelID, signer, executorAccount, txs, deadline, chainSelector)`: convenience wrapper that builds the `Operation` for you. Draft operations use the same digest. In the draft flow, CRE Connect stores the unsigned operation first, then your application sends the digest and signature when it finalizes the draft. The lower-level handler is exposed in `transact/eip712` if you need the digest without signing or want to drive the pipeline yourself: - `eip712.Handler.HashOperation(op, chainSelector)`: returns the 32-byte digest, no signing. - `eip712.Handler.SignOperation(ctx, op, signer, chainSelector)`: computes the digest *and* asks the supplied `Signer` to sign it. ## Signers A `signer.Signer` is anything that produces a 65-byte ECDSA signature over a 32-byte hash. The interface lives in `github.com/smartcontractkit/crec-sdk/transact/signer`: ```go import "github.com/smartcontractkit/crec-sdk/transact/signer" type Signer interface { Sign(ctx context.Context, hash []byte) ([]byte, error) } ``` Some signer adapters (for example, Fireblocks) also implement `signer.TypedDataSigner`, which lets the adapter render a typed-data prompt to a human approver: ```go type TypedDataSigner interface { SignTypedData(ctx context.Context, typedData *TypedData) ([]byte, error) } ``` The SDK ships five built-in adapters and supports any custom implementation: | Adapter | Package | Notes | | ------------------- | ---------------------------- | ------------------------------------------------------- | | **Local (ECDSA)** | `transact/signer/local` | In-process key. Recommended for local development only. | | **AWS KMS** | `transact/signer/kms` | KMS-managed `ECC_SECG_P256K1` key. | | **HashiCorp Vault** | `transact/signer/vault` | Vault Transit secrets engine, ECDSA `secp256k1` key. | | **Fireblocks** | `transact/signer/fireblocks` | Implements both `Signer` and `TypedDataSigner`. | | **Privy** | `transact/signer/privy` | Privy wallet via REST API. | | **Custom** | any package | Any type implementing the `Signer` interface. | See [Signers](/crec/guides/signers/local) for runnable setup guides. ## Authorization vs execution EIP-712 signing only **authorizes** the Operation. It does not pay gas, and it does not put the Operation on-chain by itself. Execution still goes through the Chainlink DON, which signs and broadcasts the underlying transaction on your Smart Account's behalf (see [Account Abstraction & Gas Sponsorship](/crec/concepts/account-abstraction)). This separation has two practical consequences: 1. **The signing key never holds gas.** A KMS- or Vault-managed key with no on-chain presence is sufficient. 2. **The on-chain `tx.origin` is the DON's writer, not the user.** Authorization is anchored on the EIP-712-recovered signer address compared against the wallet's allow-list, not on the message sender. See [Smart Accounts](/crec/concepts/smart-accounts) for the contract-level model. ## Related - [Operations & Transactions](/crec/concepts/operations): what gets signed. - [Draft Operations](/crec/concepts/drafts): deferred signing with the same EIP-712 digest. - [Build and Sign Operations](/crec/guides/operations/build-and-sign): the SDK call flow. - [Signing Transparency](/crec/guides/operations/signing-transparency): render the typed data for human approvers. - [Smart Accounts](/crec/concepts/smart-accounts): the on-chain verifier of the signature. - [EIP-712: Typed structured data hashing and signing](https://eips.ethereum.org/EIPS/eip-712): the canonical Ethereum specification. --- # Account Abstraction and Gas Sponsorship Source: https://docs.chain.link/crec/concepts/account-abstraction Last Updated: 2026-08-31 CRE Connect [Operations](/crec/concepts/operations) are **gas-less** from the application's point of view. Your code signs an Operation with a key that holds no on-chain balance, hands it to the SDK, and the on-chain transaction lands without your code paying gas. This page explains the mechanics behind that experience and where it differs from ERC-4337 account abstraction. ## What "gas-less" means here When you call `client.Transact.ExecuteTransactions(...)`: - The signing key (local ECDSA, KMS, Vault, Fireblocks, Privy, or custom) only signs an EIP-712 hash. It is never asked to broadcast a transaction. - The Operation is POSTed over HTTPS to the CRE Connect API: no gas, no nonce, no chain RPC. - A CRE workflow on the Chainlink DON picks up the Operation, signs the underlying transaction with the DON's keys, and broadcasts it on-chain. - The DON's writer EOA is the on-chain `tx.origin` and pays the gas. Your application's signing key never appears in `tx.origin`. From your application's perspective, executing an Operation is a single SDK call that returns immediately with a tracking ID, and `operation.status` events flow back as the chain transaction is included and confirmed. See [Multi-Event Finality](/crec/concepts/multi-event-finality). ## End-to-end flow Gas is paid by the DON's writer, not by your application's signer. The signer's only role is producing the EIP-712 signature. ## Why this is "account abstraction" Account abstraction is the property that the *authorizer* of an Operation does not have to be the *payer* of gas. CRE Connect's model achieves this by: 1. **Authorization is decoupled from gas via EIP-712.** The Smart Account verifies that the EIP-712 signature was produced by an address in its `AllowedEcdsaSigners` (or RSA equivalent) allow-list. That address holds no ETH and is never `tx.origin`. 2. **Execution is delegated to the DON.** The DON signs and broadcasts the on-chain transaction itself, paying gas from its own writer EOA. The Smart Account treats the DON-attested call as sufficient permission to execute the Operation atomically. This is functionally equivalent to ERC-4337's "User signs, paymaster pays" model, but the implementation is Chainlink-native and does not depend on EntryPoint, UserOperation, or bundlers. See the comparison below. ## How this differs from ERC-4337 | Concept | ERC-4337 | CRE Connect | | ------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- | | Account model | UserOperation queue → EntryPoint → Account | EIP-712 Operation → Chainlink DON → Smart Account | | Authorization | Account-defined `validateUserOp` | Account-defined EIP-712 signer allow-list (ECDSA or RSA) | | Gas sponsorship | Paymaster contract or self-funded | Chainlink DON pays gas from its own writer EOA | | Bundler | Off-chain bundler builds UserOp batches | The DON itself signs and broadcasts each operation | | Network requirement | Any EVM chain with deployed EntryPoint | Any EVM chain supported by CRE (see [Supported Networks](/crec/supported-networks)) | | Signing format | Account-defined | EIP-712, fixed `CLLSmartAccount` domain | The two models solve the same problem with different infrastructure. CRE Connect's model is a fit when you also want verifiable inbound events from the same DON that executes your outbound operations, which is the typical CREC integration pattern. ## What gas sponsorship covers | Sponsored | Not sponsored | | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Gas for the on-chain transaction the DON broadcasts. | Funding the `value` field of any `Transaction` in the Operation: the Smart Account itself must hold enough native token. | | Gas for the Smart Account's `OperationExecuted` log. | Tokens transferred or approved by the Operation's transactions. | | Gas for the on-chain status read that triggers your `operation.status` event. | Off-chain compute (the application's own infrastructure). | Practically: if your Operation needs to send 1 ETH to a counterparty, the Smart Account needs to hold 1 ETH. Gas for that send is sponsored by the DON. ## Related - [Operations & Transactions](/crec/concepts/operations): the unit of write that this gas-less model executes. - [Smart Accounts](/crec/concepts/smart-accounts): the on-chain identity that pays no gas. - [Build and Sign Operations](/crec/guides/operations/build-and-sign): turn the model into runnable code. --- # Chain Queries Source: https://docs.chain.link/crec/concepts/queries Last Updated: 2026-08-26 A **chain query** is a one-shot, DON-backed, verifiable blockchain read. You submit a query to CRE Connect, the Chainlink DON executes it against a target block, and you receive a cryptographically signed result that you can verify end-to-end off-chain. Queries are [channel](/crec/concepts/channels)-scoped and asynchronous: creation returns `202 Accepted` immediately, and the result arrives via polling or as a `query.status` event on the channel's event stream. ## `evm_call` query kind The only supported query kind today is **`evm_call`**: a read-only EVM `eth_call` against a specified block. The DON executes the call and returns the raw ABI-encoded return bytes. No state is modified on chain. ## Block selection Every query specifies a **block selection** that determines which block the call executes against: | Selector | Description | | -------------- | ------------------------------------------------------------ | | `latest` | Resolve to the latest block before executing. | | `finalized` | Resolve to the finalized block before executing. | | `block_number` | Execute against an explicit block number (decimal `uint64`). | The **resolved block** (block number, block hash, block timestamp) is included in the verifiable result, proving which block was actually read. This means you can verify not just *what* the DON read, but *when* it read it. ## Query lifecycle Queries move through a bounded state machine: | State | Description | Terminal? | | ----------- | -------------------------------------------------------------- | --------- | | `accepted` | Query created and persisted; job enqueued for dispatch. | No | | `sending` | Dispatch worker is actively sending to the CRE gateway. | No | | `sent` | Successfully dispatched to CRE gateway; awaiting DON callback. | No | | `completed` | DON returned a successful result with OCR proof. | Yes | | `failed` | DON returned an error, or dispatch failed permanently. | Yes | | `expired` | TTL elapsed before a terminal callback arrived. | Yes | The default TTL is **5 minutes**. If no terminal callback arrives within this window, the query transitions to `expired`. See [Lifecycles](/crec/reference/lifecycles#query-lifecycle) for the full state diagram. ## How results are delivered There are two complementary paths: 1. **Poll the query resource**: call `GET /channels/{channel_id}/queries/{query_id}` (or use `client.Queries.Wait`) until the query reaches a terminal status. This is the primary SDK path. 2. **Channel events**: terminal query results are emitted as `query.status` events on the channel's event stream. Search or poll for them with `client.Events.SearchEvents`, filtered by `type=query.status`. Both paths carry the same data: the `verifiable_result`, `event_hash`, and OCR `proof`. ## The verifiable result When a query reaches `completed` or `failed`, the result includes a base64-encoded `verifiable_result` string. Decoding it yields a `ChainQueryVerifiableEvent` envelope: | Field | Description | | -------------------------------- | ----------------------------------------------------------------------------- | | `service` | Always `"_crec"`. | | `name` | Always `"ChainQuery"`. | | `chain_selector` | The chain the query was executed on. | | `timestamp` | When the terminal result was produced. | | `data.query_id` | UUID of the query. | | `data.channel_id` | UUID of the owning channel. | | `data.query_kind` | The query kind (`evm_call`). | | `data.target` | The EVM call target: `from_address`, `contract_address`, `call_data`. | | `data.block_selection.requested` | The original block selector you chose. | | `data.block_selection.resolved` | The concrete block metadata: `block_number`, `block_hash`, `block_timestamp`. | | `data.result` | Present on success: `raw_return_data` (0x-prefixed ABI-encoded bytes). | | `data.error` | Present on failure: `code`, `message`, and optional `raw_revert_data`. | Exactly one of `data.result` or `data.error` is present on a terminal result. ## Verification Terminal `query.status` events carry OCR proofs and can be verified with `client.Events.VerifyQueryStatus`. The verification algorithm is the same as for `watcher.event` and `operation.status`, with one difference: the event hash is computed as `Keccak256(verifiable_result)` instead of `Keccak256(verifiable_event)`. See [Event Verification](/crec/concepts/event-verification) for the full algorithm. ## Idempotency keys Every query create requires an **idempotency key**. Keys are scoped to `(org_id, channel_id, idempotency_key)`: - **Same key, same request** → the original query is returned (idempotent success). - **Same key, different request** → `409 Conflict` with `IDEMPOTENCY_KEY_MISMATCH`. Use a deterministic, unique-per-logical-request key (e.g. `"balance-check-eth-2026-08-26-001"`) so that retries after network errors don't create duplicate queries. ## Queries vs watchers Both queries and [watchers](/crec/concepts/watchers) are channel-scoped, DON-backed chain reads, but they serve different purposes: | Aspect | Queries | Watchers | | --------------- | ----------------------------------------------------------------- | -------------------------------------------------- | | Purpose | One-shot on-demand read | Persistent event subscription | | Execution | Single call, returns a result | Continuous monitoring, emits events | | Lifecycle | `accepted` → … → `completed` / `failed` / `expired` (TTL-bounded) | `pending` → `active` → `archived` (no TTL) | | TTL | 5 minutes default | No expiry | | Block selection | Explicit (`latest` / `finalized` / `block_number`) | Confidence level (`latest` / `safe` / `finalized`) | | Idempotency | Required (`idempotency_key`) | Not applicable | | Result | Single verifiable result with OCR proof | Stream of verifiable events with OCR proofs | A query asks "what is the value of X at block Y?" and gets a single signed answer. A watcher asks "tell me whenever event X happens" and receives a stream of signed events over time. ## Error codes When a query reaches `failed`, the `data.error` object in the verifiable result carries a machine-readable error code: | Code | Meaning | | ------------------------------- | --------------------------------------------------------------- | | `CRE_GATEWAY_REJECTED` | CRE gateway rejected the query (e.g. 4xx). | | `CONTRACT_NOT_FOUND` | The target contract address does not exist on chain. | | `CALL_REVERTED` | The EVM call reverted; `raw_revert_data` contains revert bytes. | | `CHAIN_UNAVAILABLE` | The target chain was unreachable during execution. | | `BLOCK_SELECTION_NOT_AVAILABLE` | The requested block is not available. | | `CRE_WORKFLOW_FAILED` | The CRE chain-query workflow itself failed. | | `QUERY_EXPIRED` | Query expired before a terminal callback arrived. | | `INTERNAL_ERROR` | Unexpected internal error. | See [Error Handling](/crec/reference/error-handling) for the full sentinel error catalog. ## Related - [Execute a Chain Query](/crec/guides/queries/execute-a-query): step-by-step guide with code examples. - [Watchers](/crec/concepts/watchers): the persistent alternative for event subscriptions. - [Event Verification](/crec/concepts/event-verification): the cryptographic verification algorithm. - [Lifecycles](/crec/reference/lifecycles#query-lifecycle): the full query state machine. --- # Extensions Source: https://docs.chain.link/crec/concepts/extensions Last Updated: 2026-08-31 An **extension** is an optional Go module that layers protocol-specific knowledge on top of the core CRE Connect SDK. Extensions ship three things: 1. **Typed Operation builders.** A `PrepareOperation(...)` function for every supported on-chain action, with strongly-typed Go arguments. The builder returns a fully-formed `*types.Operation` ready for `client.Transact.ExecuteOperation`. 2. **One-call watcher provisioning.** A registered service name plus the contract ABIs required to monitor a class of contracts. Watchers created with `CreateWithService(..., Service: "")` use these. 3. **Decoded event types.** Strongly-typed Go structs for each event the watcher emits, so application code does not need to write ABI-decoding glue. The currently available extension is **DTA (Digital Transfer Agent)**, distributed as `github.com/smartcontractkit/crec-sdk-ext-dta`. Its repository uses contract versioning to expose multiple ABI versions (`/v1`, `/v2`, …) under the same Go module. The CRE Connect documentation focuses on the `/v2` import path. ## Why use an extension Without an extension you can still do anything CREC supports: call any contract through `Operation` + `Transaction`, and monitor any event through `CreateWithABI`. Extensions exist to remove the per-protocol boilerplate: | Without an extension | With an extension | | ---------------------------------------- | ---------------------------------------------------------------------------------- | | Hand-craft calldata via `abi.Pack` | One typed Go call per on-chain action | | Supply the contract ABI to every watcher | Reference the service by name (`"dta.v2"`) | | Decode raw event topics yourself | Receive a typed Go struct (e.g. `RedemptionRequested{ Shares, ReferenceID, ... }`) | | Track ABI revisions per protocol upgrade | Bump the extension's Go module version | An extension is, at its heart, a vendored copy of "everything you would have written by hand" for a protocol, maintained alongside the protocol's own contracts so it stays accurate. ## Anatomy of an extension Every CREC extension exposes the same shape: ### `operations.Extension` A small client wired at construction time: ```go import ( "github.com/smartcontractkit/crec-sdk-ext-dta/v2/operations" ) dta, err := operations.New(&operations.Options{ AccountAddress: smartAccountAddress.Hex(), // hex string DTARequestManagementAddress: dtaManagementAddress.Hex(), DTARequestSettlementAddress: dtaSettlementAddress.Hex(), }) ``` `dta` exposes one `PrepareOperation(...)` per on-chain method. Each builder ABI-encodes the call, wraps it in a `Transaction`, and returns a `*types.Operation` with `Account` set to the configured `AccountAddress`. ### `watcher/bundle` The `watcher/bundle` package declares the service name and its supported events: ```go import bundle "github.com/smartcontractkit/crec-sdk-ext-dta/v2/watcher/bundle" b := bundle.Get() // b.Service == "dta.v2" ``` To create a watcher backed by the service, pass `Service: "dta.v2"` to `client.Watchers.CreateWithService(...)` along with the address of the contract you want to observe and the list of event names you care about. The service descriptor is consumed by the CRE Connect backend; applications do not pass it to `crec.NewClient`. ### `events` package and `DecodeFromEvent` The `events` package contains one Go struct per event the service emits, for example `SubscriptionRequested`, `RedemptionRequested`, `DistributorRequestProcessing`. Each struct has typed fields matching the on-chain event signature. `dtav2.DecodeFromEvent(ctx, ev)` (root of `crec-sdk-ext-dta/v2`) dispatches a verifiable event to the correct typed struct, returning a `DecodedEvent` whose `ConcreteEvent` field is the matching event struct. ## Combining extension calls with raw transactions Because extension builders return a `*types.Operation` whose `Transactions` field is just a list, you can append extra `Transaction` entries to the same Operation before submitting: ```go op, err := ext.PrepareRequestSubscriptionWithTokenApprovalOperation(/* ... */) if err != nil { /* ... */ } op.Transactions = append(op.Transactions, types.Transaction{ To: auxLogger, Value: big.NewInt(0), Data: myAuxCalldata, }) resp, err := client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector) ``` The whole composed Operation still executes atomically. ## Available extensions | Extension | Module | Documented import path | | ------------------------------- | ---------------------------------------------- | ---------------------- | | **DTA: Digital Transfer Agent** | `github.com/smartcontractkit/crec-sdk-ext-dta` | `/v2` | The DTA extension repository uses contract versioning (`/v1`, `/v2`, …) to expose multiple deployed contract ABIs side-by-side. The CRE Connect documentation focuses on the `/v2` import path; if you operate against v1 contracts, import the `/v1` sub-package and consult the extension [README](https://github.com/smartcontractkit/crec-sdk-ext-dta#readme) for v1-specific operation signatures. See the [DTA Extension](/crec/extensions/dta) guides for the full v2 surface. ## Related - [Extensions overview](/crec/extensions): the catalog of available extensions. - [DTA v2](/crec/extensions/dta): the first GA extension. - [Watchers](/crec/concepts/watchers): `CreateWithService` is how an extension's pre-packaged watcher is provisioned. --- # Create and Manage Channels Source: https://docs.chain.link/crec/guides/channels/manage-channels Last Updated: 2026-08-31 A **channel** is a logical container for the watchers, events, and operations that belong to one application or environment. Most teams create a channel per application (and per environment within that application). This guide covers the four channel-management operations exposed by the SDK and the [Channels page](https://app.chain.link/cre-connect/channels) in the CRE Connect UI: **create**, **list/filter**, **update**, and **archive**. ## Create a channel ## List and filter channels ## Update channel metadata ## Archive a channel ## Naming constraints Channel names must be **unique within your organisation** (verified by the SDK package documentation in `crec-sdk/channels`). Pick a naming scheme that encodes the channel's purpose and environment so collisions are obvious before you call `Create`. The `Description` field is free-form text up to 255 characters and is a useful place for ownership / on-call context. ## Next steps - Provision your first watcher: [Create a Watcher with a Predefined Service](/crec/guides/watchers/create-with-service). - Stand up a Smart Account: [Create and Manage Wallets](/crec/guides/wallets/create-and-manage). - Read the [Channels concept page](/crec/concepts/channels) for the design rationale. --- # Create a Watcher with a Predefined Service Source: https://docs.chain.link/crec/guides/watchers/create-with-service Last Updated: 2026-08-31 When the contract you want to monitor is supported by a published CRE Connect service (for example `dta.v2`), use **`watchers.Client.CreateWithService`** instead of `CreateWithABI`. The service ships everything CRE Connect needs to monitor that contract: the ABIs, the event schemas, and the underlying processing pipeline. You only supply the chain selector, address, the event names you want to subscribe to, and (optionally) a `service_config` map. ## When to use this | You should use `CreateWithService` if... | Otherwise use [`CreateWithABI`](/crec/guides/watchers/create-with-abi) | | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | The contract is covered by a CRE Connect extension. | The contract is custom or not yet supported by an extension. | | You want typed event payloads via the extension's `events`/`decode` packages. | You are happy decoding the raw event payload with `Client.Decode`. | | You want service-managed defaults (confidence level, polling cadence). | You need an ad-hoc watcher with no protocol-specific behavior. | ## Procedure ## Wait for the watcher to become active A service-based watcher is "active" once CRE Connect has deployed and confirmed it healthy. Use `WaitForActive` to block until the transition completes (or fails): ```go active, err := client.Watchers.WaitForActive(ctx, channelID, w.WatcherId, 2*time.Minute) switch { case errors.Is(err, watchers.ErrWaitForActiveTimeout): log.Fatal("watcher took too long to deploy") case errors.Is(err, watchers.ErrWatcherDeploymentFailed): log.Fatal("watcher failed to deploy: check service / address") case err != nil: log.Fatal(err) } fmt.Println(active.Status) // -> active ``` `WaitForActive` polls every `Options.PollInterval` (default 2 s; configurable via [`crec.WithWatcherPolling`](/crec/reference/sdk-configuration)) and tolerates `404` responses for `Options.EventualConsistencyWindow` (default 2 s) to absorb propagation lag right after creation. ## What `service_config` accepts Every service defines its own config schema. The DTA v2 service accepts an empty config in the typical case; specialized scenarios (custom polling cadence, alternative confidence level, additional indexer hints) are covered in the extension's own README. Pass `nil` (Go) or omit the field (REST) when in doubt. ## Next steps - [Manage Watcher Lifecycle](/crec/guides/watchers/manage-lifecycle): list, update, archive, and inspect status. - [DTA Subscriptions and Redemptions](/crec/extensions/dta/subscriptions-redemptions): typed examples using the same `dta.v2` service. - [Verifiable Events](/crec/concepts/verifiable-events): what the watcher actually emits. --- # Create a Watcher with a Custom ABI Source: https://docs.chain.link/crec/guides/watchers/create-with-abi Last Updated: 2026-08-31 For contracts not covered by a [CRE Connect extension](/crec/concepts/extensions), use **`watchers.Client.CreateWithABI`**. You provide the event ABI fragments yourself and CRE Connect provisions a generic listener for that contract. ## When to use this | You should use `CreateWithABI` if... | Otherwise use [`CreateWithService`](/crec/guides/watchers/create-with-service) | | --------------------------------------------------------- | ------------------------------------------------------------------------------ | | The contract is custom or no published service covers it. | A published extension covers your protocol (e.g. `dta.v2`). | | You only need a subset of events from the ABI. | You want service-managed defaults and typed decoders. | ## Procedure ## Validation rules The SDK fails fast if the request is malformed: | Sentinel error | Cause | | ----------------------------------- | --------------------------------------------------------------------------- | | `watchers.ErrChannelIDRequired` | The channel UUID is `uuid.Nil`. | | `watchers.ErrChainSelectorRequired` | `ChainSelector` is empty or `"0"`. | | `watchers.ErrAddressRequired` | `Address` is empty. | | `watchers.ErrEventsRequired` | `Events` is empty. | | `watchers.ErrABIRequired` | `ABI` is empty. | | `watchers.ErrInvalidABIType` | An entry has `Type != "event"`. The CREC API only accepts event ABIs today. | | `watchers.ErrEventNotInABI` | One of `Events` is not declared in `ABI`. | | `watchers.ErrWatcherNameTooShort` | Name is shorter than 4 characters after trim. | These checks happen entirely client-side, so a failure does not consume API quota. ## Wait for active ```go active, err := client.Watchers.WaitForActive(ctx, channelID, w.WatcherId, 2*time.Minute) if err != nil { return err } fmt.Println(active.Status) // -> active ``` The SDK polls every `Options.PollInterval` (default 2 s) and tolerates `404` for `Options.EventualConsistencyWindow` (default 2 s) immediately after creation. See [SDK Configuration](/crec/reference/sdk-configuration) to tune both. ## Limitations - Only event ABIs are supported today. Function ABIs return `watchers.ErrInvalidABIType`. - Anonymous events are accepted (`Anonymous: true` field is preserved) but are uncommon and difficult to filter on later. - The `Inputs` you pass must match the on-chain event signature exactly, including parameter names, for the watcher to decode payloads correctly. ## Next steps - [Poll and Search Events](/crec/guides/events/poll-and-search): consume what the watcher emits. - [Verify Event Signatures](/crec/guides/events/verify-signatures): cryptographically authenticate every emitted event. - [Manage Watcher Lifecycle](/crec/guides/watchers/manage-lifecycle): list, update, and archive. --- # Manage Watcher Lifecycle Source: https://docs.chain.link/crec/guides/watchers/manage-lifecycle Last Updated: 2026-08-31 Once a watcher is created (see [Create with Service](/crec/guides/watchers/create-with-service) or [Create with ABI](/crec/guides/watchers/create-with-abi)), you can list, filter, rename, and archive it. ## Status reference A watcher moves through these states: The entity statuses surfaced by the REST API are `pending`, `active`, `archiving`, `archived`, and `failed` (the API maps internal `archive_failed` to `failed` on the entity response). The richer `archive_failed` state is visible on the `watcher.status` event payload (`apiClient.WatcherEventStatus`). ## List and filter ## Get a single watcher ```go w, err := client.Watchers.Get(ctx, channelID, watcherID) if errors.Is(err, watchers.ErrWatcherNotFound) { // 404: surface to caller } ``` The watcher's `Status`, `ChainSelector`, `Address`, and (for service watchers) `Service` are all returned in the response. The `StatusReason` is **not** part of the entity response; subscribe to `watcher.status` events on the channel to receive the human-readable reason whenever the status changes. ## Update metadata (name only) The SDK only supports updating the watcher **name**. Address, ABI, service, and chain selector are immutable; create a new watcher to change them. ## Archive Archiving is **asynchronous**. The PATCH returns HTTP **202** with the watcher in `archiving`; you must poll until it reaches `archived` or `archive_failed`. ## Handling failure states | Sentinel error | Returned by | Recovery | | ------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------- | | `watchers.ErrWatcherDeploymentFailed` | `WaitForActive` | Archive, then re-create; check service config and address. | | `watchers.ErrWatcherIsArchiving` | `WaitForActive` | Someone archived concurrently; wait for `WaitForArchived`. | | `watchers.ErrWatcherAlreadyArchived` | `WaitForActive` | Archived watchers cannot return to `active`; create a new one. | | `watchers.ErrWatcherArchiveFailed` | `WaitForArchived` | Subscribe to the `watcher.status` event on the channel to read the `StatusReason`, then retry the archive once. | ## Transient vs. permanent errors `WaitForActive` and `WaitForArchived` both invoke the SDK's `isTransientError` helper. Transient errors (HTTP **429**, **5xx**, network timeouts, broken pipes) are silently retried at the configured poll interval. Permanent errors (validation, `ctx.Done()`, `4xx` other than 429) abort the wait immediately. For details on tuning the poll interval and consistency window, see [SDK Configuration](/crec/reference/sdk-configuration). ## Next steps - [Poll and Search Events](/crec/guides/events/poll-and-search): how the active watcher delivers data. - [Lifecycles](/crec/reference/lifecycles): full state machines for watchers, wallets, and operations. --- # Poll and Search Events Source: https://docs.chain.link/crec/guides/events/poll-and-search Last Updated: 2026-08-31 The events client exposes three read paths: | Method | Use case | Pagination | Filter surface | | --------------------------------------------------------------- | ------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------- | | `Events.Poll` | Real-time tail-following of a channel | offset / limit | None: pure `GET /channels/{id}/events` | | `Events.SearchEvents` | Historical queries and analytics | offset / limit | Type, date range, chain, watcher, wallet, address, event name, service | | `apiClient.GetChannelsChannelIdEventsSearchEventIdWithResponse` | Fetch one event by UUID (via the underlying generated client) | — | — | ## Poll for new events `Poll` is the simplest path: it returns a batch ordered by **descending offset** (newest first) and a `hasMore` flag. Most consumers run it in a loop with a small back-off when the channel is idle. ### Loop pattern ```go import ( crecevents "github.com/smartcontractkit/crec-sdk/events" ) for { evts, hasMore, err := client.Events.Poll(ctx, channelID, nil) if err != nil { if errors.Is(err, crecevents.ErrChannelNotFound) { return err } log.Printf("transient poll error: %v", err) time.Sleep(2 * time.Second) continue } for _, ev := range evts { if ok, _ := client.Events.Verify(&ev); !ok { continue } process(ev) } if !hasMore { time.Sleep(5 * time.Second) } } ``` The SDK does **not** advance an internal cursor for you. `Poll` returns the most-recent unread events for the channel; persist the largest `Headers.Offset` you have processed so you can resume across restarts. ## Search historical events For point-in-time queries (date ranges, address filters, event-name filters) use `SearchEvents`. It accepts the full `GetChannelsChannelIdEventsSearchParams` filter set: ```go import apiClient "github.com/smartcontractkit/crec-api-go/client" types := []apiClient.EventType{apiClient.EventTypeWatcherEvent} addresses := []apiClient.EthereumAddress{"0xYourErc20"} chainSelectors := []string{"16015286601757825753"} createdGte := time.Now().Add(-24 * time.Hour).Unix() createdLte := time.Now().Unix() eventName := "Transfer" params := &apiClient.GetChannelsChannelIdEventsSearchParams{ Type: &types, EventName: &eventName, Address: &addresses, ChainSelector: &chainSelectors, CreatedGte: &createdGte, CreatedLte: &createdLte, } events, hasMore, err := client.Events.SearchEvents(ctx, channelID, params) ``` curl equivalent (note the dotted query params `created.gte`, `created.lte`): ```bash curl -sS "$CREC_BASE_URL/channels/$CHANNEL_ID/events/search?type=watcher.event&event_name=Transfer&address=0xYourErc20&chain_selector=16015286601757825753&created.gte=...&created.lte=..." \ -H "Authorization: Apikey $CREC_API_KEY" ``` ### Filter reference | Filter | Type | Notes | | ------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------- | | `Type` | `*[]apiClient.EventType` | Multi-value: `watcher.event`, `watcher.status`, `operation.status`, `wallet.status`. | | `EventName` | `*string` | Filter to a specific event name (e.g. `"Transfer"`). Applies to `watcher.event` only. | | `Address` | `*[]apiClient.EthereumAddress` | Multi-value EVM addresses. | | `ChainSelector` | `*[]string` | Multi-value chain selectors. | | `WatcherId` / `WalletId` | `*openapi_types.UUID` | Filter to events from a specific watcher / wallet. | | `Service` | `*[]string` | Multi-value (e.g. `["dta.v2"]`). | | `Status` | `*[]string` | For `operation.status` / `wallet.status` / `watcher.status` events. | | `WalletOperationId` / `OperationId` | `*string` | Applies to `operation.status` events. | | `CreatedGt` / `CreatedGte` / `CreatedLt` / `CreatedLte` | `*int64` | Unix-second range filters (sent as `created.gt`, etc.). | | `Limit` / `Offset` | `*int` / `*int64` | Pagination (default `limit=50`, max `200`). | The SDK returns these error shapes from `SearchEvents`: | Sentinel | Trigger | | ------------------------------------------------------------------ | -------------------------------------------------------------------- | | `events.ErrSearchEvents` wrapping `events.ErrBadRequest` | API returned **400** with a `Message` describing the invalid filter. | | `events.ErrChannelNotFound` | The channel does not exist. | | `events.ErrSearchEvents` wrapping `events.ErrUnexpectedStatusCode` | Any other non-200. | ## Get a specific event There is no typed helper for single-event fetch. Use the underlying API client: ```go import apiClient "github.com/smartcontractkit/crec-api-go/client" api, err := crec.NewAPIClient("https://cre-connect.api.chain.link/v1", apiKey) if err != nil { return err } resp, err := api.GetChannelsChannelIdEventsSearchEventIdWithResponse(ctx, channelID, eventID) if err != nil { return err } if resp.JSON200 == nil { return fmt.Errorf("nil event payload") } ev := *resp.JSON200 ``` ## Always verify before processing ## Next steps - [Verify Event Signatures](/crec/guides/events/verify-signatures): cryptographically authenticate every event. - [Decode Event Data](/crec/guides/events/decode-data): turn the verified payload into a Go struct. - [Event Types and Payloads](/crec/reference/event-payloads): every payload variant in one table. --- # Verify Event Signatures Source: https://docs.chain.link/crec/guides/events/verify-signatures Last Updated: 2026-08-26 Every event the SDK consumes carries an Off-Chain Reporting (OCR) proof from the Chainlink DON, and the SDK refuses to trust an event that does not check out cryptographically. This guide covers the three verification helpers, their per-call variants, and the sentinel errors they return. The deeper algorithm is documented in [Concepts: Event Verification](/crec/concepts/event-verification). ## The verification methods Three event types can be verified: `watcher.event`, `operation.status`, and `query.status`. Each accepts the workflow owner in three ways: | Use case | Watcher events | `operation.status` events | `query.status` events | | --------------------------------------------- | -------------------------------------- | ----------------------------------------------------- | ------------------------------------------------- | | Use the client's default (most apps) | `Verify(event)` | `VerifyOperationStatus(event)` | `VerifyQueryStatus(event)` | | Multi-org service deriving owner from org ID | `VerifyWithOrgID(event, orgID)` | `VerifyOperationStatusWithOrgID(event, orgID)` | `VerifyQueryStatusWithOrgID(event, orgID)` | | Caller already has the workflow owner address | `VerifyWithWorkflowOwner(event, addr)` | `VerifyOperationStatusWithWorkflowOwner(event, addr)` | `VerifyQueryStatusWithWorkflowOwner(event, addr)` | `Verify` chooses based on which option you set on the client: 1. If `OrgID` is set → `VerifyWithOrgID`. 2. Else if `WorkflowOwner` is set → `VerifyWithWorkflowOwner`. 3. Else → returns `events.ErrOrgIDOrWorkflowOwnerReq`. ## Configure the client Verification is **enabled by default** with the production DON keys (`DefaultMinRequiredSignatures = 4`, `DefaultValidSigners` = the production DON signing addresses). ```go client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithEventVerification(4, []string{ "0xff9b062fccb2f042311343048b9518068370f837", // ... remaining production DON signers ... }), crec.WithOrgID("your-org-id"), // OR WithWorkflowOwner("0x...") ) ``` Use `crec.WithoutEventVerification()` only in tests against an in-process mock server. ## Verifying a watcher event ```go events, _, err := client.Events.Poll(ctx, channelID, nil) if err != nil { return err } for _, ev := range events { ok, err := client.Events.Verify(&ev) if err != nil { log.Printf("event %s verification error: %v", ev.EventId, err) continue } if !ok { log.Printf("event %s did not meet signature threshold", ev.EventId) continue } // Safe to process from here. } ``` `Verify` returns `false` (no error) when: - The OCR proof has fewer than `MinRequiredSignatures` valid signatures from `ValidSigners`. - A signature is structurally valid but recovers to an address not in `ValidSigners`. It returns an error when the event is malformed (no proof, bad hex, payload type mismatch, etc.); see the **Sentinel errors** table below. ### Multi-org verification If a single client receives events from multiple organizations, derive the workflow owner per-event: ```go ok, err := client.Events.VerifyWithOrgID(&ev, "their-org-id") ``` Internally this calls `WorkflowOwnerFromOrgID(orgID)`, which uses the CRE canonical CREATE2-style derivation with the configured `CRETenantID` (default `"1"`). You can also pre-compute the address yourself and call `VerifyWithWorkflowOwner` to avoid the derivation per event. ## Verifying an `operation.status` event ```go ok, err := client.Events.VerifyOperationStatus(&ev) ``` `VerifyOperationStatus` is structurally identical to `Verify` but expects `event.Headers.Type == apiClient.EventTypeOperationStatus`. The hash recipe differs (it hashes the base64-decoded `VerifiableEvent` directly), but the signature-checking step is the same. ## Lower-level: verifying raw OCR signatures If you have an OCR report, OCR context, and a list of signatures and want to check them in isolation (for example because you persisted only the proof bytes), use `VerifyOCRSignatures`: ```go ok, err := client.Events.VerifyOCRSignatures(report, ctxStr, signatures) ``` This validates only that enough signatures recover to addresses in `ValidSigners`. It does **not** check the event hash or workflow owner; use it only for forensic / replay scenarios. ## Sentinel errors | Error | Meaning | | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `events.ErrVerificationNotConfigured` | No `ValidSigners`. The client was constructed with `WithoutEventVerification()`. | | `events.ErrOrgIDOrWorkflowOwnerReq` | Default `Verify` was called but neither `OrgID` nor `WorkflowOwner` was configured. | | `events.ErrOnlyWatcherEventsSupported` | `Verify` was called with a non-`watcher.event` envelope. Use `VerifyOperationStatus` for status events. | | `events.ErrOnlyOperationStatusSupported` | Symmetric: `VerifyOperationStatus` was called with a non-status event. | | `events.ErrInvalidEventHash` | The locally-computed event hash does not match the report. Possible tampering or wrong workflow owner. | | `events.ErrNoOCRProofs` | No OCR proof is attached to the event yet. This is usually a transient race: the backend surfaced the record before the DON had attached the proof. Re-poll on the next cycle; the same event will reappear with the proof attached. | | `events.ErrMultipleOCRProofs` | More than one OCR proof on the same event: exactly one is expected. Indicates a backend bug; report it. | | `events.ErrOCRReportTooShort` | The report is shorter than the minimum needed to extract the payload. | | `events.ErrParseOCRReport` / `ErrParseOCRContext` / `ErrParseSignature` | Hex parsing failure. | | `events.ErrRecoverPubKeyFromSignature` | A signature was malformed (wrong length / non-recoverable). | | `events.ErrDeriveWorkflowOwner` | `WorkflowOwnerFromOrgID` failed (typically a malformed `OrgID`). | ## Tuning `MinRequiredSignatures` `DefaultMinRequiredSignatures = 4`. For a higher security bar, raise the threshold; for a more permissive setup, lower it. The constraint is `MinRequiredSignatures > 0` whenever `ValidSigners` is non-empty; otherwise `crec.NewClient` returns `crec.ErrInvalidEventVerificationConfig`. ## Next steps - [Decode Event Data](/crec/guides/events/decode-data): once the event verifies, turn it into a typed payload. - [Event Verification](/crec/concepts/event-verification): the algorithm explained step-by-step. --- # Decode Event Data Source: https://docs.chain.link/crec/guides/events/decode-data Last Updated: 2026-08-31 After verifying an event you typically want a typed Go value rather than the `apiClient.Event` envelope. The SDK provides three layers of decoding, each suited to a different use case. | Helper | Returns | Use when | | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `Events.DecodeVerifiableEvent` | `*models.VerifiableEvent` | You want the canonical structured representation: chain family/selector, EVM event metadata, decoded `params` map. | | `Events.Decode` | Custom struct (caller-supplied) | You have a hand-written Go type that mirrors the payload schema and want to map directly onto it. | | `.DecodeFromEvent` (e.g. `dtav2.DecodeFromEvent`) | Extension `DecodedEvent` wrapper carrying the typed `ConcreteEvent` plus enrichment data | You're consuming an extension service (DTA v2 etc.). The extension SDK ships one entry-point decoder that resolves the concrete event by name. | ## 1. Canonical decoding with `DecodeVerifiableEvent` Each `apiClient.Event` carries a base64-encoded `VerifiableEvent` string in its `Payload`. `DecodeVerifiableEvent` decodes it into the `models.VerifiableEvent` struct exposed by `crec-api-go/models`. ```go import ( apiClient "github.com/smartcontractkit/crec-api-go/client" "github.com/smartcontractkit/crec-api-go/models" ) events, _, err := client.Events.Poll(ctx, channelID, nil) if err != nil { return err } for _, ev := range events { if ok, _ := client.Events.Verify(&ev); !ok { continue } payload, err := ev.Payload.AsWatcherEventPayload() if err != nil { return err } ve, err := client.Events.DecodeVerifiableEvent(&payload) if err != nil { return err } fmt.Println(ve.Name) // "Transfer" fmt.Println(*ve.ChainFamily) // "evm" if ve.ChainEvent != nil { evm, err := ve.ChainEvent.AsEVMEvent() if err == nil { fmt.Println(evm.Address, evm.TxHash, evm.BlockNumber, evm.LogIndex) fmt.Println(*evm.Params) // map[string]any{"from": "...", "to": "...", "value": ...} } } } ``` `models.VerifiableEvent` fields: | Field | Type | Notes | | --------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `Name` | `string` | Event name. For ABI-defined events this is the Solidity event name (`Transfer`, `Swap`…). For extension events it is the service-defined name. | | `Service` | `*string` | The service that produced the event (`_crec` for non-service events). | | `ChainFamily` | `*string` | E.g. `"evm"`. | | `ChainSelector` | `*string` | Chain selector string. | | `ChainEvent` | `*VerifiableEvent_ChainEvent` | Discriminated union: call `.AsEVMEvent()` for EVM events. | | `Data` | `*map[string]any` | Service-defined free-form data (used by extension events). | | `Timestamp` | `time.Time` | When the event was produced. | `models.EVMEvent` fields are: `Address`, `BlockNumber`, `BlockTimestamp`, `ChainId`, `EventSignature`, `LogIndex`, `TopicHash`, `TxHash`, `Params`. ## 2. Direct decoding with `Events.Decode` If you control the consumer end and want strong types end-to-end, declare a Go struct that mirrors the envelope you expect and decode straight into it: ```go type TransferEvent struct { Headers struct { Type apiClient.EventType `json:"type"` Service string `json:"service,omitempty"` EventName string `json:"event_name,omitempty"` ChainSelector string `json:"chain_selector,omitempty"` } `json:"headers"` Payload struct { VerifiableEvent string `json:"verifiable_event"` OcrProofs []struct { OcrReport string `json:"ocr_report"` OcrContext string `json:"ocr_context"` Signatures []string `json:"signatures"` } `json:"ocr_proofs"` } `json:"payload"` } var typed TransferEvent if err := client.Events.Decode(&ev, &typed); err != nil { return err } ``` `Events.Decode` re-marshals the event to JSON and unmarshals into your struct, so any field naming mismatch will surface as zero values. Use this for top-level envelope shaping; use `DecodeVerifiableEvent` for the embedded chain event. ## 3. Extension-decoded payloads (DTA v2) Extensions ship typed event structs and a single entry-point decoder so you never have to touch a `map[string]any`. For DTA v2 the entry point is `dtav2.DecodeFromEvent` (package `github.com/smartcontractkit/crec-sdk-ext-dta/v2`): ```go import ( dtav2 "github.com/smartcontractkit/crec-sdk-ext-dta/v2" dtaevents "github.com/smartcontractkit/crec-sdk-ext-dta/v2/events" ) dec, err := dtav2.DecodeFromEvent(ctx, ev) if err != nil { return err } switch concrete := dec.ConcreteEvent.(type) { case dtaevents.SubscriptionRequested: fmt.Println(concrete.RequestId, concrete.FundAdminAddr, concrete.FundTokenId, concrete.Amount) case dtaevents.RedemptionRequested: fmt.Println(concrete.RequestId, concrete.FundAdminAddr, concrete.FundTokenId, concrete.Shares) case dtaevents.DistributorRequestProcessing: fmt.Println(concrete.RequestId, concrete.FundAdminAddr, concrete.FundTokenId) } // Enrichment data (when present on the verifiable event) if dec.FundTokenData != nil { fmt.Println("fund token:", dec.FundTokenData) } if dec.DistributorRequest != nil { fmt.Println("distributor request:", dec.DistributorRequest) } ``` `DecodeFromEvent`: - Extracts the `WatcherEventPayload` (returns an error otherwise). - Decodes the underlying `VerifiableEvent` and resolves the concrete event by name via `events.EventDecoders()`. - Surfaces enrichment data from the on-chain reference data attached to the event (fund-token configuration, distributor request, payment requests). The extension does **not** export sentinel error variables; failures are returned as wrapped `fmt.Errorf` errors describing the failed step. See [DTA Events](/crec/extensions/dta/events) for the full list of typed event structs. ## EVM `params` decoding tips `EVMEvent.Params` is the result of decoding the log against the watcher's ABI. Numbers are returned as JSON numbers (so `string` for any uint256 above `2^53`): ```go params := *evm.Params amountStr := params["value"].(string) amount, ok := new(big.Int).SetString(amountStr, 10) if !ok { return fmt.Errorf("invalid amount %q", amountStr) } ``` Address fields come back as 0x-prefixed hex strings; bytes fields as 0x-prefixed hex. ## `operation.status` events `operation.status` payloads are decoded through the same machinery. The difference is that you call `AsOperationStatusPayload()` and `DecodeOperationStatusVerifiableEvent`: ```go osPayload, err := ev.Payload.AsOperationStatusPayload() if err != nil { return err } ve, err := client.Events.DecodeOperationStatusVerifiableEvent(&osPayload) ``` The resulting `VerifiableEvent.Data` carries the operation outcome (`status`, `tx_hash`, `error_message` if any). See [Submit and Track Operations](/crec/guides/operations/submit-and-track) for a full status-watching loop. ## Next steps - [Event Types and Payloads](/crec/reference/event-payloads): schemas for every payload variant. - [Operations: Submit and Track](/crec/guides/operations/submit-and-track): apply decoding to `operation.status` events. --- # Build and Sign Operations Source: https://docs.chain.link/crec/guides/operations/build-and-sign Last Updated: 2026-08-31 A CRE Connect **operation** is a batch of one or more transactions, signed once with EIP-712 and executed atomically by your Smart Account. This guide covers the build → sign half of the lifecycle. The submit → track half is in [Submit and Track Operations](/crec/guides/operations/submit-and-track). ## The data model ```go import ( "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/smartcontractkit/crec-sdk/transact/types" ) op := &types.Operation{ ID: big.NewInt(time.Now().Unix()), Account: common.HexToAddress("0xYourSmartAccount"), Deadline: big.NewInt(0), Transactions: []types.Transaction{{ To: common.HexToAddress("0xTargetContract"), Value: big.NewInt(0), Data: hexutil.Bytes(callData), }}, } ``` Field rules: | Field | Notes | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ID` | Unique-per-account nonce. Must be a non-negative integer. If you build the Operation yourself, pick any scheme that never repeats per Smart Account (a counter, a UUID-derived value). The Smart Account rejects re-used IDs. Note: `ExecuteTransactions` does not use this convention; it generates a random 128-bit ID for you. | | `Account` | Address of your **Smart Account** (not your EOA / signer). This is the `verifyingContract` in the EIP-712 domain. | | `Deadline` | Unix seconds. `0` means no expiration. The Smart Account reverts after `block.timestamp > deadline`. | | `Transactions` | At least one. Each `Transaction` is `(to, value, data)`. | ### Building calldata The SDK does not include an ABI encoder; use `go-ethereum`'s `accounts/abi`: ```go import ( "strings" "github.com/ethereum/go-ethereum/accounts/abi" ) const counterABI = `[{"inputs":[{"internalType":"uint256","name":"by","type":"uint256"}],"name":"incrementBy","outputs":[],"stateMutability":"nonpayable","type":"function"}]` parsed, err := abi.JSON(strings.NewReader(counterABI)) if err != nil { return err } callData, err := parsed.Pack("incrementBy", big.NewInt(7)) if err != nil { return err } ``` For batched operations, append further `types.Transaction` entries with their own `callData`. ## Sign with EIP-712 The `transact` client embeds an `eip712.Handler` that: 1. Resolves the chain ID from the chain selector (via `smartcontractkit/chain-selectors`). 2. Builds the `TypedData` (domain `CLLSmartAccount` v1, primary type `Operation`). 3. Hashes it (`EIP712Hash`). 4. Asks the configured `signer.Signer` to produce a signature. You can do this in one call: ```go import ( "github.com/ethereum/go-ethereum/crypto" "github.com/smartcontractkit/crec-sdk/transact/signer/local" ) pk, err := crypto.HexToECDSA(privateKeyHex) // 64-char hex, no 0x if err != nil { return err } ecdsa := local.NewSigner(pk) opHash, sig, err := client.Transact.SignOperation(ctx, op, ecdsa, chainSelector) ``` Or in two steps if you want to inspect/audit the hash before signing: ```go opHash, err := client.Transact.HashOperation(op, chainSelector) if err != nil { return err } sig, err := client.Transact.SignOperationHash(ctx, opHash, ecdsa) ``` The returned signature is **65 bytes** (r ∥ s ∥ v), suitable for `eth_sign`-style recovery on chain. ## Picking the right signer Any type that implements `signer.Signer` (and `signer.TypedDataSigner`) works: | Signer | Package | Use for | | --------------- | ------------------------------------- | --------------------------------------- | | Local ECDSA | `crec-sdk/transact/signer/local` | Tests, CLIs, dev environments. | | AWS KMS | `crec-sdk/transact/signer/kms` | Production where keys must stay in HSM. | | HashiCorp Vault | `crec-sdk/transact/signer/vault` | Self-hosted secret management. | | Fireblocks | `crec-sdk/transact/signer/fireblocks` | MPC-managed keys with policy approval. | | Privy | `crec-sdk/transact/signer/privy` | Embedded user wallets. | | Custom | implement `signer.Signer` yourself | Bring-your-own KMS / multisig flow. | The signer's address must match, or be allowed by, the Smart Account's signer set. See [Manage Wallet Signers](/crec/guides/wallets/manage-signers). ## Re-signing for the same operation ID `Operation.ID` is a per-account nonce. If you build an operation, sign it, then change anything (`Transactions`, `Deadline`, anything that changes the typed-data hash), you must: - Either re-sign with the same `ID` **and** make sure no copy of the original signed payload was sent. If both reach CRE Connect, the second one will be rejected on chain. - Or bump `ID` (e.g. `time.Now().Unix() + 1`) and sign that. Most flows just regenerate the operation from scratch. ## End-to-end example ```go op := &types.Operation{ ID: big.NewInt(time.Now().Unix()), Account: smartAccount, Deadline: big.NewInt(0), Transactions: []types.Transaction{{ To: counterAddr, Value: big.NewInt(0), Data: hexutil.Bytes(callData), }}, } opHash, sig, err := client.Transact.SignOperation(ctx, op, ecdsa, chainSelector) if err != nil { return err } fmt.Printf("hash=%s sig=0x%x\n", opHash.Hex(), sig) ``` The `(op, sig)` pair is now ready to send. Continue with [Submit and Track Operations](/crec/guides/operations/submit-and-track). ## Deferred signing If your signer cannot approve the operation synchronously, create a draft instead of producing the signature in this guide. A draft stores the unsigned operation in `pending_signature`, lets your approval system sign the digest later, and can be cancelled before execution. Use this for MPC policy review, human approval queues, KMS workflows, or preview-before-sign screens. See [Draft Operations](/crec/concepts/drafts) for the model and [Draft Operations: Create, Finalize, Cancel](/crec/guides/operations/drafts) for the SDK and REST flow. ## Next steps - [Submit and Track Operations](/crec/guides/operations/submit-and-track): send the signed operation and watch the resulting status events. - [Draft Operations: Create, Finalize, Cancel](/crec/guides/operations/drafts): create an unsigned operation and finalize it later. - [Batch Multiple Transactions](/crec/guides/operations/batch-transactions): group several calls into a single atomic operation. - [Signing Transparency](/crec/guides/operations/signing-transparency): show users exactly what they're signing. --- # Draft Operations: Create, Finalize, Cancel Source: https://docs.chain.link/crec/guides/operations/drafts Last Updated: 2026-08-31 [Draft operations](/crec/concepts/drafts) let you create an operation first and collect the signature later. Use this flow when an approval system, MPC signer, KMS operator, or user review screen needs to inspect the operation before it becomes executable. This guide shows the SDK path first, then the REST shape for integrations that call the API directly. ## Prerequisites Before you start, you need: - An active CRE Connect channel ID. - A Smart Account address on the target chain. - The chain selector for that network. - One or more encoded EVM transactions. - A signer that the Smart Account accepts when you finalize the draft. See [Prerequisites](/crec/getting-started/prerequisites), [Create and Manage Wallets](/crec/guides/wallets/create-and-manage), and [Build and Sign Operations](/crec/guides/operations/build-and-sign) for setup. ## Build the operation A draft uses the same `types.Operation` payload as a signed operation. The difference is that you submit it without a signature. ```go import ( "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/smartcontractkit/crec-sdk/transact/types" ) op := &types.Operation{ ID: big.NewInt(time.Now().Unix()), Account: common.HexToAddress("0xYourSmartAccount"), Deadline: big.NewInt(time.Now().Add(30 * time.Minute).Unix()), Transactions: []types.Transaction{{ To: common.HexToAddress("0xTargetContract"), Value: big.NewInt(0), Data: hexutil.Bytes(callData), }}, } ``` `Deadline` is part of the EIP-712 payload. Choose it before you create the draft. If the operation expires before finalization, CRE Connect rejects finalization with `OPERATION_DEADLINE_ELAPSED`. ## Create a draft Use `SendDraftOperation` when you already have a `types.Operation`. ```go import "github.com/smartcontractkit/crec-sdk/transact" previews := []*transact.DraftTransactionPreview{{ FunctionSignature: "transfer(address,uint256)", }} draftID, err := client.Transact.SendDraftOperation( ctx, channelID, op, chainSelector, previews, ) if err != nil { return err } draft, err := client.Transact.GetOperation(ctx, channelID, *draftID) if err != nil { return err } fmt.Println(draft.OperationId, draft.Status) // -> pending_signature ``` Use `CreateUnsignedDraftOperation` when your application already works with API-shaped strings: ```go draftID, err := client.Transact.CreateUnsignedDraftOperation(ctx, channelID, transact.CreateDraftOperationInput{ ChainSelector: chainSelector, Address: op.Account.Hex(), WalletOperationID: op.ID.String(), Deadline: op.Deadline.Int64(), Transactions: []transact.DraftTransactionRequest{{ To: op.Transactions[0].To.Hex(), Value: op.Transactions[0].Value.String(), Data: "0x" + common.Bytes2Hex(op.Transactions[0].Data), Preview: &transact.DraftTransactionPreview{ FunctionSignature: "transfer(address,uint256)", }, }}, }) ``` REST equivalent: ```bash curl -sS -X POST "$CREC_BASE_URL/channels/$CHANNEL_ID/operations" \ -H "Authorization: Apikey $CREC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "chain_selector": "16015286601757825753", "address": "0xYourSmartAccount", "wallet_operation_id": "1735312000", "deadline": 1788192000, "transactions": [ { "to": "0xTargetContract", "value": "0", "data": "0xa9059cbb...", "preview": { "function_signature": "transfer(address,uint256)" } } ] }' ``` The omitted `signature` field makes this a draft. The operation starts in `pending_signature` and CRE Connect does not relay it to the DON yet. ## Compute and sign the digest Compute the EIP-712 digest locally, then send it to your signer or approval system. ```go digest, err := client.Transact.HashOperation(op, chainSelector) if err != nil { return err } signature, err := client.Transact.SignOperationHash(ctx, digest, operationSigner) if err != nil { return err } ``` The same signer types work here as in regular operation flows: local ECDSA, AWS KMS, HashiCorp Vault, Fireblocks, Privy, or any custom `signer.Signer`. ## Finalize the draft If the SDK should sign the digest and finalize in one call, use `ExecuteDraftOperation`: ```go finalized, err := client.Transact.ExecuteDraftOperation( ctx, channelID, *draftID, digest.Bytes(), operationSigner, ) if err != nil { return err } fmt.Println(finalized.Status) // -> accepted ``` If another system already produced the signature, use `SendSignedDraftOperation`: ```go finalized, err := client.Transact.SendSignedDraftOperation( ctx, channelID, *draftID, digest.Bytes(), signature, ) ``` REST equivalent: ```bash curl -sS -X PATCH "$CREC_BASE_URL/channels/$CHANNEL_ID/operations/$OPERATION_ID" \ -H "Authorization: Apikey $CREC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "accepted", "digest": "0x1234...", "signature": "0xabcdef..." }' ``` After finalization, the operation transitions from `pending_signature` to `accepted`. It then follows the normal operation lifecycle through `sending`, `sent`, `broadcasting`, and a terminal status. ## Cancel a draft Cancel a draft while it is still `pending_signature`: ```go if err := client.Transact.CancelDraftOperation(ctx, channelID, *draftID); err != nil { return err } ``` REST equivalent: ```bash curl -sS -X PATCH "$CREC_BASE_URL/channels/$CHANNEL_ID/operations/$OPERATION_ID" \ -H "Authorization: Apikey $CREC_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status":"cancelled"}' ``` You cannot finalize a cancelled draft. Create a new draft if the user wants to approve a revised operation. ## Handle expiration Drafts use the same `deadline` field as signed operations: - `0` means no expiration. - A positive value is a Unix timestamp. - CRE Connect can mark a draft `expired` when the deadline passes. - A finalize request near or after the deadline can fail with `OPERATION_DEADLINE_ELAPSED`. After `cancelled`, `expired`, or `failed`, the same `wallet_operation_id` can be reused for a new operation on the same wallet and chain. In most systems, generating a fresh ID remains easier to reason about. ## Error handling | Error or code | When it happens | Action | | --------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------- | | `transact.ErrDraftNotFound` | The draft operation does not exist, or the channel/operation ID is wrong. | Check the IDs and channel ownership. | | `transact.ErrDraftNotFinalizable` | The draft is not in `pending_signature`, or the API returned a finalization conflict. | Fetch the operation and inspect its current status. | | `transact.ErrDraftNotCancellable` | The draft is not in `pending_signature`, or the API returned a cancellation conflict. | Fetch the operation before showing another cancel action. | | `transact.ErrDigestRequired` | Finalization did not include a 32-byte digest. | Compute the digest with `HashOperation`. | | `transact.ErrSignatureRequired` | Finalization did not include a signature. | Sign the digest before finalizing. | | `OPERATION_DEADLINE_ELAPSED` | The deadline elapsed before finalization. | Create a new draft with a fresh deadline. | See [Error Handling](/crec/reference/error-handling) for the full REST and SDK error catalog. ## Next steps - [Submit and Track Operations](/crec/guides/operations/submit-and-track): track the operation after finalization. - [Signing Transparency](/crec/guides/operations/signing-transparency): show users what they approve before signing. - [Operation Lifecycles](/crec/reference/lifecycles#operation-lifecycle): review every operation status. --- # Submit and Track Operations Source: https://docs.chain.link/crec/guides/operations/submit-and-track Last Updated: 2026-08-31 Once you have a signed `(Operation, signature)` pair (see [Build and Sign Operations](/crec/guides/operations/build-and-sign)), submitting it is a single SDK call. Tracking it to a final status takes a little more work. This guide covers both REST polling and the `operation.status` verifiable event. If you need to create the operation before collecting a signature, use [Draft Operations](/crec/guides/operations/drafts). Drafts start in `pending_signature` and move into this submit-and-track flow after finalization. ## One-shot submission with `ExecuteOperation` `ExecuteOperation` signs and sends in one call. This is the simplest path: ```go opr, err := client.Transact.ExecuteOperation(ctx, channelID, ecdsa, op, chainSelector) if err != nil { return err } fmt.Println(opr.OperationId, opr.Status) // -> accepted ``` Internally `ExecuteOperation`: 1. Calls `SignOperation` (EIP-712 hash + signer). 2. Marshals every `Transaction` to `(to, value, data)` strings. 3. POSTs `/channels/{channelID}/operations` with the signature. 4. GETs the freshly-created operation so you have its server-side `OperationId` and initial `Status`. If you already signed elsewhere, use `SendSignedOperation(ctx, channelID, op, sig, chainSelector)` and skip the signing step. ## Submit raw without the helper For tools that build their own input layer (CLIs, batched submitters, off-chain services), call `CreateOperation` directly: ```go import "github.com/smartcontractkit/crec-sdk/transact" opID, err := client.Transact.CreateOperation(ctx, transact.CreateOperationInput{ ChannelID: channelID, ChainSelector: chainSelector, Address: op.Account.Hex(), WalletOperationID: op.ID.String(), Deadline: op.Deadline.Int64(), Transactions: []transact.TransactionRequest{{ To: op.Transactions[0].To.Hex(), Value: op.Transactions[0].Value.String(), Data: "0x" + common.Bytes2Hex(op.Transactions[0].Data), }}, Signature: "0x" + common.Bytes2Hex(sig), }) ``` `CreateOperation` returns only the operation UUID. The API responds with HTTP **201** and an `OperationResponse` body containing just the new `operation_id`. Call `GetOperation(ctx, channelID, opID)` afterwards if you need the full `Operation` record. curl equivalent: ```bash curl -sS -X POST "$CREC_BASE_URL/channels/$CHANNEL_ID/operations" \ -H "Authorization: Apikey $CREC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "chain_selector": "16015286601757825753", "address": "0xYourSmartAccount", "wallet_operation_id": "1735312000", "deadline": 0, "transactions": [ {"to":"0xCounter", "value":"0", "data":"0xa9059cbb..."} ], "signature": "0x<65-byte sig>" }' ``` ## Operation lifecycle The full operation state machine lives in [Lifecycles](/crec/reference/lifecycles#operation-lifecycle). For this guide, the key points are: | Phase | Statuses | | ----------------- | ------------------------------------------------------------------------------ | | Draft entry | `pending_signature` until finalized, cancelled, or expired. | | Signed entry | `accepted`, then relay progress through `sending`, `sent`, and `broadcasting`. | | Confirmation | `confirmed_latest`, `confirmed_safe`, then `confirmed` as the block matures. | | Terminal outcomes | `confirmed`, `failed`, `cancelled`, or `expired`. | Use the terminal status that matches your risk tolerance. Read-only dashboards can show `confirmed_latest`; irreversible business actions should wait for `confirmed`. Some testnets only emit `confirmed_latest`, so check the statuses your channel receives on the target network. ## Track to completion There are two complementary paths. ### Path A: Poll `GetOperation` Simple, no event subscription required: ```go import ( "time" apiClient "github.com/smartcontractkit/crec-api-go/client" ) deadline := time.Now().Add(2 * time.Minute) for time.Now().Before(deadline) { op, err := client.Transact.GetOperation(ctx, channelID, opID) if err != nil { return err } switch op.Status { case apiClient.OperationStatusConfirmed: fmt.Println("done", op.OperationId) return nil case apiClient.OperationStatusFailed: return fmt.Errorf("operation failed: %s", op.OperationId) } time.Sleep(2 * time.Second) } return fmt.Errorf("timed out waiting for operation %s", opID) ``` curl equivalent: ```bash curl -sS "$CREC_BASE_URL/channels/$CHANNEL_ID/operations/$OPERATION_ID" \ -H "Authorization: Apikey $CREC_API_KEY" ``` ### Path B: Subscribe to `operation.status` events This is the **verifiable** path: CRE Connect emits a signed `operation.status` event each time the operation transitions. Combine it with the regular event poller: ```go events, _, err := client.Events.SearchEvents(ctx, channelID, &apiClient.GetChannelsChannelIdEventsSearchParams{ Type: ptrSlice([]apiClient.EventType{apiClient.EventTypeOperationStatus}), }) if err != nil { return err } for _, ev := range events { if ok, _ := client.Events.VerifyOperationStatus(&ev); !ok { continue } osPayload, err := ev.Payload.AsOperationStatusPayload() if err != nil { continue } if osPayload.OperationId == opID && osPayload.Status == apiClient.OperationStatusConfirmed { fmt.Printf("confirmed via event (event_hash=%s)\n", *osPayload.EventHash) } } ``` For a real-time loop, run `Events.Poll` filtered to your operation IDs (see [Poll and Search Events](/crec/guides/events/poll-and-search)). ## List operations For dashboards and audits, `ListOperations` filters by status / chain / address / wallet: ```go status := apiClient.OperationStatusFailed ops, hasMore, err := client.Transact.ListOperations(ctx, transact.ListOperationsInput{ ChannelID: channelID, Status: &[]apiClient.OperationStatus{status}, }) ``` curl: ```bash curl -sS "$CREC_BASE_URL/channels/$CHANNEL_ID/operations?status=failed" \ -H "Authorization: Apikey $CREC_API_KEY" ``` ## Sentinel errors | Error | Meaning | | ---------------------------------------------------------------- | --------------------------------------------------------- | | `transact.ErrChannelNotFound` | Channel does not exist (404). | | `transact.ErrOperationNotFound` | Operation does not exist (404). | | `transact.ErrCreateOperation` wrapping `ErrUnexpectedStatusCode` | Non-201 from `POST /operations`. | | `transact.ErrInvalidDeadline` | `op.Deadline` is negative or doesn't fit in `int64`. | | `transact.ErrAtLeastOneTransactionRequired` | Empty `Transactions` slice. | | `transact.ErrSignatureRequired` | Signature missing on `CreateOperationInput`. | | `transact.ErrDraftNotFinalizable` | Draft cannot move from `pending_signature` to `accepted`. | | `transact.ErrDraftNotCancellable` | Draft cannot be cancelled from its current status. | ## Next steps - [Draft Operations: Create, Finalize, Cancel](/crec/guides/operations/drafts): create an unsigned operation and finalize it later. - [Batch Multiple Transactions](/crec/guides/operations/batch-transactions): atomic multi-call patterns. - [Signing Transparency](/crec/guides/operations/signing-transparency): show users exactly what they're authorising. - [Operation Lifecycles](/crec/reference/lifecycles): full state machine reference. --- # Batch Multiple Transactions Source: https://docs.chain.link/crec/guides/operations/batch-transactions Last Updated: 2026-08-31 A CRE Connect operation can carry **any number of transactions**. The Smart Account executes them in order under a single signature; if any sub-call reverts, the entire operation reverts and the on-chain state is rolled back. ## When batching matters | Pattern | Why batch | | -------------------------- | -------------------------------------------------------------- | | `approve` + `transferFrom` | Avoids the two-tx race where a user front-runs your transfer. | | `wrap` + `swap` + `unwrap` | All three legs revert together if any fails: no stranded WETH. | | Multi-recipient airdrop | One signature, one fee, one inclusion guarantee. | | Multi-asset rebalance | Atomic invariants across positions. | ## Build the batch Each leg is a `types.Transaction`. Append them in execution order: ```go import ( "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/smartcontractkit/crec-sdk/transact/types" ) approveData, _ := erc20ABI.Pack("approve", spender, amount) transferData, _ := vaultABI.Pack("deposit", amount) op := &types.Operation{ ID: big.NewInt(time.Now().Unix()), Account: smartAccount, Deadline: big.NewInt(time.Now().Add(5 * time.Minute).Unix()), Transactions: []types.Transaction{ { To: tokenAddr, Value: big.NewInt(0), Data: hexutil.Bytes(approveData), }, { To: vaultAddr, Value: big.NewInt(0), Data: hexutil.Bytes(transferData), }, }, } ``` Sign and submit exactly as you would a single-transaction operation: ```go opr, err := client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector) ``` The Smart Account will: 1. Verify the signature against `op.Account`. 2. Check `op.Deadline > block.timestamp`. 3. Check `op.ID` has not been used before. 4. Loop over `op.Transactions` and `call(to, value, data)` for each. 5. Revert atomically if any sub-call reverts. ## Sending native value Each transaction can carry its own `value`. The total `sum(value)` must be available on the Smart Account at execution time: ```go op.Transactions = []types.Transaction{ {To: alice, Value: big.NewInt(1e17), Data: nil}, // 0.1 ETH {To: bob, Value: big.NewInt(1e17), Data: nil}, // 0.1 ETH } ``` For pure transfers `Data` can be empty. Top up the Smart Account first if it does not hold the funds; see [Wallets: Create and Manage](/crec/guides/wallets/create-and-manage). ## Order matters Transactions execute in the order they appear in `Transactions`. CRE Connect does not re-order, dedupe, or merge them. ## Operation ID rules still apply `op.ID` is a per-account nonce. A batched operation uses **one** ID; the Smart Account does not increment one ID per leg. After confirmation the ID is consumed and cannot be reused. If you use `time.Now().Unix()` as the ID and submit two operations within the same second, the second submission will be rejected (`already exists` from the API or `nonce reused` from the Smart Account). Bump by one second or use a different scheme: ```go op.ID = new(big.Int).SetInt64(time.Now().UnixNano()) // higher resolution ``` ## Gas considerations The DON pays gas: there's no per-leg gas limit you need to set. However, every leg in the batch is executed inside a single transaction by the Smart Account, so the combined gas usage of all legs must fit in a single block on the destination chain. If a batch is too large, the operation fails at execution time with the failure reason on the `operation.status` event. There is no enforced cap from the SDK or API; size your batches against the per-block gas limit of each network you target. ## Example: ERC-20 approve-then-deposit ```go const erc20Json = `[{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]` const vaultJson = `[{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"}]` erc20, _ := abi.JSON(strings.NewReader(erc20Json)) vault, _ := abi.JSON(strings.NewReader(vaultJson)) amount := big.NewInt(1_000_000) // 1 USDC approveCalldata, _ := erc20.Pack("approve", vaultAddr, amount) depositCalldata, _ := vault.Pack("deposit", amount) op := &types.Operation{ ID: big.NewInt(time.Now().Unix()), Account: smartAccount, Deadline: big.NewInt(0), Transactions: []types.Transaction{ {To: usdcAddr, Value: big.NewInt(0), Data: hexutil.Bytes(approveCalldata)}, {To: vaultAddr, Value: big.NewInt(0), Data: hexutil.Bytes(depositCalldata)}, }, } opr, err := client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector) ``` If the vault `deposit` reverts (e.g. balance check fails), the `approve` is rolled back too: the `allowance` returns to its prior value. ## Next steps - [Submit and Track Operations](/crec/guides/operations/submit-and-track): drive the batch through to `confirmed`. - [Signing Transparency](/crec/guides/operations/signing-transparency): make multi-leg signatures auditable. --- # Signing Transparency Source: https://docs.chain.link/crec/guides/operations/signing-transparency Last Updated: 2026-08-31 When the Smart Account holder is a human (browser wallet, hardware wallet, MPC approval flow), they should be able to see what they are signing in unambiguous terms, not just an opaque 32-byte hash. CRE Connect operations are EIP-712 typed data, so the typed-data structure is itself the user-facing description; you just need to expose it well. This guide collects the reusable building blocks. There is no built-in helper for "render-this-operation" today; the recipe below uses `crec-sdk/transact` plus standard `go-ethereum` packages. ## What to surface For every operation, expose at minimum: | Field | Source | | --------------------------------------- | ---------------------------------------------------------------------------------- | | Smart Account address | `op.Account` | | Network (chain ID + chain selector) | `chainSelector` argument + `chain-selectors` lookup | | Operation ID (nonce) | `op.ID` | | Deadline | `op.Deadline` (decoded as Unix seconds → human time) | | Each transaction's target | `tx.To` | | Each transaction's value | `tx.Value` (decoded as native units) | | Each transaction's function + arguments | Decoded against the contract ABI | | EIP-712 domain | `CLLSmartAccount` v1, chainId = network chain ID, verifyingContract = `op.Account` | | EIP-712 typed-data hash (the digest) | `client.Transact.HashOperation(op, chainSelector)` | ## Build a typed-data summary The SDK exposes `op.TypedData(chainID)` directly: ```go import ( "encoding/json" "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/crec-sdk/transact/types" ) family, _ := chain_selectors.GetSelectorFamily(chainSelectorUint) chainID, _ := chain_selectors.GetChainIDFromSelector(chainSelectorUint) td, err := op.TypedData(chainID) if err != nil { return err } pretty, _ := json.MarshalIndent(td, "", " ") fmt.Println(string(pretty)) ``` This produces the exact `apitypes.TypedData` document that the signer consumes. Render it as JSON, or break it into the table above for a friendlier UI. ## Decode each transaction's calldata The Smart Account's job is to invoke `to.call(value, data)` for each transaction; you should resolve the `(method, arguments)` pair before showing it. ```go import ( "github.com/ethereum/go-ethereum/accounts/abi" ) func decodeCall(parsed abi.ABI, data []byte) (string, []any, error) { if len(data) < 4 { return "", nil, fmt.Errorf("calldata too short") } method, err := parsed.MethodById(data[:4]) if err != nil { return "", nil, err } args, err := method.Inputs.Unpack(data[4:]) if err != nil { return method.Name, nil, err } return method.Name, args, nil } ``` Combine with the operation: ```go for i, tx := range op.Transactions { name, args, err := decodeCall(parsed, tx.Data) if err != nil { log.Printf("tx[%d] %s: undecodable calldata", i, tx.To.Hex()) continue } log.Printf("tx[%d] %s.%s(%v) value=%s", i, tx.To.Hex(), name, args, tx.Value.String()) } ``` For unknown ABIs, fall back to the 4-byte selector and the raw hex payload. ## Compute and display the hash Always show the user the **same digest the signer will sign**: ```go opHash, err := client.Transact.HashOperation(op, chainSelector) if err != nil { return err } fmt.Println("EIP-712 digest:", opHash.Hex()) ``` If you split the flow across two services (one renders the summary, another invokes the signer), pin the operation by its hash so you can detect tampering between the two stages. ## Hardware wallet considerations Hardware wallets that natively support EIP-712 (Ledger / Trezor with Eth app ≥ 1.10) will display the typed data directly. The user sees: - Domain: `CLLSmartAccount`, version `1`, chainId, verifyingContract. - Primary type: `Operation`. - Message fields: `id`, `account`, `deadline`, and the array of `transactions`. The on-screen display **does not** decode `transactions[i].data`: it shows the raw bytes. Pair the device confirmation with an off-device decoded summary so the user can cross-check. ## MPC / approval-policy signers For Fireblocks, Privy, and similar custody systems, push the typed-data document and the decoded summary into the approval payload. Most providers support a free-form "transaction note" field; populate it with a short, human description (e.g. `Deposit 1.0 USDC into Vault 0x…cafe`) that mirrors the decoded calldata. Approvers should be trained to reject if the note doesn't match the typed data. ## A reusable rendering function ```go type Summary struct { SmartAccount string ChainID string Nonce string Deadline time.Time Hash string Transactions []TxSummary } type TxSummary struct { To string Value string Method string Args []any Selector string Raw string } func Summarise(op *types.Operation, abis map[common.Address]abi.ABI, chainSelector string, chainID string, hash common.Hash) Summary { out := Summary{ SmartAccount: op.Account.Hex(), ChainID: chainID, Nonce: op.ID.String(), Deadline: time.Unix(op.Deadline.Int64(), 0).UTC(), Hash: hash.Hex(), } for _, tx := range op.Transactions { sel := "0x" + common.Bytes2Hex(tx.Data[:4]) sum := TxSummary{To: tx.To.Hex(), Value: tx.Value.String(), Selector: sel, Raw: "0x" + common.Bytes2Hex(tx.Data)} if abi, ok := abis[tx.To]; ok { if name, args, err := decodeCall(abi, tx.Data); err == nil { sum.Method = name sum.Args = args } } out.Transactions = append(out.Transactions, sum) } return out } ``` Call `Summarise` immediately before `SignOperationHash` and surface the `Summary` to the user (web UI, CLI prompt, audit log, etc.). ## Next steps - [Build and Sign Operations](/crec/guides/operations/build-and-sign): for the underlying `HashOperation` / `SignOperationHash` calls. - [EIP-712 Signing](/crec/concepts/eip712-signing): full typed-data schema reference. --- # Execute a Chain Query Source: https://docs.chain.link/crec/guides/queries/execute-a-query Last Updated: 2026-08-26 A **chain query** is a one-shot, read-only EVM call executed by a Chainlink DON. You submit the query to CRE Connect, the DON executes it against the block you selected, and you receive a signed result you can verify off-chain. Nothing is written on chain. This guide covers the three SDK paths (one-call with raw calldata, one-call with ABI unpacking, and the manual submit-then-wait lifecycle), block selection, idempotency keys, and result verification. For the conceptual model, see [Chain Queries](/crec/concepts/queries). ## Prerequisites You need: - A CRE Connect client (`crec.Client`) constructed with your base URL, API key, and org ID. See [Authentication](/crec/getting-started/authentication). - A **channel ID**: queries are scoped to a channel. - A **chain selector** for the target network. See [Supported Networks](/crec/supported-networks). - The **contract address** you want to read from. ## Path 1: One-call with `CallContract` The simplest path: submit the query, wait for completion, and decode the result in a single call. ```go import ( "fmt" "math/big" "time" "github.com/smartcontractkit/crec-sdk/queries" ) finalized, err := queries.Finalized() if err != nil { return err } result, err := client.Queries.CallContract(ctx, queries.CallContractInput{ CallInput: queries.EVMCallInput{ ChannelID: channelID, ChainSelector: "16015286601757825753", // Ethereum Sepolia ContractAddress: "0x1234567890123456789012345678901234567890", CallData: []byte{0x18, 0x16, 0x0d, 0xdd}, // totalSupply() BlockSelection: finalized, IdempotencyKey: "total-supply-finalized-001", }, MaxWaitTime: 30 * time.Second, }) if err != nil { return err // API, polling, or decode error } if result.Error != nil { return fmt.Errorf("query failed: %s: %s", result.Error.Code, result.Error.Message) } totalSupply := new(big.Int).SetBytes(result.RawReturnData) fmt.Println("total supply:", totalSupply) ``` `CallContract` handles the full lifecycle: create → wait → decode. Use it when you have the calldata ready and want the result synchronously. ## Path 2: One-call with `CallContractWithABI` When you want the SDK to pack arguments and unpack return values, use `CallContractWithABI`: ```go result, err := client.Queries.CallContractWithABI(ctx, queries.CallContractWithABIInput{ ChannelID: channelID, ChainSelector: chainSelector, ContractAddress: tokenAddress, ABIFragment: "function balanceOf(address owner) view returns (uint256)", FunctionName: "balanceOf", Args: []any{"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"}, BlockSelection: finalized, IdempotencyKey: "balance-finalized-001", MaxWaitTime: 30 * time.Second, }) if err != nil { return err } if result.Error != nil { return fmt.Errorf("query failed: %s", result.Error.Message) } balance := result.Outputs[0].(*big.Int) fmt.Println("balance:", balance) ``` The `ABIFragment` accepts a human-readable function signature string (as shown above) or a JSON ABI fragment. ## Path 3: Manual lifecycle When you need to submit now and check later, use the individual steps: ```go // 1. Submit the query (async) latest, err := queries.Latest() if err != nil { return err } accepted, err := client.Queries.CreateEVMCall(ctx, queries.EVMCallInput{ ChannelID: channelID, ChainSelector: chainSelector, ContractAddress: tokenAddress, CallData: []byte{0x18, 0x16, 0x0d, 0xdd}, BlockSelection: latest, IdempotencyKey: "total-supply-async-001", }) if err != nil { return err } // 2. Wait for terminal status (later, or in a different goroutine) query, err := client.Queries.Wait(ctx, channelID, accepted.QueryId, 30*time.Second) if err != nil { return err } // 3. Decode the result result, err := queries.ResultFromQuery(query) if err != nil { return err } ``` Use this path when you want to inspect intermediate statuses, decouple submission from completion, or manage your own polling cadence. ## Block selection helpers The `queries` package provides helpers for all three block selectors: ```go latest, _ := queries.Latest() finalized, _ := queries.Finalized() blockNum, _ := queries.BlockNumber(6500000) blockNumFromString, _ := queries.BlockNumberFromString("6500000") ``` All return `(BlockSelection, error)`. ## Idempotency keys Every query create requires an `IdempotencyKey`. Choose a key that is: - **Deterministic**: derived from the logical request, not a random UUID. - **Unique per logical request**: different queries get different keys. ```go IdempotencyKey: "balance-of-0x742d-eth-finalized-2026-08-26", ``` If you retry after a network error with the same key and the same request parameters, you get the original query back. If the parameters differ, you get `409 Conflict` with `IDEMPOTENCY_KEY_MISMATCH`. ## Detecting completion via channel events For event-driven architectures, search for `query.status` events instead of polling the query resource: ```go import ( apiClient "github.com/smartcontractkit/crec-api-go/client" ) limit := 100 eventTypes := []apiClient.EventType{apiClient.EventTypeQueryStatus} channelEvents, hasMore, err := client.Events.SearchEvents( ctx, channelID, &apiClient.SearchChannelEventsParams{ Type: &eventTypes, Limit: &limit, }, ) if err != nil { return err } for i := range channelEvents { event := &channelEvents[i] payload, err := event.Payload.AsQueryStatusPayload() if err != nil || payload.QueryId != queryID { continue } // Verify the event verified, err := client.Events.VerifyQueryStatus(event) if err != nil || !verified { return err } // Decode the verifiable result decoded, err := client.Events.DecodeQueryStatusVerifiableEvent(&payload) if err != nil { return err } _ = decoded.Data } ``` ## Error handling ### Transport errors (returned as Go errors) | Error | When | | --------------------------- | ------------------------------------------------------------------- | | `ErrChannelNotFound` | The channel does not exist or was archived (404). | | `ErrQueryNotFound` | The query ID does not exist (404). | | `ErrIdempotencyConflict` | Same idempotency key with different parameters (409). | | `ErrRateLimitExceeded` | Query create quota or workflow admission rate limit exceeded (429). | | `ErrWaitQueryTimeout` | `Wait` exceeded `maxWaitTime` before terminal status. | | `ErrUnsupportedQueryKind` | Query kind is not `evm_call`. | | `ErrDecodeVerifiableResult` | Verifiable result could not be decoded. | ### Signed terminal errors (in `result.Error`) When the query reaches `failed` or `expired`, the Go method returns `nil` error but `result.Error` is non-nil: ```go if result.Error != nil { fmt.Errorf("query failed: %s: %s", result.Error.Code, result.Error.Message) } ``` If the query status is `failed` or `expired` but no signed error is present in the verifiable result, the SDK synthesizes one (`CRE_WORKFLOW_FAILED` for `failed`, `QUERY_EXPIRED` for `expired`). See [Error Handling](/crec/reference/error-handling) for the full sentinel error catalog. ## REST API ### Create a query ```bash curl -X POST https://cre-connect.api.chain.link/v1/channels/$CHANNEL_ID/queries \ -H "Authorization: Apikey $CREC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "idempotency_key": "total-supply-finalized-001", "query_kind": "evm_call", "chain_selector": "16015286601757825753", "params": { "contract_address": "0x1234567890123456789012345678901234567890", "call_data": "0x18160ddd", "block_selection": { "type": "finalized" } } }' ``` Returns `202 Accepted` with the query in the `accepted` state. ### Get a query ```bash curl https://cre-connect.api.chain.link/v1/channels/$CHANNEL_ID/queries/$QUERY_ID \ -H "Authorization: Apikey $CREC_API_KEY" ``` ### List queries ```bash curl "https://cre-connect.api.chain.link/v1/channels/$CHANNEL_ID/queries?status=completed&limit=20" \ -H "Authorization: Apikey $CREC_API_KEY" ``` ## Next steps - Read the [Chain Queries concept page](/crec/concepts/queries) for the full data model and verification details. - See [Event Verification](/crec/concepts/event-verification) for the OCR proof algorithm. - See [Lifecycles](/crec/reference/lifecycles#query-lifecycle) for the query state machine. --- # Create and Manage Wallets Source: https://docs.chain.link/crec/guides/wallets/create-and-manage Last Updated: 2026-08-31 A **wallet** in CRE Connect is a Smart Account deployed on a specific chain, owned by an EOA you control, and authorised to be driven by one or more **signers** (ECDSA addresses or RSA keys). It is the `op.Account` you target when you build operations. This guide covers wallet provisioning and life-cycle management. For runtime signer changes see [Manage Wallet Signers](/crec/guides/wallets/manage-signers). ## Create a wallet A wallet has two security layers: - **Owner**: the EOA that owns the deployed Smart Account on chain. Used for ownership-level changes (handled outside CRE Connect). - **Allowed signers**: the keys CRE Connect will accept signatures from when executing operations. Today we support `ecdsa` (EVM addresses) and `rsa` (public-key pairs). ### Status channel `StatusChannelId` is optional on `Create`. If you supply it, every wallet status transition (`pending → deploying → deployed`, etc.) emits a `wallet.status` verifiable event into that channel, which is the canonical way to observe `deployed` outside the SDK. The SDK rejects the zero UUID with `wallets.ErrStatusChannelIDZero`. ```go sc := channelID w, err := client.Wallets.Create(ctx, wallets.CreateInput{ // ... fields above ... StatusChannelId: &sc, }) ``` Reuse a single dedicated channel per environment for status events, or co-locate status events with the channel that owns the wallet's downstream operations: both patterns work. ## Look up a wallet ```go w, err := client.Wallets.Get(ctx, walletID) if err != nil { if errors.Is(err, wallets.ErrWalletNotFound) { return fmt.Errorf("wallet does not exist: %s", walletID) } return err } fmt.Println(w.Address, w.Status, w.AllowedEcdsaSigners) ``` curl: ```bash curl -sS "$CREC_BASE_URL/wallets/$WALLET_ID" -H "Authorization: Apikey $CREC_API_KEY" ``` ## List and filter ## Rename a wallet The only updatable field through the SDK is `Name`. The Platform UI also lets you edit the wallet's **Description** in the same flow. Signer-set changes go through a separate flow; see [Manage Wallet Signers](/crec/guides/wallets/manage-signers). ## Archive a wallet Archive is **synchronous**. The wallet transitions to `archived` and stops accepting new operations. The on-chain account is **not** destroyed; you can re-import it via a fresh `Create` if needed. ## Sentinel errors | Error | Trigger | | --------------------------------------------------------------- | ------------------------------------------------ | | `wallets.ErrNameRequired` / `wallets.ErrNameTooLong` | Validation. | | `wallets.ErrInvalidWalletOwnerAddress` | `WalletOwnerAddress` is not a valid hex address. | | `wallets.ErrUnsupportedWalletType` | `WalletType` is not `ecdsa` or `rsa`. | | `wallets.ErrInvalidSignersForEcdsa` | `AllowedRsaSigners` set on an ECDSA wallet. | | `wallets.ErrInvalidSignersForRsa` | `AllowedEcdsaSigners` set on an RSA wallet. | | `wallets.ErrInvalidEcdsaSigner` / `wallets.ErrInvalidRsaSigner` | Bad signer entry. | | `wallets.ErrWalletNotFound` | 404 from `Get` / `Update` / `Archive`. | | `wallets.ErrInvalidLimit` / `wallets.ErrInvalidOffset` | Bad pagination parameters. | ## Service limits - **Max 10 ECDSA signers** and **max 10 RSA signers** per wallet (enforced by the OpenAPI spec). - Wallet name length capped at **255 characters**. ## Next steps - [Manage Wallet Signers](/crec/guides/wallets/manage-signers): change the allowed-signers set after creation. - [Smart Accounts](/crec/concepts/smart-accounts): the on-chain contract behind a CREC wallet. --- # Manage Wallet Signers Source: https://docs.chain.link/crec/guides/wallets/manage-signers Last Updated: 2026-08-31 A wallet's **signer set** is the list of keys CRE Connect will accept signatures from when executing operations against the wallet's Smart Account. The set is typed: `ecdsa` wallets accept EVM addresses, `rsa` wallets accept RSA public keys. ## What can be configured today | Operation | Supported via Go SDK | Notes | | ------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | Set initial signer set | Yes | At wallet-creation time, via `wallets.CreateInput`. | | Read current signer set | Yes | `Wallets.Get` returns `AllowedEcdsaSigners` / `AllowedRsaSigners`. | | Add / remove signers at runtime | No | The Go SDK exposes no API for mutating the signer set after creation; `wallets.UpdateInput` only accepts `Name`. | | Rename wallet | Yes | `Wallets.Update` (Go SDK) accepts only `Name`. The REST `PATCH /wallets/{id}` endpoint also accepts `description` and `status`. | | Archive wallet | Yes | `Wallets.Archive` is synchronous (PATCHes status to `archived`); the on-chain Smart Account contract is not modified. | ## Set the signer set at creation time `StatusChannelId` is optional on `Create` (the SDK rejects a zero-UUID value with `wallets.ErrStatusChannelIDZero`); pass the channel where you want `wallet.status` events to land. ECDSA wallet: ```go import ( "github.com/smartcontractkit/crec-sdk/wallets" apiClient "github.com/smartcontractkit/crec-api-go/client" ) statusChannelID := channelID // any channel you own ecdsaSigners := []string{ "0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222", } w, err := client.Wallets.Create(ctx, wallets.CreateInput{ Name: "treasury-eth", ChainSelector: "5009297550715157269", WalletOwnerAddress: "0xYourOwnerEOA", WalletType: apiClient.Ecdsa, AllowedEcdsaSigners: &ecdsaSigners, StatusChannelId: &statusChannelID, }) ``` RSA wallet: ```go rsaSigners := apiClient.RSASignersList{ // E and N are 0x-prefixed hex strings (validated by the API regex // `^0x[a-fA-F0-9]{2,34}$` for `e` and `^0x[a-fA-F0-9]{512,}$` for `n`). // E is typically `0x010001` (= 65537). N is at least 2048 bits = 512 hex chars. {E: "0x010001", N: "0xc2a8...hex-encoded-modulus..."}, } w, err := client.Wallets.Create(ctx, wallets.CreateInput{ Name: "rsa-treasury-eth", ChainSelector: "5009297550715157269", WalletOwnerAddress: "0xYourOwnerEOA", WalletType: apiClient.Rsa, AllowedRsaSigners: &rsaSigners, StatusChannelId: &statusChannelID, }) ``` Service limits enforced by the API (`RSASignersList` / `ECDSASignersList` schemas, `maxItems: 10`): **maximum 10 ECDSA signers** and **maximum 10 RSA signers** per wallet. ## Read the current signer set ```go w, err := client.Wallets.Get(ctx, walletID) if err != nil { return err } if w.AllowedEcdsaSigners != nil { for _, addr := range *w.AllowedEcdsaSigners { fmt.Println("ecdsa signer:", addr) } } if w.AllowedRsaSigners != nil { for _, k := range *w.AllowedRsaSigners { fmt.Println("rsa signer:", k.E, k.N) } } ``` curl: ```bash curl -sS "$CREC_BASE_URL/wallets/$WALLET_ID" \ -H "Authorization: Apikey $CREC_API_KEY" \ | jq '{allowed_ecdsa_signers, allowed_rsa_signers}' ``` ## Changing the signer set after creation The signer set is fixed at creation. To use a different signer set, provision a new wallet with `Wallets.Create` and (when the old wallet is no longer needed) archive it with `Wallets.Archive`. Both APIs are the same ones documented above. ## Choosing signer addresses up front When you call `Wallets.Create` you don't pass a `signer.Signer` instance: you pass the **address (ECDSA)** or **`{e, n}` pair (RSA)** that signer will produce. Each per-provider guide documents the exact helper to use: - [Local Signer](/crec/guides/signers/local): `crypto.PubkeyToAddress(privateKey.PublicKey)`. - [AWS KMS Signer](/crec/guides/signers/aws-kms): `awskms.GetPubKeyCtx(ctx, client, keyID)` then `crypto.PubkeyToAddress`. - [HashiCorp Vault Signer](/crec/guides/signers/hashicorp-vault): `s.Public()` for ECDSA keys; `s.GetRSAModulus()` for RSA keys. - [Fireblocks Signer](/crec/guides/signers/fireblocks): `s.GetVaultAccountAddress(ctx)`. - [Privy Signer](/crec/guides/signers/privy): `s.GetWalletAddress(ctx)`. For **RSA** signers the modulus `n` and exponent `e` must be **`0x`-prefixed hex** strings, validated by the API regex `^0x[a-fA-F0-9]{2,34}$` for `e` and `^0x[a-fA-F0-9]{512,}$` for `n` (i.e. ≥ 2048-bit modulus). The Vault helper `GetRSAModulus()` returns hex **without** the `0x` prefix; prepend it before passing the value to `wallets.Create`. ## Sentinel errors | Error | Trigger | | ----------------------------------- | ------------------------------------------------------------- | | `wallets.ErrInvalidEcdsaSigner` | An entry in `AllowedEcdsaSigners` is not a valid hex address. | | `wallets.ErrInvalidRsaSigner` | An entry in `AllowedRsaSigners` has empty `E` or `N`. | | `wallets.ErrInvalidSignersForEcdsa` | `AllowedRsaSigners` set on an `ecdsa` wallet. | | `wallets.ErrInvalidSignersForRsa` | `AllowedEcdsaSigners` set on an `rsa` wallet. | ## Next steps - [Local Signer](/crec/guides/signers/local): generate an ECDSA address from a private key for testing. - [AWS KMS Signer](/crec/guides/signers/aws-kms): derive an address from a KMS-held key for production. - [Smart Accounts](/crec/concepts/smart-accounts): what the signer set protects. --- # Local ECDSA Signer Source: https://docs.chain.link/crec/guides/signers/local Last Updated: 2026-08-31 The local signer (`github.com/smartcontractkit/crec-sdk/transact/signer/local`) signs CRE Connect operations with a secp256k1 private key held in process memory. It is the simplest signer and the right choice for local development, integration tests, and CI. ## When to use - **Tests / fixtures**: deterministic ECDSA signatures. - **CLI tools** that read a key from disk or env var. - **Single-node services** where the operator owns the key. For production, prefer a managed signer ([AWS KMS](/crec/guides/signers/aws-kms), [HashiCorp Vault](/crec/guides/signers/hashicorp-vault), [Fireblocks](/crec/guides/signers/fireblocks), [Privy](/crec/guides/signers/privy)) so the key never sits in process memory. ## Construct from a private key ```go import ( "github.com/ethereum/go-ethereum/crypto" "github.com/smartcontractkit/crec-sdk/transact/signer/local" ) privateKey, err := crypto.HexToECDSA(os.Getenv("ECDSA_PRIVATE_KEY")) if err != nil { return err } s := local.NewSigner(privateKey) ``` `NewSigner` takes a `*ecdsa.PrivateKey` (from `crypto/ecdsa`). The constructor never returns an error: validation happens at signing time. ### Generate a fresh key ```go privateKey, err := crypto.GenerateKey() if err != nil { return err } s := local.NewSigner(privateKey) addr := crypto.PubkeyToAddress(privateKey.PublicKey) fmt.Println("signer address:", addr.Hex()) // add this to AllowedEcdsaSigners ``` ### Load from a hex string The hex string is the 32-byte secp256k1 private key (no `0x` prefix): ```go // Hardhat/Anvil test key #0: never use in production privateKey, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") ``` ## Sign an operation ```go opHash, sig, err := client.Transact.SignOperation(ctx, op, s, chainSelector) ``` `local.Signer` implements `signer.Signer.Sign(ctx, hash) ([]byte, error)`: 1. Calls `crypto.Sign(hash, privateKey)` (go-ethereum's secp256k1 sign). 2. If the recovery byte is `0` or `1`, adds `27` to make it Ethereum-canonical. 3. Returns the 65-byte `(r, s, v)` signature. The result is suitable for `ecrecover` on chain: exactly what the Smart Account verifies. ## Provision the wallet's signer set The signer's address is the keccak256-derived address of its public key: ```go addr := crypto.PubkeyToAddress(privateKey.PublicKey).Hex() ``` When you create the wallet, include this address in `AllowedEcdsaSigners`: ```go ecdsa := []string{addr} w, err := client.Wallets.Create(ctx, wallets.CreateInput{ Name: "dev", ChainSelector: "16015286601757825753", WalletOwnerAddress: ownerEOA.Hex(), WalletType: apiClient.Ecdsa, AllowedEcdsaSigners: &ecdsa, StatusChannelId: &statusChannelID, // optional; receives wallet.status events }) ``` ## Security checklist - Inject the key via env var; never check it into source control. - Bind the key to a wallet whose blast radius is bounded (test funds, low-value testnet positions). - Rotate the key by archiving the wallet and provisioning a new one with the new signer; see [Manage Wallet Signers](/crec/guides/wallets/manage-signers). ## Next steps - [AWS KMS Signer](/crec/guides/signers/aws-kms): production-grade equivalent. - [Build and Sign Operations](/crec/guides/operations/build-and-sign): feed the signer into the operation flow. --- # AWS KMS Signer Source: https://docs.chain.link/crec/guides/signers/aws-kms Last Updated: 2026-08-31 The KMS signer (`github.com/smartcontractkit/crec-sdk/transact/signer/kms`) signs CRE Connect operations using a secp256k1 key held in AWS Key Management Service. The private key never leaves the AWS HSM; the signer asks KMS to sign a digest and returns an Ethereum-canonical 65-byte signature. ## Prerequisites - A KMS key with `KeyUsage=SIGN_VERIFY` and `KeySpec=ECC_SECG_P256K1`. - AWS credentials available to the process (env vars, IAM role, or config file). - IAM permissions: `kms:Sign` and `kms:GetPublicKey` on the target key. Create the key: ```bash aws kms create-key \ --key-usage SIGN_VERIFY \ --key-spec ECC_SECG_P256K1 \ --description "CREC signing key for treasury-prod-eth" ``` Note the key ARN: you'll pass it to `NewSigner`. ## Construct the signer ```go import ( "github.com/smartcontractkit/crec-sdk/transact/signer/kms" ) s, err := kms.NewSigner(ctx, "arn:aws:kms:us-west-2:123456789012:key/abcd-...") if err != nil { return err } ``` `NewSigner` loads AWS configuration via `config.LoadDefaultConfig(ctx)` (standard AWS SDK env / role chain). ### Custom AWS configuration Pin a region or credentials explicitly: ```go cfg, _ := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1")) s, err := kms.NewSignerWithConfig(cfg, keyID) ``` ### Testing with a mock client ```go import "github.com/smartcontractkit/crec-sdk/transact/signer/kms" mockKMS := &mocks.KMSClient{} s, err := kms.NewSigner(ctx, keyID, kms.WithClient(mockKMS)) ``` ## Derive the signer's address Before you can provision a wallet you need the address the KMS key signs as. The signer exposes a helper: ```go import ( "github.com/aws/aws-sdk-go-v2/service/kms" awskms "github.com/smartcontractkit/crec-sdk/transact/signer/kms" "github.com/ethereum/go-ethereum/crypto" ) cfg, _ := config.LoadDefaultConfig(ctx) client := kms.NewFromConfig(cfg) pubKey, err := awskms.GetPubKeyCtx(ctx, client, keyID) if err != nil { return err } addr := crypto.PubkeyToAddress(*pubKey).Hex() fmt.Println("KMS signer address:", addr) ``` Add this address to `AllowedEcdsaSigners` when you create the wallet; see [Manage Wallet Signers](/crec/guides/wallets/manage-signers). ## Sign an operation ```go opHash, sig, err := client.Transact.SignOperation(ctx, op, s, chainSelector) ``` Internally `Sign(ctx, hash)`: 1. Calls `KMS.GetPublicKey` to retrieve the secp256k1 public key (used to disambiguate the recovery byte). 2. Calls `KMS.Sign` with `MessageType=DIGEST` and `SigningAlgorithm=ECDSA_SHA_256`. 3. Decodes the ASN.1 ECDSA signature into raw `(r, s)`. 4. Normalises `s` to the lower half of the curve (Ethereum BIP-62 rule). 5. Tries `v=0` and `v=1` in turn, picking whichever recovers to the public key returned by `GetPublicKey`. The result is a 65-byte `(r ∥ s ∥ v)` signature that the Smart Account verifies with `ecrecover` (the on-chain verifier accepts the raw recovery byte). ## End-to-end flow ```go import ( "github.com/smartcontractkit/crec-sdk/transact/signer/kms" ) s, err := kms.NewSigner(ctx, os.Getenv("KMS_KEY_ID")) if err != nil { return err } op := &types.Operation{ /* ... build as usual ... */ } opr, err := client.Transact.ExecuteOperation(ctx, channelID, s, op, chainSelector) if err != nil { return err } ``` ## IAM least-privilege policy The signer needs only `Sign` and `GetPublicKey`: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["kms:Sign", "kms:GetPublicKey"], "Resource": "arn:aws:kms:us-west-2:123456789012:key/abcd-..." } ] } ``` Avoid wildcard resources: bind the policy to the specific key ARN your service is allowed to drive. ## Operational notes - **API calls per signature.** `Sign` issues two KMS round-trips per signature: one `GetPublicKey` to disambiguate the recovery byte, then one `Sign`. Latency is dominated by the network path between your service and KMS; measure it from your own deployment. - **Throughput.** Per-account `kms:Sign` request rate is governed by AWS KMS service quotas. Confirm the current limits and any account-specific overrides in the AWS KMS console (Quotas) before sizing a high-throughput workload. - **Cost.** A `kms:Sign` call on an asymmetric key is billed per request; check the current AWS pricing page. - **Audit.** Every `Sign` shows up in CloudTrail. Pair with the [Signing Transparency](/crec/guides/operations/signing-transparency) guide to keep an off-AWS audit trail too. ## Next steps - [HashiCorp Vault Signer](/crec/guides/signers/hashicorp-vault): for self-hosted secret management. - [Smart Accounts](/crec/concepts/smart-accounts): how the recovered signer address is checked on chain. --- # HashiCorp Vault Signer Source: https://docs.chain.link/crec/guides/signers/hashicorp-vault Last Updated: 2026-08-31 The Vault signer (`github.com/smartcontractkit/crec-sdk/transact/signer/vault`) signs CRE Connect payloads using HashiCorp Vault's **Transit** secrets engine. Vault holds the key (optionally backed by an HSM) and signs digests on request. ## Key types supported | Constant | Algorithm | | ------------------------ | -------------- | | `vault.KeyTypeRSA2048` | RSA, 2048-bit | | `vault.KeyTypeRSA4096` | RSA, 4096-bit | | `vault.KeyTypeECDSAP256` | ECDSA on P-256 | | `vault.KeyTypeECDSAP384` | ECDSA on P-384 | | `vault.KeyTypeECDSAP521` | ECDSA on P-521 | ## Prerequisites - Vault cluster reachable from your service. - Transit engine mounted (default mount `transit`). - A Vault token with policies that allow `update transit/sign/` and `read transit/keys/`. ## Create a key in Vault You can create the key with the Vault CLI: ```bash vault write transit/keys/treasury-prod-eth type=rsa-2048 ``` Or directly through the Go SDK: ```go import "github.com/smartcontractkit/crec-sdk/transact/signer/vault" result, err := vault.CreateKeyInVault( "https://vault.example.com:8200", os.Getenv("VAULT_TOKEN"), "transit", "treasury-prod-eth", vault.KeyTypeRSA2048, ) if err != nil { return err } fmt.Println("modulus (hex):", result.Modulus, "exponent (hex):", "010001") // RSA exponent in Vault is 65537 ``` Save the `(modulus, exponent)` pair: you'll feed it into `AllowedRsaSigners` when you provision the wallet. `result.Modulus` is already hex-encoded, which is the format `AllowedRsaSigners` expects. ## Construct the signer ```go import "github.com/smartcontractkit/crec-sdk/transact/signer/vault" s, err := vault.NewSigner( "https://vault.example.com:8200", os.Getenv("VAULT_TOKEN"), "transit", "treasury-prod-eth", ) if err != nil { return err } ``` All four arguments are required; an empty value returns `"vaultUrl, token, mountPath, and key must be set"`. ### Inject a custom client (for tests) ```go client, _ := vaultapi.NewClient(vaultapi.DefaultConfig()) client.SetAddress("http://127.0.0.1:8200") client.SetToken("dev-only-token") s, err := vault.NewSigner("http://127.0.0.1:8200", "dev-only-token", "transit", "key", vault.WithClient(client)) ``` ## Provision the wallet's signer set Read back the key's hex-encoded modulus and pass it (with hex-encoded exponent for RSA-65537) into `wallets.Create`. Both `e` and `n` must be **`0x`-prefixed hex** strings. `GetRSAModulus()` returns hex **without** the prefix, so prepend `0x` before passing it to the API: ```go import ( apiClient "github.com/smartcontractkit/crec-api-go/client" "github.com/smartcontractkit/crec-sdk/wallets" ) modHex, err := s.GetRSAModulus() if err != nil { return err } rsaSigners := apiClient.RSASignersList{ {E: "0x010001", N: "0x" + modHex}, } w, err := client.Wallets.Create(ctx, wallets.CreateInput{ Name: "treasury-prod-eth", ChainSelector: "5009297550715157269", WalletOwnerAddress: ownerEOA.Hex(), WalletType: apiClient.Rsa, AllowedRsaSigners: &rsaSigners, StatusChannelId: &statusChannelID, // optional; receives wallet.status events }) ``` For ECDSA-P256 / P-384 / P-521 keys, use `s.Public()` to retrieve the `*ecdsa.PublicKey` and serialise it according to your wallet's expected format. ## Sign an operation ```go opHash, sig, err := client.Transact.SignOperation(ctx, op, s, chainSelector) ``` Internally `Sign(ctx, hash)`: 1. Base64-encodes the digest. 2. Calls `transit/sign/` with `prehashed=true` and `marshaling_algorithm=asn1`. 3. Strips the `vault:v1:` prefix, decodes the ASN.1 signature, and returns the raw bytes. For ECDSA-P256 the result is a DER-encoded `(r, s)` pair; for RSA it's a PKCS#1 v1.5 signature. The CRE Connect Smart Account expects this format for the corresponding `WalletType`. ## Operational notes - **Vault token lifecycle.** The signer holds a single Vault token; rotate it by re-creating the signer when the token nears expiry. Consider using AppRole for short-lived tokens. - **Latency.** Each `SignOperation` call issues one Vault sign request synchronously. Latency is dominated by the network path to Vault and Vault's own response time; measure against your own Vault deployment and provision a cluster with headroom for your peak throughput. - **Audit logging.** Vault's audit devices capture every `transit/sign` call; pair with the [Signing Transparency](/crec/guides/operations/signing-transparency) guide for a complete signer-side audit trail. - **HSM backing.** For FIPS 140-2 compliance, run Vault Enterprise with the HSM auto-unseal + entropy plugin so the Transit engine's key material lives inside the HSM. ## Vault policy ```hcl path "transit/keys/treasury-prod-eth" { capabilities = ["read"] } path "transit/sign/treasury-prod-eth" { capabilities = ["update"] } ``` Bind the policy to the AppRole / token used by the signer. ## Next steps - [Manage Wallet Signers](/crec/guides/wallets/manage-signers): encode the modulus into `AllowedRsaSigners`. - [AWS KMS Signer](/crec/guides/signers/aws-kms): alternative HSM path for AWS environments. --- # Fireblocks Signer Source: https://docs.chain.link/crec/guides/signers/fireblocks Last Updated: 2026-08-31 The Fireblocks signer (`github.com/smartcontractkit/crec-sdk/transact/signer/fireblocks`) drives a Fireblocks vault account through the Fireblocks REST API. Two flows are supported: - **`Sign(ctx, hash)`**: creates a `RAW` signing operation. Fireblocks signs the digest opaquely. - **`SignTypedData(ctx, td)`**: creates a `TYPED_MESSAGE` operation with the full EIP-712 typed data, so the Fireblocks policy engine and approvers can see what they are authorising. Both return an Ethereum-canonical 65-byte `(r, s, v)` signature. ## Prerequisites - Fireblocks API key. - RSA private key (PEM-encoded) for signing JWT requests to the Fireblocks API. - A vault account ID containing the secp256k1 signing key. - An asset ID (e.g. `ETH`, `ETH_TEST5` for Sepolia, etc.). - IAM/policy: the API user has rights to create signing operations on the target vault account. ## Construct the signer ### Explicit parameters ```go import "github.com/smartcontractkit/crec-sdk/transact/signer/fireblocks" privateKeyPEM := os.Getenv("FIREBLOCKS_RSA_PEM") // -----BEGIN RSA PRIVATE KEY-----... s, err := fireblocks.NewSigner( os.Getenv("FIREBLOCKS_API_KEY"), privateKeyPEM, "0", // vault account ID "ETH", // asset ID fireblocks.WithTimeout(60*time.Second), fireblocks.WithPollingInterval(500*time.Millisecond), ) ``` ### From environment ```go s, err := fireblocks.NewSignerFromEnv() ``` Reads: | Variable | Required | Notes | | ----------------------------- | -------- | ----------------------------------------------------------------------------------- | | `FIREBLOCKS_API_KEY` | yes | API key. | | `FIREBLOCKS_API_SECRET` | yes | Inline PEM **or** path to a PEM file. | | `FIREBLOCKS_VAULT_ACCOUNT_ID` | yes | E.g. `"0"`. | | `FIREBLOCKS_ASSET_ID` | yes | E.g. `ETH`, `ETH_TEST5`. | | `FIREBLOCKS_BASE_URL` | no | Defaults to `https://api.fireblocks.io`; set to the sandbox URL during development. | ## Derive the signer's address The signer's secp256k1 address is the EVM address Fireblocks reports for that `(vault account, asset)` pair. Read it once from the Fireblocks console (or via the Fireblocks SDK) and add it to `AllowedEcdsaSigners` when you provision the wallet; see [Manage Wallet Signers](/crec/guides/wallets/manage-signers). ## Sign with `Sign` (RAW) ```go opHash, sig, err := client.Transact.SignOperation(ctx, op, s, chainSelector) ``` Internally `Sign(ctx, hash)`: 1. POSTs `/v1/transactions` with `operation: "RAW"` and the digest in the message body. 2. Polls `/v1/transactions/{id}` every `pollingInterval` (default 500 ms) until the operation reaches a terminal status. 3. Extracts the `(r, s)` from the signed message and reconstructs the recovery byte `v` so the signature recovers to the vault's public key. 4. Returns the 65-byte `(r ∥ s ∥ v)` signature. If the operation reaches `REJECTED`, `CANCELLED`, `FAILED`, or `BLOCKED`, `Sign` returns a wrapped error containing the Fireblocks status string. If `timeout` (default 60 s) elapses first, `Sign` returns `context.DeadlineExceeded`-style error. ## Sign with `SignTypedData` (recommended for human-approved flows) ```go import "github.com/smartcontractkit/crec-sdk/transact/signer" td := &signer.TypedData{ Types: map[string][]signer.TypedDataField{ "EIP712Domain": { {Name: "name", Type: "string"}, {Name: "version", Type: "string"}, {Name: "chainId", Type: "uint256"}, {Name: "verifyingContract", Type: "address"}, }, "Operation": { /* ... */ }, "Transaction": { /* ... */ }, }, PrimaryType: "Operation", Domain: signer.TypedDataDomain{ Name: "CLLSmartAccount", Version: "1", ChainID: 1, VerifyingContract: op.Account.Hex(), }, Message: map[string]any{ /* ... build from op.EIP712Message() ... */ }, } sig, err := s.SignTypedData(ctx, td) ``` Fireblocks uses its `TYPED_MESSAGE` operation, so the policy engine and approvers see the full structured payload (domain, primary type, message fields), not an opaque hash. This is what you want any time a human is approving the operation. The CRE Connect SDK's `Transact.SignOperation` always calls `Sign(ctx, hash)`. To opt into `SignTypedData`, build a `signer.TypedData` document yourself (you can derive the fields from `op.TypedData(chainSelector)` and `op.EIP712Message()` in `crec-sdk/transact/types`, then translate them into `signer.TypedData` / `signer.TypedDataDomain`), call `s.SignTypedData(ctx, td)`, and pass the resulting signature to `Transact.SendSignedOperation`. ## Operational notes - **Latency.** Fireblocks operations are asynchronous. Each `SignOperation` polls Fireblocks every `pollingInterval` (default 500ms) until the transaction reaches a terminal status; total latency is therefore set by Fireblocks itself and any approval policy / 3rd-party screening attached to your vault. - **Status terminology.** Fireblocks statuses (`PENDING_SIGNATURE`, `PENDING_AUTHORIZATION`, `BROADCASTING`, `COMPLETED`, etc.) are independent from the CRE Connect operation status. The Fireblocks signer only returns control once the **signing** is done; it does **not** broadcast the resulting transaction. CRE Connect handles the on-chain submission. - **Policy engine.** Build allow-lists in Fireblocks for `(asset, contract address)` pairs your service needs to call. Reject operations early at Fireblocks rather than at the Smart Account. - **Sandbox.** Use `WithBaseURL("https://sandbox-api.fireblocks.io")` for the Fireblocks sandbox during development. ## Next steps - [Signing Transparency](/crec/guides/operations/signing-transparency): feed the same typed data into your audit log. - [Privy Signer](/crec/guides/signers/privy): alternative for embedded user wallets. --- # Privy Signer Source: https://docs.chain.link/crec/guides/signers/privy Last Updated: 2026-08-31 The Privy signer (`github.com/smartcontractkit/crec-sdk/transact/signer/privy`) signs CRE Connect operations through Privy's wallet-as-a-service API. It is designed for consumer applications where each end-user has their own embedded wallet. ## When to use - Per-user Smart Accounts whose signer is the user's Privy wallet. - Server-side flows that need to act on behalf of a Privy-managed wallet (e.g. policy-gated background jobs). - Existing apps already using Privy for auth and wallet provisioning. For team-owned signers (production batch jobs, treasury operations) prefer [AWS KMS](/crec/guides/signers/aws-kms), [Vault](/crec/guides/signers/hashicorp-vault), or [Fireblocks](/crec/guides/signers/fireblocks). ## Prerequisites - Privy app ID and app secret. - A Privy wallet ID for the user/account this signer drives. - Network egress to `https://api.privy.io` (or your configured base URL). ## Construct the signer ### Explicit parameters ```go import "github.com/smartcontractkit/crec-sdk/transact/signer/privy" s, err := privy.NewSigner( os.Getenv("PRIVY_APP_ID"), os.Getenv("PRIVY_APP_SECRET"), walletID, ) if err != nil { return err } ``` `walletID` is the user-specific wallet identifier returned by Privy. ### From environment ```go s, err := privy.NewSignerFromEnv() ``` Reads: | Variable | Required | Notes | | ------------------ | -------- | ----------------------------------- | | `PRIVY_APP_ID` | yes | Your Privy app ID. | | `PRIVY_APP_SECRET` | yes | Privy app secret. | | `PRIVY_WALLET_ID` | yes | Wallet ID this signer signs for. | | `PRIVY_BASE_URL` | no | Defaults to `https://api.privy.io`. | ### Inject a custom HTTP client (tests) ```go s, err := privy.NewSigner(appID, appSecret, walletID, privy.WithHTTPClient(mockHTTP), privy.WithBaseURL("https://api.privy.io"), ) ``` ## Read the wallet's address ```go addr, err := s.GetWalletAddress(ctx) if err != nil { return err } fmt.Println("Privy wallet address:", addr) ``` This is the address you add to `AllowedEcdsaSigners` when provisioning the corresponding CREC wallet; see [Manage Wallet Signers](/crec/guides/wallets/manage-signers). ## Sign an operation ```go opHash, sig, err := client.Transact.SignOperation(ctx, op, s, chainSelector) ``` Internally `Sign(ctx, hash)`: 1. Hex-encodes the digest (`0x...`). 2. POSTs `/v1/wallets/{walletID}/rpc` with `method: "secp256k1_sign"` and params `{ "message": "0x...", "encoding": "hex" }`. 3. Authenticates with Basic Auth (`appID:appSecret`) plus the `privy-app-id` header. 4. Returns the raw signature bytes from the response. The returned signature is suitable for `ecrecover` with the wallet's address. ## Per-request flow Each `Sign` call is one HTTP round-trip to Privy. There is **no asynchronous approval flow** in this signer: Privy handles authentication / authorisation policy internally based on your app config. If you want a confirmation UI, build it client-side before calling the server endpoint that triggers `SignOperation`. ## Using server-side vs. client-side The Privy signer in the CREC SDK uses the **app secret** and runs server-side. It is **not** a browser SDK. The typical architecture is: 1. The user authenticates with Privy in the browser (Privy frontend SDK). 2. The user requests an action from your service. 3. Your service constructs the `types.Operation`, calls `signer.Sign` via the Privy server-side signer, submits with `Transact.SendSignedOperation`. If you need the user to physically click "Sign" before each operation, surface a confirmation in your UI before the server-side `Sign` is invoked, and persist the user's intent (signed message, JWT, etc.) so you can prove they consented. ## Operational notes - **Throughput** is bounded by Privy's API quotas; check your plan. - **Latency.** Each `SignOperation` makes a single HTTP request to Privy's `/v1/wallets/{walletID}/rpc` endpoint with method `secp256k1_sign`. Total latency is set by Privy and the network path between your service and Privy. - **Error model.** Any non-200 from Privy surfaces as `RPC request failed with status : `. Inspect the body to distinguish auth failures (`401`), policy rejections (`403`), or wallet-not-found (`404`). ## Next steps - [Build and Sign Operations](/crec/guides/operations/build-and-sign): construct the `Operation` that the Privy signer will sign. - [Custom Signer](/crec/guides/signers/custom): if Privy's flow doesn't fit, implement your own. --- # Custom Signer Source: https://docs.chain.link/crec/guides/signers/custom Last Updated: 2026-08-31 The built-in signers (local, AWS KMS, HashiCorp Vault, Fireblocks, Privy) cover the most common production cases. For anything else, implement the `signer.Signer` interface yourself: the CRE Connect SDK will wire your custody system into the operation flow with no further changes. ## The interface The contract is intentionally tiny: ```go package signer type Signer interface { Sign(ctx context.Context, hash []byte) ([]byte, error) } type TypedDataSigner interface { SignTypedData(ctx context.Context, typedData *TypedData) ([]byte, error) } ``` Source: [`signer.go`](https://github.com/smartcontractkit/crec-sdk/blob/main/transact/signer/signer.go). `Sign` receives a 32-byte digest (the EIP-712 hash of the `Operation`) and must return a signature that recovers to the address you registered in `AllowedEcdsaSigners`. ## What "signature" means here The CRE Connect Smart Account uses on-chain `ecrecover` to verify ECDSA signatures, so: - **Length** must be exactly **65 bytes**: `r` (32) ∥ `s` (32) ∥ `v` (1). - `s` must be in the **lower half** of the secp256k1 curve order (BIP-62 / EIP-2). go-ethereum's `crypto.Sign` already enforces this. - `v` must be **27 or 28**, not `0`/`1`. If your custody system returns `0`/`1`, add 27 before returning. - The recovered address must be present in the wallet's `AllowedEcdsaSigners` list. For RSA-backed wallets the Smart Account uses RSA verification instead: the signature shape differs (PKCS#1 v1.5 over the digest); see [HashiCorp Vault Signer](/crec/guides/signers/hashicorp-vault). ## Minimal example: Multisig approval service ```go package mysigner import ( "context" "fmt" "github.com/smartcontractkit/crec-sdk/transact/signer" ) type MultisigSigner struct { endpoint string apiKey string keyID string expected common.Address http *http.Client } var _ signer.Signer = (*MultisigSigner)(nil) func New(endpoint, apiKey, keyID string, expected common.Address) *MultisigSigner { return &MultisigSigner{ endpoint: endpoint, apiKey: apiKey, keyID: keyID, expected: expected, http: &http.Client{Timeout: 30 * time.Second}, } } func (s *MultisigSigner) Sign(ctx context.Context, hash []byte) ([]byte, error) { body, _ := json.Marshal(map[string]any{ "key_id": s.keyID, "digest": "0x" + hex.EncodeToString(hash), }) req, _ := http.NewRequestWithContext(ctx, "POST", s.endpoint+"/sign", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+s.apiKey) req.Header.Set("Content-Type", "application/json") resp, err := s.http.Do(req) if err != nil { return nil, fmt.Errorf("multisig sign: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("multisig sign returned %d: %s", resp.StatusCode, string(b)) } var out struct{ Signature string `json:"signature"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, err } sig, err := hex.DecodeString(strings.TrimPrefix(out.Signature, "0x")) if err != nil { return nil, err } if len(sig) != 65 { return nil, fmt.Errorf("expected 65-byte signature, got %d", len(sig)) } if sig[64] <= 1 { sig[64] += 27 } if err := s.verifyRecover(hash, sig); err != nil { return nil, fmt.Errorf("signature does not recover to expected signer: %w", err) } return sig, nil } func (s *MultisigSigner) verifyRecover(hash, sig []byte) error { pub, err := crypto.SigToPub(hash, sig) if err != nil { return err } if got := crypto.PubkeyToAddress(*pub); got != s.expected { return fmt.Errorf("recovered %s, expected %s", got.Hex(), s.expected.Hex()) } return nil } ``` Use it exactly like a built-in signer: ```go ms := mysigner.New("https://multisig.example/api", os.Getenv("APPROVAL_API_KEY"), "treasury-key", expectedAddr) opr, err := client.Transact.ExecuteOperation(ctx, channelID, ms, op, chainSelector) ``` ## Optionally implement `TypedDataSigner` If your custody system supports typed-data signing natively (e.g. so it can render the message to approvers), also implement `signer.TypedDataSigner`: ```go func (s *MultisigSigner) SignTypedData(ctx context.Context, td *signer.TypedData) ([]byte, error) { body, _ := json.Marshal(map[string]any{ "key_id": s.keyID, "typed_data": td, }) // ... POST and return signature ... } ``` The CRE Connect SDK's `Transact.SignOperation` always calls `Sign(ctx, hash)` today. If you want to use `SignTypedData`, build the typed-data document yourself with `op.TypedData(chainID)` and call `SignTypedData` directly, then submit the resulting signature with `Transact.SendSignedOperation`. ## Register the signer's address Whatever address your custody system signs as must appear in the wallet's `AllowedEcdsaSigners` (or `AllowedRsaSigners` for RSA). When you provision the wallet, derive the address up front: ```go addr := common.HexToAddress(myCustodySystem.GetSignerAddress()) ecdsa := []string{addr.Hex()} client.Wallets.Create(ctx, wallets.CreateInput{ // ... AllowedEcdsaSigners: &ecdsa, }) ``` See [Manage Wallet Signers](/crec/guides/wallets/manage-signers) for the full provisioning flow. ## Hardening checklist - **Time-out the upstream call.** Don't let a hung custody backend pin a CRE Connect goroutine indefinitely; give every HTTP / gRPC call a `context.WithTimeout`. - **Honour `ctx`.** If `ctx.Done()` fires, abandon any polling loop and return `ctx.Err()`. - **Idempotence.** If your custody system retries internally, make sure the hash you sign is the same on retry. The Smart Account's `op.ID` already provides operation-level idempotence. - **Audit log.** Every `Sign` call is a security-critical event. Persist `(timestamp, op.ID, signer key, requesting service, recovered address)` somewhere immutable. - **Test with the real Smart Account.** Use the [Local Signer](/crec/guides/signers/local) in tests to confirm the wallet accepts your signatures, then swap in your custom signer behind the same interface. ## Next steps - [Build and Sign Operations](/crec/guides/operations/build-and-sign): the operation contract your `Sign` is fulfilling. - [Smart Accounts](/crec/concepts/smart-accounts): how the recovered address is checked on chain. - [Signing Transparency](/crec/guides/operations/signing-transparency): what to expose to humans before your custom signer signs. --- # Extensions Overview Source: https://docs.chain.link/crec/extensions Last Updated: 2026-08-31 CRE Connect **extensions** are first-party Go modules that wrap the generic `crec-sdk` with service-specific helpers. Each extension provides three things: 1. **An operation builder** that packs calldata for the service's smart contracts so you don't have to manage the ABI yourself. 2. **A typed event decoder** so that `dta.SubscriptionRequested` arrives as a Go struct, not a `map[string]any`. 3. **One-call watcher provisioning.** CRE Connect ships pre-packaged watchers for the service so a single SDK call provisions every watcher the service needs. Extensions are independent Go modules; you import only the ones you need. ## Available extensions | Extension | Module | Use when | | ----------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------- | | **DTA** ([Digital Transfer Agent](https://docs.chain.link/dta-technical-standard/)) | `github.com/smartcontractkit/crec-sdk-ext-dta` | Tokenised funds: subscriptions, redemptions, distributor lifecycle. | The DTA extension repository uses **contract versioning**: each deployed contract ABI is exposed as a separate import path (`/v1`, `/v2`, …) under the same Go module. Per the extension's [README](https://github.com/smartcontractkit/crec-sdk-ext-dta#readme), this versioning matches the on-chain contract ABI version and is independent of Go module semantic versioning. The CRE Connect documentation focuses on the **v2** import path; if you operate against v1 contracts, import the corresponding `/v1` sub-package and consult the extension README for v1-specific operation signatures. ## Anatomy of an extension Take DTA v2 as the example. The module exposes three sub-packages: ``` crec-sdk-ext-dta/v2 ├── / // Decode helper: v2.DecodeFromEvent ├── /events // Typed event structs + EventName constants + decoders ├── /operations // Extension client: PrepareXxxOperation builders └── /watcher/bundle // bundle.Get(): service name + ABIs (consumed by CRE Connect) ``` `v2/operations` exposes an `Extension` struct with one `PrepareXxxOperation` method per on-chain action. Each method returns a `*types.Operation` ready to sign and send through `crec-sdk/transact`: ```go import "github.com/smartcontractkit/crec-sdk-ext-dta/v2/operations" ext, err := operations.New(&operations.Options{ AccountAddress: smartAccount.Hex(), DTARequestManagementAddress: managementAddr.Hex(), DTARequestSettlementAddress: settlementAddr.Hex(), }) op, err := ext.PrepareRequestSubscriptionOperation(fundAdmin, fundTokenId, amount, referenceID) ``` `v2/events` declares one Go struct per Solidity event (e.g. `SubscriptionRequested`, `DistributorRegistered`) and an `EventName` enum. The root `v2.DecodeFromEvent(ctx, ev)` helper combines watcher decoding with on-chain reference-data enrichment. `v2/watcher/bundle` ships the service descriptor (name + ABIs) that CRE Connect uses to expose this service to `watchers.CreateWithService`. You typically don't import this package directly; only use it if you want to inspect the published service name or event list from your own code. ## Why extensions matter - **No ABI bookkeeping.** Calldata packing happens inside the extension; bumping a contract version is a single-module dependency upgrade. - **Typed events.** `events.SubscriptionRequested` carries `FundAdminAddr common.Address`, `Amount *big.Int`, etc.: no string casting. - **One-call watcher provisioning.** Pass `Service: "dta.v2"` to `watchers.CreateWithService` and CRE Connect sets up every watcher in `bundle.Get().Events`. - **Backwards-compatible upgrades.** When an event schema or operation signature changes, the extension version bumps and you get a compile error rather than a silent runtime drift. ## Using an extension Three integration points: ```go import ( crec "github.com/smartcontractkit/crec-sdk" dta "github.com/smartcontractkit/crec-sdk-ext-dta/v2" dtaop "github.com/smartcontractkit/crec-sdk-ext-dta/v2/operations" ) client, _ := crec.NewClient(baseURL, apiKey) ext, _ := dtaop.New(&dtaop.Options{ AccountAddress: smartAccount.Hex(), DTARequestManagementAddress: mgmtAddr.Hex(), DTARequestSettlementAddress: settleAddr.Hex(), }) op, _ := ext.PrepareRequestSubscriptionOperation(fundAdmin, fundTokenId, amount, refID) opr, _ := client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector) events, _, _ := client.Events.Poll(ctx, channelID, nil) for _, ev := range events { decoded, err := dta.DecodeFromEvent(ctx, ev) if err != nil { continue } fmt.Println(decoded.EventName(), decoded.ConcreteEvent) } ``` ## What's next - [DTA Overview](/crec/extensions/dta/): the `dta.v2` extension end-to-end. - [Concepts: Extensions](/crec/concepts/extensions): design philosophy and combination patterns. --- # DTA v2 Overview Source: https://docs.chain.link/crec/extensions/dta Last Updated: 2026-08-31 The DTA v2 extension (`github.com/smartcontractkit/crec-sdk-ext-dta/v2`) packages everything needed to drive the [DTA contracts](https://docs.chain.link/dta-technical-standard/concepts/architecture) from CRE Connect: - An `Extension` that builds typed `*types.Operation` payloads for every DTA contract function. - An `events` sub-package with one Go struct per emitted event and a `DecodeFromEvent` helper. - A `watcher/bundle` package whose service descriptor CRE Connect loads to expose `dta.v2` as a `Service` on watchers: one SDK call provisions every watcher the integration needs. ## Module layout ``` crec-sdk-ext-dta/v2 ├── v2.go // root package; exposes DecodeFromEvent + DecodedEvent ├── operations/ // PrepareXxxOperation builders for every DTA function │ ├── extension_gen.go // Options, New, Extension struct │ ├── operations_gen.go // 21 generated PrepareXxxOperation methods │ └── operations.go // 2 hand-written helpers (with token approval, etc.) ├── events/ // Typed event structs + EventName enum │ ├── events_gen.go // 28 EventName constants + payload structs │ ├── decode_gen.go // EventDecoders map per event │ └── types.go // Shared types (FundTokenData, DistributorRequest, RequestStatus, ...) ├── watcher/ │ └── bundle/bundle.go // bundle.Get(): service name + ABIs (consumed by CRE Connect) └── fee_manager/ // Optional fee-manager sub-extension (out of scope) ``` ## When to use the DTA v2 extension Use it whenever your application calls the [DTA Request Management or DTA Request Settlement contracts](https://docs.chain.link/dta-technical-standard/concepts/architecture). The extension covers every public function on both contracts, so any flow described in the [DTA standard](https://docs.chain.link/dta-technical-standard/), including onboarding, allowlisting, [subscriptions / redemptions](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle), and [settlement](https://docs.chain.link/dta-technical-standard/concepts/payment-modes), is one `PrepareXxxOperation` call away. If you are emitting verifiable events from a **different** smart-contract suite (i.e. not DTA), write a custom watcher with `Watchers.CreateWithABI` instead. ## Construct the extension ```go import ( crec "github.com/smartcontractkit/crec-sdk" dtaop "github.com/smartcontractkit/crec-sdk-ext-dta/v2/operations" ) client, _ := crec.NewClient(baseURL, apiKey) ext, err := dtaop.New(&dtaop.Options{ AccountAddress: smartAccount.Hex(), DTARequestManagementAddress: managementAddr.Hex(), DTARequestSettlementAddress: settlementAddr.Hex(), }) if err != nil { return err } ``` `Options` are validated: - `AccountAddress` must be a hex address (this is the `op.Account` your operations target). - `DTARequestManagementAddress` and `DTARequestSettlementAddress` must both be valid hex addresses. - `Deadline` is optional; if set, every prepared operation gets that deadline by default. The service descriptor (`dtabundle.Get()`) is consumed by CRE Connect, not by the SDK client. You only need to import it when you want to inspect its metadata (service name, event list) from your application code. ## Categories of operations The 23 `PrepareXxxOperation` methods split into four groups: | Group | Methods | Page | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | | **Subscriptions & redemptions** | `PrepareRequestSubscriptionOperation`, `PrepareRequestSubscriptionWithTokenApprovalOperation`, `PrepareRequestRedemptionOperation`, `PrepareCancelDistributorRequestOperation`, `PrepareProcessDistributorRequestOperation`, `PrepareCompleteRequestProcessingOperation` | [Subscriptions & Redemptions](/crec/extensions/dta/subscriptions-redemptions) | | **Fund & distributor management** | `PrepareRegisterFundAdminOperation`, `PrepareRegisterFundTokenOperation`, `PrepareRegisterDistributorOperation`, `PrepareEnableFundTokenOperation`, `PrepareDisableFundTokenOperation`, `PrepareAuthorizeDistributorForTokenOperation`, `PrepareRevokeDistributorForTokenOperation`, `PrepareAllowDistributorForTokenOperation`, `PrepareDisallowDistributorForTokenOperation` | [Fund & Distributor Management](/crec/extensions/dta/fund-and-distributors) | | **Cross-DTA settlement** | `PrepareAllowDTAOperation`, `PrepareDisallowDTAOperation`, `PrepareTransferDTARequestSettlementOwnershipOperation`, `PrepareRenounceDTARequestSettlementOwnershipOperation` | [Fund & Distributor Management](/crec/extensions/dta/fund-and-distributors) | | **Operational** | `PrepareSetManagementCCIPGasLimitOperation`, `PrepareSetSettlementCCIPGasLimitOperation`, `PrepareWithdrawManagementTokensOperation`, `PrepareWithdrawSettlementTokensOperation` | (covered inline in [Fund & Distributor Management](/crec/extensions/dta/fund-and-distributors)) | ## Sample flow End-to-end: register a distributor, subscribe to a fund token, watch for the resulting events. ```go op, err := ext.PrepareRegisterDistributorOperation(distributorWalletAddr) if err != nil { return err } if _, err := client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector); err != nil { return err } op2, _ := ext.PrepareRequestSubscriptionWithTokenApprovalOperation( fundAdminAddr, fundTokenId, amount, referenceID, paymentTokenAddr, ) if _, err := client.Transact.ExecuteOperation(ctx, channelID, signer, op2, chainSelector); err != nil { return err } events, _, _ := client.Events.Poll(ctx, channelID, nil) for _, ev := range events { decoded, err := dtav2.DecodeFromEvent(ctx, ev) if err != nil { continue } switch v := decoded.ConcreteEvent.(type) { case dtaevents.SubscriptionRequested: log.Printf("subscription %s shares pending", v.RequestId.Hex()) case dtaevents.DistributorRegistered: log.Printf("distributor %s online", v.DistributorAddr.Hex()) } } ``` ## Watcher provisioning Pass `Service: "dta.v2"` to `Watchers.CreateWithService` along with the events you want to subscribe to. The DTA service publishes 11 events you can subscribe to (the full list is exposed via `bundle.Get().Events`); list whichever subset your integration needs. ```go import "github.com/smartcontractkit/crec-sdk/watchers" w, err := client.Watchers.CreateWithService(ctx, channelID, watchers.CreateWithServiceInput{ Name: "dta-management", ChainSelector: chainSelector, Service: "dta.v2", Address: managementAddr.Hex(), Events: []string{ "SubscriptionRequested", "RedemptionRequested", "DistributorRequestProcessing", "DistributorRequestProcessed", }, }) ``` CRE Connect owns the ABIs: you only need to know the event names you want to receive. ## What's next - [Subscriptions & Redemptions](/crec/extensions/dta/subscriptions-redemptions) - [Fund & Distributor Management](/crec/extensions/dta/fund-and-distributors) - [DTA Events](/crec/extensions/dta/events) --- # DTA Subscriptions and Redemptions Source: https://docs.chain.link/crec/extensions/dta/subscriptions-redemptions Last Updated: 2026-08-31 This page covers the SDK calls for the investor-facing DTA flows, `requestSubscription`, `requestRedemption`, and `cancelDistributorRequest`, and the fund-admin operations that drive a request through to settlement (`processDistributorRequest`, `completeRequestProcessing`). ## Prerequisites Before any subscription or redemption can succeed, the actors and fund must be set up per the [DTA standard](https://docs.chain.link/dta-technical-standard/actors). In SDK terms: - The fund admin must be registered (`PrepareRegisterFundAdminOperation`; see [Fund & Distributor Management](/crec/extensions/dta/fund-and-distributors)). - The fund token must be registered and enabled. - The distributor must be registered and authorised for the fund token. You also need the extension constructed: ```go import ( dtaop "github.com/smartcontractkit/crec-sdk-ext-dta/v2/operations" ) ext, err := dtaop.New(&dtaop.Options{ AccountAddress: smartAccount.Hex(), DTARequestManagementAddress: mgmtAddr.Hex(), DTARequestSettlementAddress: settleAddr.Hex(), }) ``` ## Request a subscription Two flavours: with and without an inline token approval. ### Without approval (token already approved) ```go op, err := ext.PrepareRequestSubscriptionOperation( fundAdminAddr, fundTokenId, // [32]byte amount, // *big.Int: payment-token units referenceID, // [32]byte: your idempotency key ) if err != nil { return err } opr, err := client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector) ``` Underlying call: `DTARequestManagement.requestSubscription(fundAdminAddr, fundTokenId, amount, referenceID)` Use this when the investor's Smart Account has previously approved the management contract for `amount`. ### With inline `approve` (recommended) For first-time subscriptions or whenever the existing allowance is insufficient, batch `approve` and `requestSubscription` into a single atomic operation: ```go op, err := ext.PrepareRequestSubscriptionWithTokenApprovalOperation( fundAdminAddr, fundTokenId, amount, referenceID, paymentTokenAddress, // ERC-20 used for payment (e.g. USDC) ) ``` The returned operation contains **two transactions**: 1. `paymentToken.approve(managementAddr, amount)` 2. `DTARequestManagement.requestSubscription(fundAdminAddr, fundTokenId, amount, referenceID)` Either both succeed atomically or neither does; see [Batch Multiple Transactions](/crec/guides/operations/batch-transactions). ### Resulting events A successful `requestSubscription` emits one `SubscriptionRequested` event: ```go type SubscriptionRequested struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address ReferenceID common.Hash RequestId common.Hash Amount *big.Int CreatedAt uint64 } ``` Persist `RequestId`: it threads through the entire request lifecycle. ## Request a redemption ```go op, err := ext.PrepareRequestRedemptionOperation( fundAdminAddr, fundTokenId, shares, // *big.Int: fund-token units referenceID, ) ``` Underlying call: `DTARequestManagement.requestRedemption(fundAdminAddr, fundTokenId, shares, referenceID)` The Smart Account must already hold (or be approved for) the fund tokens being redeemed. Build a separate batched operation if you need to combine `approve(fundToken, mgmt, shares) + requestRedemption(...)`. ### Resulting events ```go type RedemptionRequested struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address ReferenceID common.Hash RequestId common.Hash Shares *big.Int CreatedAt uint64 } ``` ## Cancel a request Investors (or the distributor on their behalf) can cancel a request before it is picked up: ```go op, err := ext.PrepareCancelDistributorRequestOperation(requestId) ``` Underlying call: `DTARequestManagement.cancelDistributorRequest(requestId)`. Emits `DistributorRequestCanceled`: ```go type DistributorRequestCanceled struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address RequestId common.Hash } ``` A cancelled request cannot be reopened: re-issue with a fresh `referenceID` if needed. ## Process a request (fund admin) The fund admin's worker picks up an open request: ```go op, err := ext.PrepareProcessDistributorRequestOperation(requestId) ``` Underlying call: `DTARequestManagement.processDistributorRequest(requestId)`. Emits `DistributorRequestProcessing`: ```go type DistributorRequestProcessing struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address RequestId common.Hash Shares *big.Int Amount *big.Int } ``` This event is enriched with on-chain reference data (`distributor_request`, `fund_token_data`); see [DTA Events](/crec/extensions/dta/events). ## Complete a request (settlement) After settlement processing succeeds (or fails) on the fund side: ```go op, err := ext.PrepareCompleteRequestProcessingOperation( requestId, success, // bool: true if shares were minted / payment was made errBytes, // []byte: abi-encoded revert reason if !success revertOnErr, // bool: true to bubble error up to the caller ) ``` Underlying call: `DTARequestSettlement.completeRequestProcessing(requestId, success, err, revertOnErr)`. Emits `DistributorRequestProcessed`: ```go type DistributorRequestProcessed struct { RequestId common.Hash Shares *big.Int Status RequestStatus Error []byte } ``` `Status` is the [DTA request state machine](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle), surfaced from the Solidity enum as a Go `uint8`. The SDK exposes named constants for every value: | Constant | Standard state | | ------------------------- | -------------------------------------------------------------------------------------------------- | | `RequestStatusNone` | Zero value (request not yet recorded) | | `RequestStatusPending` | [Pending](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle#pending) | | `RequestStatusProcessing` | [Processing](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle#processing) | | `RequestStatusProcessed` | [Processed](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle#processed) | | `RequestStatusCanceled` | [Canceled](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle#canceled) | | `RequestStatusFailed` | [Failed](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle#failed) | For the full state diagram and NAV-TTL behavior (manual vs automatic processing), see the [request lifecycle](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle) page. ## Where the SDK fits in the standard's flow The [DTA standard](https://docs.chain.link/dta-technical-standard/how-it-works) describes a four-step subscription flow (Request Submission → NAV Update → Request Processing → Token Minting & Escrow → Settlement). The CRE Connect SDK is the **submission and observation layer** for that flow: | Standard step | SDK call | Resulting event (decoded via `dtav2.DecodeFromEvent`) | | -------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Request Submission | `PrepareRequestSubscriptionWithTokenApprovalOperation` + `ExecuteOperation` | `SubscriptionRequested` | | Request Processing (admin) | `PrepareProcessDistributorRequestOperation` + `ExecuteOperation` | `DistributorRequestProcessing` | | Settlement completion | `PrepareCompleteRequestProcessingOperation` + `ExecuteOperation` | `DistributorRequestProcessed` (+ `DTASettlementOpened` / `DTASettlementClosed` for cross-chain) | Subscribe to these events via the `dta.v2` service (`Service: "dta.v2"` on `Watchers.CreateWithService`); see [DTA Events](/crec/extensions/dta/events) for every payload shape. ## Idempotency with `referenceID` Always pass a stable `referenceID` (a 32-byte hash of your client-side request ID). The contract uses it to detect duplicates and to surface the chain-side `RequestId` back to your application via the emitted event. ```go import "github.com/ethereum/go-ethereum/crypto" referenceID := crypto.Keccak256Hash([]byte(yourInternalRequestUUID)) var refArr [32]byte copy(refArr[:], referenceID.Bytes()) ``` ## Next steps - [Fund & Distributor Management](/crec/extensions/dta/fund-and-distributors): set up admins, tokens, and distributors before subscribing. - [DTA Events](/crec/extensions/dta/events): full event reference, including settlement events. - [Submit and Track Operations](/crec/guides/operations/submit-and-track): drive the operation through to `confirmed`. --- # DTA Fund and Distributor Management Source: https://docs.chain.link/crec/extensions/dta/fund-and-distributors Last Updated: 2026-08-31 This page covers the **operator-side** flows of DTA v2: onboarding fund admins, registering fund tokens, managing distributors, and wiring cross-DTA settlement. Investor-facing flows (subscriptions, redemptions) live in [Subscriptions and Redemptions](/crec/extensions/dta/subscriptions-redemptions). All examples assume an extension constructed as in [DTA Overview](/crec/extensions/dta/): ```go ext, _ := dtaop.New(&dtaop.Options{ AccountAddress: smartAccount.Hex(), DTARequestManagementAddress: mgmtAddr.Hex(), DTARequestSettlementAddress: settleAddr.Hex(), }) ``` ## Fund admin onboarding Register once per Smart Account that will act as a [Fund Administrator](https://docs.chain.link/dta-technical-standard/actors#fund-administrator): ```go op, err := ext.PrepareRegisterFundAdminOperation() ``` Underlying call: `DTARequestManagement.registerFundAdmin()`. Emits `FundAdminRegistered`: ```go type FundAdminRegistered struct { FundAdminAddr common.Address } ``` ## Register a fund token Each tokenised fund is registered once with its complete configuration: ```go import dtaevents "github.com/smartcontractkit/crec-sdk-ext-dta/v2/events" tokenData := dtaevents.FundTokenData{ FundTokenAddr: fundTokenAddr, NavFeedDecimals: 8, PurchaseTokenRoundingDecimals: 6, PurchaseTokenDecimals: 6, // e.g. USDC FundRoundingDecimals: 18, FundTokenDecimals: 18, RequestsPerDay: 4, NavAddr: navOracleAddr, TokenChainSelector: 5009297550715157269, // Ethereum mainnet DtaRequestSettlementAddr: settleAddr, TimezoneOffsetSecs: big.NewInt(0), NavTTL: big.NewInt(86400), PaymentInfo: dtaevents.DTAPayment{ OffChainPaymentCurrency: 0, PaymentTokenSourceAddr: paymentTokenAddr, PaymentTokenDestAddr: paymentDestAddr, }, } op, err := ext.PrepareRegisterFundTokenOperation(fundTokenId, tokenData) ``` Underlying call: `DTARequestManagement.registerFundToken(fundTokenId, tokenData)`. Emits `FundTokenRegistered`: ```go type FundTokenRegistered struct { FundAdminAddr common.Address FundTokenId common.Hash FundTokenAddr common.Address NavAddr common.Address TokenChainSelector uint64 } ``` `fundTokenId` is the [Fund Token ID](https://docs.chain.link/dta-technical-standard/reference/glossary#fund-token-id), a `[32]byte` (`bytes32`) identifier defined by the DTA Technical Standard. Choose a derivation strategy that fits your fund-token registry; see the DTA Standard glossary for the canonical definition. ### Toggle availability A fund token can be temporarily disabled (rejecting new requests) without re-registering: ```go op, _ := ext.PrepareDisableFundTokenOperation(fundTokenId) op, _ := ext.PrepareEnableFundTokenOperation(fundTokenId) ``` Underlying calls: `disableFundToken(fundTokenId)` / `enableFundToken(fundTokenId)`. Both emit no separate event; observe via the on-chain reference data. ## Distributor lifecycle A [Fund Distributor](https://docs.chain.link/dta-technical-standard/actors#fund-distributor) is a Smart Account authorised to submit subscription / redemption requests on behalf of investors. ### Register a distributor ```go op, err := ext.PrepareRegisterDistributorOperation(distributorWalletAddr) ``` Underlying call: `DTARequestManagement.registerDistributor(distributorWalletAddr)`. Emits `DistributorRegistered`: ```go type DistributorRegistered struct { DistributorAddr common.Address } ``` ### Authorise / revoke a distributor for a fund token (v2 model) DTA v2 uses an **authorise / revoke** model rather than the v1 allow / disallow flag. The fund admin signs `authorize` or `revoke` calls to set per-token distributor permissions: ```go op, _ := ext.PrepareAuthorizeDistributorForTokenOperation(fundAdminAddr, fundTokenId, distributorAddr) op, _ := ext.PrepareRevokeDistributorForTokenOperation(fundAdminAddr, fundTokenId, distributorAddr) ``` Underlying calls: - `authorizeDistributorForToken(fundAdminAddr, fundTokenId, distributorAddr)` - `revokeDistributorForToken(fundAdminAddr, fundTokenId, distributorAddr)` Both emit `DistributorAuthorizationUpdated`: ```go type DistributorAuthorizationUpdated struct { DistributorAddr common.Address FundAdminAddr common.Address FundTokenId common.Hash Authorized bool } ``` ### Allow-list (per-token, no admin involvement) For lower-trust scenarios where the fund admin already pre-approved a category of distributors, the per-token allow-list is the lightweight knob: ```go op, _ := ext.PrepareAllowDistributorForTokenOperation(fundTokenId, distributorAddr) op, _ := ext.PrepareDisallowDistributorForTokenOperation(fundTokenId, distributorAddr) ``` Emits `FundTokenAllowlistUpdated`: ```go type FundTokenAllowlistUpdated struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address Allowed bool } ``` ## Cross-DTA settlement For [cross-chain settlement](https://docs.chain.link/dta-technical-standard/concepts/payment-modes#3-cross-chain-onchain-settlement) topologies, configure which remote DTA contracts are allowed to settle against your settlement contract. The [Chain Selector](https://docs.chain.link/dta-technical-standard/reference/glossary#chain-selector) identifies the remote chain. ```go op, _ := ext.PrepareAllowDTAOperation( dtaAddr, // remote DTA contract dtaChainSelector, // CCIP chain selector fundAdminAddr, fundTokenId, fundTokenAddr, dtaevents.TokenMintTypeMint, // or TokenMintTypeIssueTokens dtaevents.TokenBurnTypeBurn, // or BurnFrom / BurnWithReason / ForceBurn ) op, _ := ext.PrepareDisallowDTAOperation(dtaAddr, dtaChainSelector, fundAdminAddr, fundTokenId) ``` Emits `DTAAdded` / `DTARemoved`: ```go type DTAAdded struct { DtaAddr common.Address DtaChainSelector uint64 FundAdminAddr common.Address FundTokenId common.Hash FundTokenAddr common.Address } type DTARemoved struct { DtaAddr common.Address DtaChainSelector uint64 FundAdminAddr common.Address FundTokenId common.Hash } ``` Match the `TokenMintType` / `TokenBurnType` to the fund token contract's interface; see the v2 events `types.go` for the full mapping. ## Settlement contract operations Two ownership operations bind to the settlement contract: ```go op, _ := ext.PrepareTransferDTARequestSettlementOwnershipOperation(newOwner) op, _ := ext.PrepareRenounceDTARequestSettlementOwnershipOperation() ``` These are standard OpenZeppelin `Ownable` operations. ### Token withdrawals Both contracts expose a `withdrawTokens` operation for sweeping accidentally-sent ERC-20 balances: ```go op, _ := ext.PrepareWithdrawManagementTokensOperation(token, recipient, amount) op, _ := ext.PrepareWithdrawSettlementTokensOperation(token, recipient, amount) ``` Emits `TokenWithdrawn`: ```go type TokenWithdrawn struct { Token common.Address Recipient common.Address Amount *big.Int } ``` ### CCIP gas-limit tuning For cross-chain operations, both contracts let the owner adjust the CCIP message gas limit: ```go op, _ := ext.PrepareSetManagementCCIPGasLimitOperation(big.NewInt(500_000)) op, _ := ext.PrepareSetSettlementCCIPGasLimitOperation(big.NewInt(500_000)) ``` Tune in response to gas-limit-exceeded settlement failures. ## Suggested onboarding sequence ``` 1. PrepareRegisterFundAdminOperation (fund admin) 2. PrepareRegisterFundTokenOperation (fund admin, per token) 3. PrepareEnableFundTokenOperation (fund admin) 4. PrepareRegisterDistributorOperation (distributor) 5. PrepareAuthorizeDistributorForTokenOperation (fund admin, per (token, distributor)) 6. (optionally) PrepareAllowDTAOperation (cross-chain DTA) 7. Investors can now subscribe / redeem ``` ## Next steps - [Subscriptions and Redemptions](/crec/extensions/dta/subscriptions-redemptions): investor-facing flows. - [DTA Events](/crec/extensions/dta/events): every emitted event with its decoded struct. --- # DTA Events Reference Source: https://docs.chain.link/crec/extensions/dta/events Last Updated: 2026-08-31 The DTA v2 extension provisions watchers for every event emitted by `DTARequestManagement` and `DTARequestSettlement`. This page enumerates each one, its payload, and the operation(s) that emit it. For the **state semantics** behind these events, including when a request is `Pending` vs `Processing` vs `Processed`, how NAV TTL changes the flow, and how the [settlement model](https://docs.chain.link/dta-technical-standard/concepts/payment-modes) determines what fires, see the [DTA request lifecycle](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle). ## Decoder dispatch Use the root-package helper to decode any DTA event in one call: ```go import ( dtav2 "github.com/smartcontractkit/crec-sdk-ext-dta/v2" dtaevents "github.com/smartcontractkit/crec-sdk-ext-dta/v2/events" ) decoded, err := dtav2.DecodeFromEvent(ctx, ev) if err != nil { return err } switch v := decoded.ConcreteEvent.(type) { case dtaevents.SubscriptionRequested: // ... case dtaevents.RedemptionRequested: // ... } if decoded.FundTokenData != nil { fmt.Println("fund token NAV addr:", decoded.FundTokenData.NavAddr) } if decoded.DistributorRequest != nil { fmt.Println("status:", decoded.DistributorRequest.Status) } ``` `DecodedEvent` carries the raw `WatcherEventPayload`, the typed `ConcreteEvent`, and three enrichment fields (`FundTokenData`, `DistributorRequest`, `PaymentRequests`) populated from the verifiable event's reference data when present. ## Event catalogue The full `EventName` enum (from `events_gen.go`): ``` AnswerUpdated CCIPMessageDecodeFailed CCIPMessageHandleFailed DTAAdded DTARemoved DTASettlementClosed DTASettlementOpened DistributorAuthorizationUpdated DistributorRegistered DistributorRequestCanceled DistributorRequestProcessed DistributorRequestProcessing EmptyRequestType FundAdminRegistered FundTokenAllowlistUpdated FundTokenRegistered Initialized InvalidDTARequestSettlement InvalidSubscriptionCrossChainPayment MessageFailed NativeFundsRecovered OwnershipTransferred RedemptionRequested RequestAlreadyProcessed SettlementFailed SubscriptionRequested TokenWithdrawn UnauthorizedSenderDTA Unknown ``` The `dta.v2` service publishes the subset that downstream applications typically subscribe to (exposed via `bundle.Get().Events`). Other events still flow through CRE Connect; they just aren't part of the `dta.v2` service. ## Investor lifecycle events ### `SubscriptionRequested` Emitted by `DTARequestManagement.requestSubscription` (with or without inline approval). ```go type SubscriptionRequested struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address ReferenceID common.Hash RequestId common.Hash Amount *big.Int CreatedAt uint64 } ``` ### `RedemptionRequested` Emitted by `DTARequestManagement.requestRedemption`. ```go type RedemptionRequested struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address ReferenceID common.Hash RequestId common.Hash Shares *big.Int CreatedAt uint64 } ``` ### `DistributorRequestCanceled` Emitted by `cancelDistributorRequest`. ```go type DistributorRequestCanceled struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address RequestId common.Hash } ``` ### `DistributorRequestProcessing` Emitted by `processDistributorRequest`. Enriched with `DistributorRequest` + `FundTokenData` reference data. ```go type DistributorRequestProcessing struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address RequestId common.Hash Shares *big.Int Amount *big.Int } ``` ### `DistributorRequestProcessed` Emitted by `completeRequestProcessing`. Final state of a request. ```go type DistributorRequestProcessed struct { RequestId common.Hash Shares *big.Int Status RequestStatus // Pending / Processing / Processed / Canceled / Failed Error []byte // ABI-encoded revert reason on failure } ``` ## Cross-DTA settlement events ### `DTASettlementOpened` Emitted by `DTARequestSettlement` when a settlement run is initiated. Enriched with `payment_request` reference data. ```go type DTASettlementOpened struct { FundAdminAddr common.Address FundTokenId common.Hash RequestType DistributorRequestType // Subscription / Redemption DistributorAddr common.Address DtaChainSelector uint64 DtaAddr common.Address RequestId common.Hash DistributorWalletAddr common.Address Shares *big.Int Amount *big.Int Currency uint8 } ``` ### `DTASettlementClosed` Emitted at the end of a settlement run. ```go type DTASettlementClosed struct { FundAdminAddr common.Address FundTokenId common.Hash RequestType DistributorRequestType DistributorAddr common.Address DtaChainSelector uint64 DtaAddr common.Address RequestId common.Hash Success bool Err []byte } ``` ### `SettlementFailed` Emitted when an individual settlement leg fails (e.g. payment token transfer revert). ```go type SettlementFailed struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address DtaChainSelector uint64 DtaAddr common.Address PaymentTokenAddr common.Address DistributorWalletAddr common.Address RequestId common.Hash Shares *big.Int Amount *big.Int ErrData []byte } ``` ### `InvalidDTARequestSettlement` Emitted when an inbound CCIP message references a settlement contract that doesn't match the locally configured one. ```go type InvalidDTARequestSettlement struct { FundAdminAddr common.Address FundTokenId common.Hash RequestId common.Hash ActualChainSelector uint64 ActualDTARequestSettlementAddr common.Address } ``` ### `InvalidSubscriptionCrossChainPayment` Emitted when a cross-chain subscription payment fails token / amount checks. ```go type InvalidSubscriptionCrossChainPayment struct { FundAdminAddr common.Address FundTokenId common.Hash RequestId common.Hash DtaChainSelector uint64 DtaAddr common.Address PaymentTokenDestAddr common.Address CcipDestTokenAmountsLength *big.Int CcipPaymentTokenAddr common.Address } ``` ## Configuration / lifecycle events ### `FundAdminRegistered` ```go type FundAdminRegistered struct { FundAdminAddr common.Address } ``` ### `FundTokenRegistered` Emitted by `registerFundToken`. ```go type FundTokenRegistered struct { FundAdminAddr common.Address FundTokenId common.Hash FundTokenAddr common.Address NavAddr common.Address TokenChainSelector uint64 } ``` ### `FundTokenAllowlistUpdated` Emitted by `allowDistributorForToken` / `disallowDistributorForToken`. ```go type FundTokenAllowlistUpdated struct { FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address Allowed bool } ``` ### `DistributorRegistered` ```go type DistributorRegistered struct { DistributorAddr common.Address } ``` ### `DistributorAuthorizationUpdated` Emitted by `authorizeDistributorForToken` / `revokeDistributorForToken`. ```go type DistributorAuthorizationUpdated struct { DistributorAddr common.Address FundAdminAddr common.Address FundTokenId common.Hash Authorized bool } ``` ### `DTAAdded` / `DTARemoved` Emitted by `allowDTA` / `disallowDTA`. See [Fund & Distributor Management](/crec/extensions/dta/fund-and-distributors#cross-dta-settlement) for the structs. ## Operational events ### `TokenWithdrawn` Emitted by `withdrawTokens` on either contract. ```go type TokenWithdrawn struct { Token common.Address Recipient common.Address Amount *big.Int } ``` ### `NativeFundsRecovered` Emitted on native-balance sweep operations. ```go type NativeFundsRecovered struct { To common.Address Amount *big.Int } ``` ### `OwnershipTransferred` Standard OpenZeppelin `Ownable` event, emitted by `transferOwnership` / `renounceOwnership`. ```go type OwnershipTransferred struct { PreviousOwner common.Address NewOwner common.Address } ``` ### `Initialized` Emitted once at contract deployment. ```go type Initialized struct { Version uint64 } ``` ### `AnswerUpdated` NAV oracle update event (from the wired `AggregatorV3Interface`). ```go type AnswerUpdated struct { Current *big.Int RoundId *big.Int UpdatedAt *big.Int } ``` ## Diagnostic / failure events ### `RequestAlreadyProcessed` Emitted when a CCIP message tries to settle a request that is already terminal. ```go type RequestAlreadyProcessed struct { RequestId common.Hash DtaAddr common.Address DtaChainSelector uint64 FundAdminAddr common.Address FundTokenId common.Hash } ``` ### `EmptyRequestType` Emitted when an inbound CCIP message has a missing `request_type` discriminator. ```go type EmptyRequestType struct { MessageId common.Hash SourceChainSelector uint64 DtaAddr common.Address RequestId common.Hash } ``` ### `UnauthorizedSenderDTA` Emitted when a CCIP message's sender DTA is not on the local allow-list. ```go type UnauthorizedSenderDTA struct { DtaAddr common.Address DtaChainSelector uint64 FundAdminAddr common.Address FundTokenId common.Hash DistributorAddr common.Address RequestId common.Hash ReqType DistributorRequestType } ``` ### `CCIPMessageDecodeFailed` / `CCIPMessageHandleFailed` Catch-all CCIP error events. ```go type CCIPMessageDecodeFailed struct { MessageId common.Hash SourceChainSelector uint64 Reason []byte } type CCIPMessageHandleFailed struct { MessageId common.Hash SourceChainSelector uint64 DtaAddr common.Address Reason []byte } ``` ### `MessageFailed` Emitted when a generic message handler reverts. ```go type MessageFailed struct { MessageId common.Hash Reason []byte } ``` ## Enum reference Several event payloads reference enum types from `events/types.go`: | Enum | Variants | Reference | | ------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `RequestStatus` | `None`, `Pending`, `Processing`, `Processed`, `Canceled`, `Failed` | [Request lifecycle](https://docs.chain.link/dta-technical-standard/concepts/request-lifecycle) | | `DistributorRequestType` | `None`, `Subscription`, `Redemption` | [Subscription / Redemption requests](https://docs.chain.link/dta-technical-standard/reference/glossary#subscription-request) | | `TokenMintType` | `Mint` (ERC-3643/CMTAT), `IssueTokens` (DSToken/BUIDL) | Fund token contract interface | | `TokenBurnType` | `Burn`, `BurnFrom`, `BurnWithReason`, `ForceBurn` | Fund token contract interface | ## See also - [Subscriptions and Redemptions](/crec/extensions/dta/subscriptions-redemptions): which operations emit each lifecycle event. - [Fund and Distributor Management](/crec/extensions/dta/fund-and-distributors): which operations emit each configuration event. - [Decode Event Data](/crec/guides/events/decode-data): generic decoding patterns and the `Events.Decode` machinery. --- # REST API Reference Source: https://docs.chain.link/crec/reference/rest-api Last Updated: 2026-08-31 This page is the narrative companion to the live, generated REST API reference. - [Interactive reference](/api/crec/docs): browse every endpoint, view request/response schemas, and try requests against your own organisation. - [OpenAPI specification](/api/crec/openapi.json) - **Go SDK**: prefer the [Go SDK reference](/crec/reference/go-sdk) for production code; the SDK wraps every endpoint described here. ## Base URL CRE Connect is offered as a managed service. Use the base URL provided to your organisation when you onboarded: ``` https://cre-connect.api.chain.link/v1 ``` Reach out to the Chainlink team if you don't yet have an environment URL or need a separate sandbox. ## Authentication Every request requires an organisation API key, sent in the `Authorization` header with the `Apikey` scheme: ```bash curl https://cre-connect.api.chain.link/v1/networks \ -H "Authorization: Apikey $CREC_API_KEY" ``` The OpenAPI specification declares this scheme as `ApiKeyAuth`: ```yaml securitySchemes: ApiKeyAuth: type: apiKey in: header name: Authorization description: | Organisation API key. Send as `Authorization: Apikey `. security: - ApiKeyAuth: [] ``` Treat API keys as secrets: they grant full access to your organisation's channels, watchers, wallets, and operations. Rotate any key that may have been exposed. ## Endpoint groups | Path prefix | Resource | Notes | | ----------------------------------------- | ------------------------------ | ------------------------------------------------------ | | `/health-check` | Service liveness | Anonymous; no auth required. | | `/networks` | Supported networks | List runtime-discovered networks. | | `/wallets` | Smart Accounts | Create, list, lookup, rename, archive. | | `/channels` | Channels | Create, list, lookup, rename, archive. | | `/channels/{id}/watchers` | Watchers within a channel | Create with service or ABI; archive (async). | | `/channels/{id}/operations` | Operations within a channel | Create, execute, finalize, or cancel; track lifecycle. | | `/channels/{id}/queries` | Chain queries within a channel | Create (async 202), list, lookup. | | `/channels/{id}/events` | Events on a channel | Real-time poll. | | `/channels/{id}/events/search` | Historical event search | Filter by type, time, address, etc. | | `/channels/{id}/events/search/{event_id}` | Single event lookup | Fetch one event by ID. | Full request/response schemas live in the [interactive reference](/api/crec/docs). ## Async semantics A small number of endpoints are asynchronous and return `202 Accepted` rather than the final state: - **`PATCH /channels/{channel_id}/watchers/{watcher_id}`** with a status transition to `archived` returns `202` and a watcher in the `archiving` state. The watcher transitions to `archived` (or `archive_failed`) once CRE Connect deprovisions it. Poll the watcher resource, or subscribe to `watcher.status` events, to observe the terminal state. - **`POST /channels/{channel_id}/operations`** returns the operation in either the `accepted` state (when a `signature` is provided) or the `pending_signature` state (when the `signature` is omitted, creating a draft). Confirmation or failure is reported via `operation.status` events or follow-up GETs. On networks with multiple active finality stages, the same operation can emit `confirmed_latest`, `confirmed_safe`, and `confirmed` as the block matures. See [Submit and Track Operations](/crec/guides/operations/submit-and-track) and [Multi-Event Finality](/crec/concepts/multi-event-finality). - **`PATCH /channels/{channel_id}/operations/{operation_id}`** with `{status: "accepted", signature, digest}` finalizes a draft operation from `pending_signature` to `accepted`. With `{status: "cancelled"}`, it cancels a draft. See [Draft Operations](/crec/concepts/drafts). - **`POST /channels/{channel_id}/queries`** returns `202 Accepted` with the query in the `accepted` state. The DON executes the query asynchronously; terminal state (`completed` / `failed`) is reported via `query.status` events or follow-up GETs. Queries expire after a TTL if no terminal callback arrives. See [Chain Queries](/crec/concepts/queries). For a complete state-machine reference for every async resource, see [Lifecycles](/crec/reference/lifecycles). ## Error responses All non-2xx responses use a uniform `ApplicationError` shape: ```json { "type": "NOT_FOUND", "code": "WALLET_NOT_FOUND", "message": "The requested resource was not found." } ``` `type` is one of: | `type` | Typical HTTP status | Meaning | | ------------------------ | ------------------- | ----------------------------------------------------------------------- | | `VALIDATION_ERROR` | 400 | Invalid input: schema validation or parameter constraint failed. | | `NOT_FOUND` | 404 | The referenced resource does not exist (or is not visible to your org). | | `CONFLICT` | 409 | A unique constraint or state transition guard was violated. | | `INTERNAL_ERROR` | 500 | Server-side error. Safe to retry. | | `ORGANIZATION_NOT_FOUND` | 401 | The authenticated organization is not onboarded in CRE Connect. | The `code` field provides a machine-readable error code. For `NOT_FOUND` responses, the code identifies which resource was not found: | `code` | Meaning | | --------------------- | ---------------------------------------- | | `CHANNEL_NOT_FOUND` | The referenced channel does not exist. | | `WALLET_NOT_FOUND` | The referenced wallet does not exist. | | `OPERATION_NOT_FOUND` | The referenced operation does not exist. | | `WATCHER_NOT_FOUND` | The referenced watcher does not exist. | | `QUERY_NOT_FOUND` | The referenced query does not exist. | For `CONFLICT` responses, the code identifies the specific conflict: | `code` | Meaning | | ---------------------------- | ---------------------------------------------------------------- | | `CHANNEL_ALREADY_EXISTS` | A channel with the same name already exists in the organization. | | `WALLET_ALREADY_EXISTS` | A wallet with the same name already exists in the organization. | | `WATCHER_ALREADY_EXISTS` | A watcher with the same name already exists in the channel. | | `IDEMPOTENCY_KEY_MISMATCH` | An idempotency key was reused with a different request. | | `OPERATION_NOT_FINALIZABLE` | The operation is not in a finalizable state. | | `OPERATION_NOT_CANCELLABLE` | The operation is not in a cancellable state. | | `OPERATION_DEADLINE_ELAPSED` | The operation deadline has elapsed. | | `RESOURCE_VERSION_CONFLICT` | The resource was modified concurrently by another request. | | `WALLET_ALREADY_ARCHIVED` | The wallet is archived and can no longer accept operations. | | `CHAIN_UNAVAILABLE` | The chain is unavailable for wallet creation. | The Go SDK maps these codes to sentinel errors (`apierror.ErrChannelAlreadyExists`, etc.) so `errors.Is` works across packages; see [Error Handling](/crec/reference/error-handling#apierror-conflict-sentinels). `message` is a human-readable explanation that **may change between releases**: never key business logic on its exact text. ### Authentication errors Calls without a valid `Authorization: Apikey ` header return `401 Unauthorized`. The body uses the same `ApplicationError` shape. ### Rate limiting When rate limits apply, the API returns `429 Too Many Requests`. The Go SDK's polling helpers (`watchers.WaitForActive` / `WaitForArchived`) classify `429` and `5xx` as transient and continue to the next poll tick rather than aborting; for every other endpoint the SDK does not retry: implement your own retry logic in your application code. See [Error Handling](/crec/reference/error-handling) for the full classification. ## Pagination Listing endpoints (`/wallets`, `/channels`, `/channels/{id}/watchers`, `/channels/{id}/operations`, `/channels/{id}/queries`, `/channels/{id}/events`) return: ```json { "data": [...], "has_more": true } ``` When `has_more: true`, paginate using the endpoint-specific cursor parameters (`limit`, `offset`, or a time-based cursor; see the [interactive reference](/api/crec/docs) for each endpoint's exact contract). ## Versioning The OpenAPI document declares `info.version`. The current spec is **`0.8.0`**. Backwards-incompatible changes are limited to major-version bumps; minor bumps may add new optional fields and endpoints. Pin your dependencies (Go SDK, generated clients) to a known minor. ## Try it The fastest way to validate authentication is to call `ListNetworks`: ```bash curl https://cre-connect.api.chain.link/v1/networks \ -H "Authorization: Apikey $CREC_API_KEY" \ | jq ``` A successful response returns `{ "data": [...], "has_more": false }`. See [Authentication](/crec/getting-started/authentication) for an end-to-end smoke test using the Go SDK. ## See also - [Interactive REST reference](/api/crec/docs) - [Go SDK reference](/crec/reference/go-sdk) - [Error handling](/crec/reference/error-handling) - [Lifecycles](/crec/reference/lifecycles) --- # Go SDK Reference Source: https://docs.chain.link/crec/reference/go-sdk Last Updated: 2026-08-31 The CRE Connect Go SDK is the recommended client for production integrations. This page lists every public sub-package, its purpose, and the canonical entry points. Full type-level documentation lives on `pkg.go.dev`: - [`github.com/smartcontractkit/crec-sdk`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk) - [`github.com/smartcontractkit/crec-sdk-ext-dta/v2`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk-ext-dta/v2) ## Module layout ``` github.com/smartcontractkit/crec-sdk ├── crec.go // crec.NewClient, crec.Client (root facade) ├── options.go // crec.Option, WithEventVerification, WithOrgID, … ├── channels/ // channels.Client ├── watchers/ // watchers.Client ├── events/ // events.Client + verification + decoding ├── transact/ // transact.Client (operations, signing helpers) │ ├── eip712/ // EIP-712 typed-data construction │ ├── signer/ // Signer interface + implementations │ │ ├── local/ // ECDSA dev signer │ │ ├── kms/ // AWS KMS ECDSA signer │ │ ├── vault/ // HashiCorp Vault RSA / non-secp256k1 signer │ │ ├── fireblocks/// Fireblocks signer │ │ └── privy/ // Privy embedded-wallet signer │ └── types/ // Operation, Transaction, OperationResponse ├── wallets/ // wallets.Client (Smart Accounts) ├── queries/ // queries.Client (chain queries) ├── parsing/ // ABI + log decoding helpers ├── extension/ // Extension SDK contract used by ext-* modules ├── interfaces/ // Public interfaces for testing / mocking └── mocks/ // gomock-generated mocks ``` ## Root client (`crec`) The root facade composes every sub-client behind one struct. ```go import crec "github.com/smartcontractkit/crec-sdk" client, err := crec.NewClient( "https://cre-connect.api.chain.link/v1", os.Getenv("CREC_API_KEY"), crec.WithOrgID(os.Getenv("CREC_ORG_ID")), ) if err != nil { return err } // Optionally configure event verification for your DON as one unit // (tenant ID, threshold, signer set; provided at onboarding): // crec.WithDONConfig("3", 2, []string{...}) // Sub-clients: client.Channels // *channels.Client client.Watchers // *watchers.Client client.Events // *events.Client client.Transact // *transact.Client client.Wallets // *wallets.Client client.Queries // *queries.Client ``` See [SDK Configuration](/crec/reference/sdk-configuration) for the complete `Option` table. [`pkg.go.dev/github.com/smartcontractkit/crec-sdk`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk) ## `channels` Manage event/operation channels: the partition unit for watchers, operations, and event streams. | Method | Description | | -------------------------------------- | -------------------------------------------------------------------------------------------------- | | `Client.Create(ctx, input)` | Create a new channel (`channels.CreateInput`). | | `Client.Get(ctx, channelID)` | Fetch one channel. | | `Client.List(ctx, input)` | List channels (`channels.ListInput` for pagination + filters); returns `(channels, hasMore, err)`. | | `Client.Update(ctx, channelID, input)` | Rename or change `Status` (e.g. archive) via `UpdateInput`. | [`pkg.go.dev/github.com/smartcontractkit/crec-sdk/channels`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/channels) ## `watchers` Provision and manage on-chain event subscriptions. | Method | Description | | ------------------------------------------------- | ----------------------------------------------------------- | | `Client.CreateWithService(ctx, channelID, input)` | Create a watcher from a predefined service (e.g. `dta.v2`). | | `Client.CreateWithABI(ctx, channelID, input)` | Create a watcher from raw contract ABI + event names. | | `Client.Get(ctx, channelID, watcherID)` | Fetch one watcher. | | `Client.List(ctx, channelID, filters)` | List watchers in a channel (`watchers.ListFilters`). | | `Client.Update(ctx, channelID, watcherID, input)` | Rename a watcher. | | `Client.Archive(ctx, channelID, watcherID)` | Archive a watcher (async; transitions through `archiving`). | Polling helpers: `WaitForActive` and `WaitForArchived` poll `Get` on a fixed-interval ticker (default 2s, configurable via `crec.WithWatcherPolling`) until the watcher reaches the requested status, the deadline elapses, or a permanent error is returned. Transient errors (`429`, `5xx`, common network errors) classified by `isTransientStatusCode` are logged and the loop continues to the next tick; see [Error Handling](/crec/reference/error-handling). [`pkg.go.dev/github.com/smartcontractkit/crec-sdk/watchers`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/watchers) ## `events` Poll, search, verify, and decode events emitted to a channel. | Method | Description | | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `Client.Poll(ctx, channelID, filters)` | Returns `(events, hasMore, err)`. | | `Client.SearchEvents(ctx, channelID, params)` | Historical search with `apiClient.GetChannelsChannelIdEventsSearchParams`. | | `Client.Verify(event)` / `VerifyWithOrgID` / `VerifyWithWorkflowOwner` | Cryptographic verification of `watcher.event` envelopes. | | `Client.VerifyOperationStatus(event)` (+ `WithOrgID` / `WithWorkflowOwner` variants) | Verification of `operation.status` envelopes, including `confirmed_latest`, `confirmed_safe`, and `confirmed`. | | `Client.VerifyQueryStatus(event)` (+ `WithOrgID` / `WithWorkflowOwner` variants) | Verification of `query.status` envelopes. | | `Client.VerifyOCRSignatures(ocrReport, ocrContext, signatures)` | Lower-level OCR signature verification (any event type). | | `Client.Decode(event, payload)` | Re-marshal an `apiClient.Event` into a user-supplied struct. | | `Client.DecodeVerifiableEvent(payload)` | Decode a `WatcherEventPayload` into the canonical `models.VerifiableEvent`. | | `Client.DecodeOperationStatusVerifiableEvent(payload)` | Same, for `OperationStatusPayload`. | | `Client.DecodeQueryStatusVerifiableEvent(payload)` | Same, for `QueryStatusPayload`. | | `Client.DecodeChainQueryVerifiableResult(b64)` | Decode a base64 `verifiable_result` directly. | | `Client.EventHash(payload)` / `OperationStatusHash(payload)` / `QueryStatusHash(payload)` | Compute the event hash for each payload type. | | `Client.ToJSON(event)` | JSON serialization helper. | | `Client.WorkflowOwnerFromOrgID(orgID)` | Derive the workflow owner address from an org ID. | Sentinel errors live in `events` (selection): - `events.ErrChannelNotFound`, `events.ErrPollEvents`, `events.ErrSearchEvents`, `events.ErrBadRequest` - `events.ErrVerifyEvent`, `events.ErrInvalidEventHash`, `events.ErrVerificationNotConfigured` - `events.ErrOnlyWatcherEventsSupported`, `events.ErrOnlyOperationStatusSupported`, `events.ErrOnlyQueryStatusSupported` - `events.ErrOrgIDOrWorkflowOwnerReq`, `events.ErrOrgIDRequired`, `events.ErrWorkflowOwnerRequired`, `events.ErrDeriveWorkflowOwner` - `events.ErrNoOCRProofs`, `events.ErrMultipleOCRProofs`, `events.ErrOCRReportTooShort`, `events.ErrParseOCRReport`, `events.ErrParseOCRContext`, `events.ErrParseSignature`, `events.ErrRecoverPubKeyFromSignature` See [Verify Event Signatures](/crec/guides/events/verify-signatures) and [Event Verification](/crec/concepts/event-verification). [`pkg.go.dev/github.com/smartcontractkit/crec-sdk/events`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/events) ## `transact` Build, sign, and submit operations. Operations can be created with a signature (immediately relayed) or without a signature as drafts (held in `pending_signature` until finalized). See [Draft Operations](/crec/concepts/drafts). | Method | Description | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `Client.SignOperation(ctx, op, signer, chainSelector)` | Produce the EIP-712 hash and ECDSA signature for an `Operation`. | | `Client.SignOperationHash(ctx, opHash, signer)` | Sign a pre-computed operation hash. | | `Client.HashOperation(op, chainSelector)` | Compute the EIP-712 digest offline (for deferred signing). | | `Client.SendSignedOperation(ctx, channelID, op, signature, chainSelector)` | Submit a signed operation. | | `Client.ExecuteOperation(ctx, channelID, signer, op, chainSelector)` | Sign and submit in one call. | | `Client.ExecuteTransactions(ctx, channelID, signer, account, txs, deadline, chainSelector)` | Convenience wrapper that builds and submits the `Operation` for you. | | `Client.CreateOperation(ctx, input)` | Lower-level submission via `CreateOperationInput`. | | `Client.SendDraftOperation(ctx, channelID, op, chainSelector, txPreviews)` | Create an unsigned draft operation. | | `Client.CreateUnsignedDraftOperation(ctx, input)` | Low-level draft creation with explicit input fields. | | `Client.ExecuteDraftOperation(ctx, channelID, operationID, digest, signer)` | Sign digest + finalize draft in one call. | | `Client.SendSignedDraftOperation(ctx, channelID, operationID, digest, signature)` | Finalize a draft with a pre-computed signature. | | `Client.CancelDraftOperation(ctx, channelID, operationID)` | Cancel a pending draft. | | `Client.GetOperation(ctx, channelID, operationID)` | Fetch one operation. | | `Client.ListOperations(ctx, input)` | List operations (`ListOperationsInput`); returns `(operations, hasMore, err)`. | Subpackages: - [`transact/types`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/transact/types): `Operation`, `Transaction`, `OperationResponse`, status enums. - [`transact/eip712`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/transact/eip712): typed-data domain + payload assembly. - [`transact/signer`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/transact/signer): `Signer` and `TypedDataSigner` interfaces. ## `transact/signer/*` Each subpackage implements `signer.Signer` (and optionally `signer.TypedDataSigner`): | Package | Use case | Notes | | -------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------- | | [`local`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/transact/signer/local) | Dev / test | ECDSA from a raw private key. | | [`kms`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/transact/signer/kms) | Production ECDSA | AWS KMS HSM-backed. | | [`vault`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/transact/signer/vault) | RSA or non-secp256k1 ECDSA | HashiCorp Vault Transit. | | [`fireblocks`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/transact/signer/fireblocks) | Fireblocks-managed wallets | Raw-hash + EIP-712 typed-data. | | [`privy`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/transact/signer/privy) | Embedded user wallets | `personal_sign` over Privy API. | To plug in a custom custody system, implement `signer.Signer` directly; see [Custom Signer](/crec/guides/signers/custom). ## `wallets` Provision and manage Smart Accounts. | Method | Description | | ------------------------------------- | ------------------------------------------------------------------------ | | `Client.Create(ctx, input)` | Provision an ECDSA or RSA wallet (`wallets.CreateInput`). | | `Client.Get(ctx, walletID)` | Fetch one wallet. | | `Client.List(ctx, input)` | List wallets (`wallets.ListInput`); returns `(wallets, hasMore, err)`. | | `Client.Update(ctx, walletID, input)` | Rename a wallet. | | `Client.Archive(ctx, walletID)` | Archive a wallet (synchronous; returns the wallet in `archived` status). | [`pkg.go.dev/github.com/smartcontractkit/crec-sdk/wallets`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/wallets) ## `queries` Submit asynchronous, DON-backed, verifiable chain queries. See [Chain Queries](/crec/concepts/queries). | Method | Description | | --------------------------------------------------- | ---------------------------------------------------------------- | | `Client.Create(ctx, input)` | Generic create with raw `EVMCallQueryParams`. | | `Client.CreateEVMCall(ctx, input)` | Create an `evm_call` query (async, no wait). | | `Client.Get(ctx, channelID, queryID)` | Fetch one query. | | `Client.List(ctx, input)` | List queries (`ListInput`); returns `(queries, hasMore, err)`. | | `Client.Wait(ctx, channelID, queryID, maxWaitTime)` | Poll until terminal status (`completed` / `failed` / `expired`). | | `Client.CallContract(ctx, input)` | One-shot: create + wait + decode (raw return bytes). | | `Client.CallContractWithABI(ctx, input)` | One-shot: create + wait + decode + ABI unpack. | Package-level helpers: | Function | Description | | --------------------------------------------- | -------------------------------------------------- | | `Latest()` / `Finalized()` | Block selection helpers. | | `BlockNumber(n)` / `BlockNumberFromString(s)` | Explicit block number selection. | | `ResultFromQuery(query)` | Build a decoded `CallContractResult` from a query. | | `DecodeVerifiableResult(b64)` | Decode a base64 `verifiable_result`. | | `IsTerminalStatus(status)` | Check if a `QueryStatus` is terminal. | | `NewClient(opts)` | Create a standalone `queries.Client`. | Key types: `CallContractResult`, `CallContractABIResult`, `ResolvedBlock`, `QueryError`, `EVMCallInput`, `CallContractInput`, `CallContractWithABIInput`. [`pkg.go.dev/github.com/smartcontractkit/crec-sdk/queries`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/queries) ## `parsing` Helpers for ABI-encoding/decoding and Solidity log parsing. Used internally by `events.Decode` and the DTA v2 extension; useful for advanced integrations that want to roll their own decoders. [`pkg.go.dev/github.com/smartcontractkit/crec-sdk/parsing`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk/parsing) ## `extension` Defines the `Extension` interface that ext-modules (DTA, future extensions) implement. Most consumers don't import this directly: it's a contract for extension authors. ## `interfaces` and `mocks` `interfaces` exposes minimal interfaces over each sub-client for use in user code (e.g. for accepting fakes in tests). `mocks` ships gomock-generated test doubles for every interface. ```go import ( "github.com/smartcontractkit/crec-sdk/interfaces" "github.com/smartcontractkit/crec-sdk/mocks" ) func mySvc(events interfaces.EventsClient) { /* ... */ } // In tests: ctrl := gomock.NewController(t) mockEvents := mocks.NewMockEventsClient(ctrl) mockEvents.EXPECT().Poll(gomock.Any(), "ch-1").Return(...) mySvc(mockEvents) ``` ## DTA v2 extension module ``` github.com/smartcontractkit/crec-sdk-ext-dta/v2 ├── doc.go ├── decode.go // dtav2.DecodeFromEvent ├── operations/ // Prepare* operation builders ├── events/ // Typed event payloads + enums └── watcher/bundle/ // bundle.Get(): watcher provisioning bundle ``` | Sub-package | Entry point | Notes | | ---------------- | --------------------------------------------------------- | ------------------------------------------------------------ | | `operations` | `New(opts)`; `ext.Prepare*Operation(...)` | Returns ready-to-sign `types.Operation`. | | `events` | `events.SubscriptionRequested`, `events.RequestStatus`, … | Typed payloads + Solidity-aligned enums. | | `watcher/bundle` | `bundle.Get()` | Watcher provisioning input for `watchers.CreateWithService`. | | (root) | `dtav2.DecodeFromEvent(ctx, ev)` | One-call decoder for DTA events. | [`pkg.go.dev/github.com/smartcontractkit/crec-sdk-ext-dta/v2`](https://pkg.go.dev/github.com/smartcontractkit/crec-sdk-ext-dta/v2) ## Versioning - The Go SDK module path is unversioned (`github.com/smartcontractkit/crec-sdk`); pin to a tagged release in `go.mod`. - The DTA extension repository (`github.com/smartcontractkit/crec-sdk-ext-dta`) uses **contract versioning** to expose multiple deployed contract ABIs side-by-side as separate import paths (`/v1`, `/v2`, …). Per the extension's README, this is separate from Go module semantic versioning. Choose the import path that matches the deployed contract ABI you target. ## See also - [SDK Configuration](/crec/reference/sdk-configuration): every constructor option. - [Error Handling](/crec/reference/error-handling): sentinel errors and retry behaviour. - [Event Payloads](/crec/reference/event-payloads): every payload struct returned by `events.Client`. --- # SDK Configuration Source: https://docs.chain.link/crec/reference/sdk-configuration Last Updated: 2026-08-31 This page enumerates every constructor option exposed by the root `crec` SDK package. All options follow the functional-options pattern. ## `NewClient` ```go import crec "github.com/smartcontractkit/crec-sdk" func NewClient(baseURL, apiKey string, opts ...crec.Option) (*crec.Client, error) ``` | Parameter | Required | Notes | | --------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | `baseURL` | yes | Environment-specific base URL (e.g. `https://cre-connect.api.chain.link/v1`). Returns `ErrBaseURLRequired` if empty. | | `apiKey` | yes | Organisation API key. Sent internally as `Authorization: Apikey `. Returns `ErrAPIKeyRequired` if empty. | | `opts` | no | Zero or more of the options below. | `NewClient` validates the event-verification config (`ErrInvalidEventVerificationConfig`) and constructs every sub-client (`Channels`, `Events`, `Transact`, `Wallets`, `Watchers`, `Queries`). ### Example ```go client, err := crec.NewClient( os.Getenv("CREC_BASE_URL"), os.Getenv("CREC_API_KEY"), crec.WithLogger(slog.Default()), crec.WithOrgID(orgID), ) if err != nil { return err } ``` ## Defaults | Default | Constant / value | Notes | | ----------------------- | ----------------------------------------- | ----------------------------------------------------------- | | HTTP client | `http.DefaultClient` | Override with `WithHTTPClient`. | | Logger | `slog.Default()` | Override with `WithLogger`. | | Min required signatures | `crec.DefaultMinRequiredSignatures` (= 4) | F+1 where F = 3 (production DON Byzantine fault tolerance). | | Valid signers | `crec.DefaultValidSigners` | 10 production DON node addresses (Zone A). | | Event verification | enabled | Disable with `WithoutEventVerification()` (test only). | | CRE tenant ID | `events.CreMainlineTenantID` (= `"1"`) | Override with `WithCRETenantID`. | ## Options ### `WithHTTPClient(c *http.Client)` Override the default HTTP client. Use this to configure timeouts, proxies, or instrumentation. ```go crec.WithHTTPClient(&http.Client{ Timeout: 10 * time.Second }) ``` ### `WithLogger(l *slog.Logger)` Inject a custom `slog.Logger`. Defaults to `slog.Default()`. ```go crec.WithLogger(slog.New(slog.NewJSONHandler(os.Stdout, nil))) ``` ### `WithDONConfig(creTenantID string, minRequiredSignatures int, validSigners []string)` Configure event verification for your organisation's DON as one atomic unit: the CRE tenant ID (used for workflow-owner derivation), the signature threshold, and the signer set. These values are provided at onboarding; they are not SDK constants. ```go crec.WithDONConfig("3", 2, []string{ "0x4d6cfd44f94408a39fb1af94a53c107a730ba161", // … your DON's signer list … }) ``` The unit must be complete: `NewClient` returns `crec.ErrIncompleteDONConfig` if the tenant ID is empty, the signer list is empty, or the threshold is not positive. If you combine `WithDONConfig` with the granular options below, the last applied option wins per field; once `WithDONConfig` is used, the completeness requirement applies regardless of option order. ### `WithEventVerification(min int, signers []string)` **Deprecated**: use `WithDONConfig` instead, which configures the CRE tenant ID, signature threshold, and signer set as one unit. Override both the minimum required signatures and the set of valid signer addresses used by `events.Client.Verify`. ```go crec.WithEventVerification(4, []string{ "0xff9b062fccb2f042311343048b9518068370f837", // … }) ``` ### `WithoutEventVerification()` Skip the default signer-set backfill: the client ends up with no signers, and verification calls fail with `events.ErrVerificationNotConfigured`. This does not override explicitly configured signers: those set via `WithEventVerification` or `WithDONConfig` still apply. ```go crec.WithoutEventVerification() ``` ### `WithOrgID(orgID string)` Set the default organisation ID for `events.Client.Verify` and `events.Client.VerifyOperationStatus`. With this option, you can call those methods without passing `orgID` explicitly. For multi-org applications, omit this option and use `VerifyWithOrgID` / `VerifyOperationStatusWithOrgID`. ### `WithWorkflowOwner(owner string)` Set the default workflow-owner address for verification. Use `VerifyWithWorkflowOwner` / `VerifyOperationStatusWithWorkflowOwner` for per-call overrides. ### `WithCRETenantID(tenant string)` **Deprecated**: use `WithDONConfig` instead, which sets the CRE tenant ID together with the threshold and signer set. Override the CRE tenant ID used for workflow-owner address derivation. Defaults to `events.CreMainlineTenantID` (`"1"`). Use a different tenant ID when targeting a non-mainline CRE environment. ### `WithWatcherPolling(pollInterval, eventualConsistencyWindow time.Duration)` Tune the `watchers` client's polling behaviour: - `pollInterval`: wait between polls when waiting for a watcher state change. - `eventualConsistencyWindow`: how long to tolerate `404` responses immediately after creating a watcher. ```go crec.WithWatcherPolling(2*time.Second, 30*time.Second) ``` ## Constants exposed by `crec` ```go const DefaultMinRequiredSignatures = 4 ``` `DefaultMinRequiredSignatures = F + 1` where F = 3 (production DON Byzantine fault tolerance). The DON only transmits once at least F+1 signatures are gathered, so verification will always succeed at this default for production events. `DefaultValidSigners` is the production DON node set on Ethereum Mainnet (Zone A), exported as a `[]string` of 10 addresses. These keys rarely change; when they do, update the SDK to pick up the new set. | Node Operator | Public Key | | ------------- | -------------------------------------------- | | Chainlayer | `0xff9b062fccb2f042311343048b9518068370f837` | | CLP | `0xe55fcaf921e76c6bbcf9415bba12b1236f07b0c3` | | Dextrac | `0x4d6cfd44f94408a39fb1af94a53c107a730ba161` | | Fiews | `0xde5cd1dd4300a0b4854f8223add60d20e1dfe21b` | | Inotel | `0xf3baa9a99b5ad64f50779f449bac83baac8bfdb6` | | LinkForest | `0xd7f22fb5382ff477d2ff5c702cab0ef8abf18233` | | LinkPool | `0xcdf20f8ffd41b02c680988b20e68735cc8c1ca17` | | LinkRiver | `0x4d7d71c7e584cfa1f5c06275e5d283b9d3176924` | | PierTwo | `0xedf4bc027a750d1a88b8ca3ec5e8a5506f6019be` | | SimplyVC | `0x4f99b550623e77b807df7cbed9c79d55e1163b48` | ## Errors | Error | Returned by | | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `crec.ErrBaseURLRequired` | `NewClient` / `NewAPIClient` when `baseURL == ""`. | | `crec.ErrAPIKeyRequired` | `NewClient` / `NewAPIClient` when `apiKey == ""`. | | `crec.ErrInvalidEventVerificationConfig` | `NewClient` when `validSigners` is set but `minRequiredSignatures <= 0`. | | `crec.ErrIncompleteDONConfig` | `NewClient` when `WithDONConfig` was used but the DON unit is incomplete: empty tenant ID, empty signer list, or non-positive threshold. | ## Sub-client construction If you only need one sub-client (e.g. just `channels`), construct an `APIClient` and pass it explicitly: ```go api, err := crec.NewAPIClient(baseURL, apiKey) if err != nil { return err } channelsClient, err := channels.NewClient(&channels.Options{ APIClient: api }) ``` This pattern is useful in services that import the SDK as a thin transport layer. ## See also - [Authentication](/crec/getting-started/authentication): basic `NewClient` setup. - [Event Verification](/crec/concepts/event-verification): what `WithEventVerification` actually controls. - [Error Handling](/crec/reference/error-handling): sentinel errors and retry policies. --- # Lifecycles Source: https://docs.chain.link/crec/reference/lifecycles Last Updated: 2026-08-31 CRE Connect resources move through well-defined state machines. This page lists every status value (taken verbatim from the OpenAPI spec) and shows how each resource transitions in response to API calls and backend events. ## Watcher lifecycle `WatcherStatus`: | State | Reachable via | Notes | | ---------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------ | | `pending` | `POST /channels/{id}/watchers` returns this immediately. | Backend is provisioning the watcher. | | `active` | Backend transition once provisioning completes. | Watcher is observing the chain. | | `failed` | Backend transition. | Provisioning or runtime failure. | | `archiving` | `PATCH /channels/{id}/watchers/{id}` with `status: archived` (returns `202`). | Async tear-down. | | `archived` | Backend transition once tear-down completes. | Terminal. | | `archive_failed` | Backend transition. | Returned via the `WatcherEventStatus` enum on event filters. | Subscribe to `watcher.status` events to receive a transition payload (`WatcherStatusPayload`) for every move. ## Wallet (Smart Account) lifecycle `WalletStatus`: | State | Reachable via | Notes | | ----------- | ---------------------------------------------- | ------------------------------------------ | | `pending` | `POST /wallets` returns this. | Backend has accepted the request. | | `deploying` | Backend transition. | On-chain deploy submitted. | | `deployed` | Backend transition. | Smart Account deployed; address is stable. | | `failed` | Backend transition. | Deploy failure. | | `archived` | `PATCH /wallets/{id}` with `status: archived`. | Wallet is hidden from default listings. | Subscribe to `wallet.status` events for a stream of wallet transitions (`WalletStatusPayload`). ## Operation lifecycle `OperationStatus`: | State | Reachable via | Notes | | ------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `pending_signature` | `POST /channels/{id}/operations` (no signature). | Draft operation awaiting finalization. Not relayed to the chain. See [Drafts](/crec/concepts/drafts). | | `accepted` | `POST /channels/{id}/operations` (with signature) or `PATCH` finalize. | Backend has accepted the operation and enqueued it for relay. | | `sending` | Backend transition. | Picked up by the worker. | | `sent` | Backend transition. | Submitted to the chain mempool. | | `broadcasting` | Backend transition. | Awaiting block inclusion. | | `confirmed_latest` | Backend transition. | Operation included on chain at `latest` confidence. May still be reorganized. See [Multi-Event Finality](/crec/concepts/multi-event-finality). | | `confirmed_safe` | Backend transition. | Operation included on chain at `safe` confidence. Reorg very unlikely. | | `confirmed` | Backend transition. | Operation included on chain at `finalized` confidence. Cannot be reorganized. | | `failed` | Backend transition. | Permanent failure (revert, gas, …). | | `cancelled` | `PATCH /channels/{id}/operations/{id}` (cancel). | Draft was cancelled before signing. Terminal. | | `expired` | Background scanner or inline during finalize. | Deadline elapsed before the operation was finalized or confirmed. Terminal. | Subscribe to `operation.status` events to track every operation through to a terminal state (`OperationStatusPayload`). Terminal states are: `confirmed`, `failed`, `cancelled`, `expired`. ## Query lifecycle `QueryStatus`: | State | Reachable via | Notes | | ----------- | ------------------------------------------- | ---------------------------------------------------------------- | | `accepted` | `POST /channels/{id}/queries` returns this. | Query created and persisted; job enqueued for dispatch. | | `sending` | Backend transition. | Dispatch worker is actively sending to the CRE gateway. | | `sent` | Backend transition. | Successfully dispatched to CRE gateway; awaiting DON callback. | | `completed` | Backend transition. | DON returned a successful result with OCR proof. Terminal. | | `failed` | Backend transition. | DON returned an error, or dispatch failed permanently. Terminal. | | `expired` | Backend transition. | TTL elapsed before a terminal callback arrived. Terminal. | Subscribe to `query.status` events to track queries through to a terminal state. Terminal `completed` and `failed` events carry OCR proofs and can be verified with `events.Client.VerifyQueryStatus`. See [Chain Queries](/crec/concepts/queries). ## Event types The `EventType` enum drives the `events.search` and `events.poll` endpoints: | `EventType` | Payload struct | Emitted when | | ------------------ | ------------------------ | ---------------------------------------------------- | | `operation.status` | `OperationStatusPayload` | An operation moves between `OperationStatus` values. | | `query.status` | `QueryStatusPayload` | A chain query moves between `QueryStatus` values. | | `watcher.status` | `WatcherStatusPayload` | A watcher moves between `WatcherStatus` values. | | `watcher.event` | `WatcherEventPayload` | A subscribed contract event is observed on chain. | | `wallet.status` | `WalletStatusPayload` | A wallet moves between `WalletStatus` values. | `watcher.event` payloads are **cryptographically verifiable** with `events.Client.Verify`. `operation.status` events at `confirmed_latest`, `confirmed_safe`, and `confirmed` are also DON-verified via `events.Client.VerifyOperationStatus`. Terminal `query.status` events (`completed`, `failed`) are DON-verified via `events.Client.VerifyQueryStatus`. Non-terminal status events and draft lifecycle events (`pending_signature`, `cancelled`, `expired`) are operational notifications without OCR proofs. ## Polling vs subscribing For lifecycle state, you have two options: 1. **Re-fetching the resource**: call `GET /channels/{id}/operations/{id}` (or the equivalent for watchers / wallets) on each tick. 2. **Polling the events stream**: call `GET /channels/{id}/events` with the appropriate `type` filter and a stable cursor, and consume the lifecycle events as they appear on the channel. See [Submit and Track Operations](/crec/guides/operations/submit-and-track) and [Poll and Search Events](/crec/guides/events/poll-and-search) for examples of both patterns. ## Confidence levels and `confirmed` An `operation.status` event reaches `confirmed_latest`, then `confirmed_safe`, then `confirmed` as the underlying transaction's block matures through [confidence levels](/crec/concepts/confidence-levels). On testnets, only `latest` may be active, making `confirmed_latest` the terminal status. On mainnets, the full ladder runs to `confirmed`. See [Multi-Event Finality](/crec/concepts/multi-event-finality) for the complete progression and reorg handling. ## See also - [REST API Reference](/crec/reference/rest-api): endpoint contract. - [Event Payloads](/crec/reference/event-payloads): full schema for each `Event_Payload`. - [Error Handling](/crec/reference/error-handling): what each `failed` state means and which retries apply. --- # Event Payloads Source: https://docs.chain.link/crec/reference/event-payloads Last Updated: 2026-08-31 The Events API returns a discriminated union: every record carries a `type` from the `EventType` enum, a `created_at` timestamp, and one of five `Event_Payload` shapes. This page lists each payload with its exact JSON schema and Go struct (from the generated OpenAPI types). ## Discriminator: `EventType` ``` operation.status // OperationStatusPayload query.status // QueryStatusPayload watcher.status // WatcherStatusPayload watcher.event // WatcherEventPayload ← cryptographically verifiable wallet.status // WalletStatusPayload ``` The Go SDK exposes typed accessors on `Event_Payload`: ```go // returns (OperationStatusPayload, error) ev.Payload.AsOperationStatusPayload() ev.Payload.AsQueryStatusPayload() ev.Payload.AsWatcherStatusPayload() ev.Payload.AsWatcherEventPayload() ev.Payload.AsWalletStatusPayload() ``` ## `WatcherEventPayload` (`watcher.event`) The cryptographically verifiable on-chain event payload. Every other payload type is an operational notification produced by the CRE Connect backend. ```go type WatcherEventPayload struct { ChainSelector string `json:"chain_selector"` EventHash string `json:"event_hash"` Timestamp int64 `json:"timestamp"` VerifiableEvent string `json:"verifiable_event"` // base64 WatcherId string `json:"watcher_id"` } ``` | Field | Notes | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chain_selector` | CCIP chain selector for the source chain. | | `event_hash` | Hash of the verifiable event payload: useful as a stable, cross-system identifier. | | `timestamp` | Unix seconds. | | `verifiable_event` | Base64-encoded `VerifiableEvent`: verify the parent event with `events.Client.Verify`, then decode with `events.Client.DecodeVerifiableEvent` (canonical model) or `events.Client.Decode` (into your own struct). | | `watcher_id` | UUID of the watcher that produced this event. | `VerifiableEvent` itself is a `models.VerifiableEvent` carrying the raw EVM log, the Off-Chain Reporting (OCR) signatures, and the workflow context required for verification; see [Event Verification](/crec/concepts/event-verification). ## `OperationStatusPayload` (`operation.status`) Emitted on every operation transition. ```go type OperationStatusPayload struct { Address string `json:"address"` ChainSelector string `json:"chain_selector"` EventHash *string `json:"event_hash,omitempty"` OperationId openapi_types.UUID `json:"operation_id"` Status OperationStatus `json:"status"` StatusReason string `json:"status_reason"` Timestamp int64 `json:"timestamp"` VerifiableEvent *string `json:"verifiable_event,omitempty"` WalletOperationId string `json:"wallet_operation_id"` } ``` | Field | Notes | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | Smart Account that authored the operation. | | `chain_selector` | Target chain. | | `event_hash` | Present for `confirmed_latest`, `confirmed_safe`, and `confirmed` statuses. | | `operation_id` | UUID assigned by the CRE Connect backend. | | `status` | One of `pending_signature`, `accepted`, `sending`, `sent`, `broadcasting`, `confirmed_latest`, `confirmed_safe`, `confirmed`, `cancelled`, `expired`, `failed`. | | `status_reason` | Human-readable explanation (especially for `failed`). | | `verifiable_event` | Present for `confirmed_latest`, `confirmed_safe`, and `confirmed`; verify with `events.Client.VerifyOperationStatus`. | | `wallet_operation_id` | The Smart-Account-side operation ID (your nonce + ABI-encoded operation). | `operation.status` events at `confirmed_latest`, `confirmed_safe`, and `confirmed` are DON-verified and carry OCR proofs. Each confirmation status has a different chain-finality guarantee; see [Multi-Event Finality](/crec/concepts/multi-event-finality). `pending_signature`, `cancelled`, `expired`, and non-terminal transitions (`accepted`, `sending`, `sent`, `broadcasting`) are operational notifications without OCR proofs. See [Draft Operations](/crec/concepts/drafts). ## `QueryStatusPayload` (`query.status`) Emitted on every chain query transition. ```go type QueryStatusPayload struct { QueryId openapi_types.UUID `json:"query_id"` Status QueryStatus `json:"status"` Target string `json:"target"` ChainSelector string `json:"chain_selector"` Timestamp int64 `json:"timestamp"` EventHash *string `json:"event_hash,omitempty"` VerifiableResult *string `json:"verifiable_result,omitempty"` WorkflowId *string `json:"workflow_id,omitempty"` WorkflowExecutionId *string `json:"workflow_execution_id,omitempty"` } ``` | Field | Notes | | ----------------------- | ------------------------------------------------------------------------------------------- | | `query_id` | UUID assigned by the CRE Connect backend. | | `status` | One of `accepted`, `sending`, `sent`, `completed`, `failed`, `expired`. | | `target` | The contract address for `evm_call` queries. | | `chain_selector` | Target chain. | | `timestamp` | Unix seconds. | | `event_hash` | Present on terminal events (`completed`, `failed`); `Keccak256(verifiable_result)`. | | `verifiable_result` | Present on terminal events; base64-encoded `ChainQueryVerifiableEvent`. | | `workflow_id` | CRE chain-query workflow that executed this query. | | `workflow_execution_id` | CRE execution ID from the workflow run. Present when the query completed with an OCR proof. | Terminal `query.status` events (`completed`, `failed`) carry OCR proofs and can be verified with `events.Client.VerifyQueryStatus`. Non-terminal events (`accepted`, `sending`, `sent`) and `expired` events are operational notifications without OCR proofs. See [Chain Queries](/crec/concepts/queries). ## `WatcherStatusPayload` (`watcher.status`) Emitted on every watcher provisioning / archival transition. ```go type WatcherStatusPayload struct { ChainSelector string `json:"chain_selector"` Service *string `json:"service,omitempty"` Status WatcherEventStatus `json:"status"` StatusReason string `json:"status_reason"` Timestamp int64 `json:"timestamp"` WatcherId string `json:"watcher_id"` } ``` `WatcherEventStatus` extends `WatcherStatus` with archival states for filtering: ``` pending | active | failed | archiving | archive_failed | archived ``` | Field | Notes | | --------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `service` | Service namespace (e.g. `dta.v2`) when the watcher was created with `Watchers.CreateWithService`. Absent for ABI-based watchers. | | `status_reason` | Useful when `status == failed` or `archive_failed`. | ## `WalletStatusPayload` (`wallet.status`) Emitted on every wallet (Smart Account) deploy / archival transition. ```go type WalletStatusPayload struct { Address string `json:"address"` ChainSelector string `json:"chain_selector"` Status WalletEventStatus `json:"status"` StatusReason string `json:"status_reason"` Timestamp int64 `json:"timestamp"` WalletId openapi_types.UUID `json:"wallet_id"` } ``` `WalletEventStatus`: ``` pending | deploying | deployed | failed | archived ``` | Field | Notes | | ---------------- | ------------------------------------------------- | | `address` | Smart-Account address. May be unset on `pending`. | | `wallet_id` | Backend UUID. | | `chain_selector` | Target chain for the wallet. | ## Common envelope Every `Event` returned by `/channels/{id}/events` and `/channels/{id}/events/search` carries: ```go type Event struct { ChannelId openapi_types.UUID `json:"channel_id"` CreatedAt int64 `json:"created_at"` EventId openapi_types.UUID `json:"event_id"` Payload Event_Payload `json:"payload"` Type EventType `json:"type"` } ``` | Field | Notes | | ------------ | -------------------------------------------- | | `event_id` | Stable UUID for deduplication. | | `created_at` | Unix seconds: use as the cursor when paging. | | `type` | Discriminator (see top). | | `payload` | One of the four `*Payload` shapes above. | ## Decoding `watcher.event` further Once you have a `WatcherEventPayload`, the next step depends on what you want: ```go // Step 1: cryptographic verification (returns (bool, error)) ok, err := client.Events.Verify(&ev) if err != nil || !ok { return err } // Extract the typed payload wp, err := ev.Payload.AsWatcherEventPayload() if err != nil { return err } // Step 2a: canonical decoded form decoded, err := client.Events.DecodeVerifiableEvent(&wp) if err != nil { return err } // Step 2b: pull out the EVM-specific event and read its decoded params evm, err := decoded.ChainEvent.AsEVMEvent() if err != nil { return err } params := *evm.Params fmt.Println(params["from"], params["to"], params["value"]) // Step 2c: extension decoder (e.g. DTA v2): handles steps 2a and 2b // for you and returns a typed struct. typed, err := dtav2.DecodeFromEvent(ctx, ev) ``` See [Decode Event Data](/crec/guides/events/decode-data). ## See also - [Lifecycles](/crec/reference/lifecycles): what `Status` transitions trigger each `*.status` payload. - [Event Verification](/crec/concepts/event-verification): how `verifiable_event` is signed and verified. - [Verifiable Events](/crec/concepts/verifiable-events): the `VerifiableEvent` model itself. --- # Error Handling Source: https://docs.chain.link/crec/reference/error-handling Last Updated: 2026-08-31 This page collects every sentinel error exposed by the Go SDK, the REST error envelope, and the transient-vs-permanent error classification used by the SDK's polling loops. ## REST error envelope Every non-2xx HTTP response uses the same shape: ```json { "type": "NOT_FOUND", "code": "WALLET_NOT_FOUND", "message": "The requested resource was not found." } ``` `type` ∈ `NOT_FOUND | VALIDATION_ERROR | CONFLICT | INTERNAL_ERROR | ORGANIZATION_NOT_FOUND`. The `code` field provides a machine-readable error code for `NOT_FOUND` and `CONFLICT` responses (e.g. `CHANNEL_NOT_FOUND`, `OPERATION_NOT_FINALIZABLE`, `IDEMPOTENCY_KEY_MISMATCH`). See the [REST API Reference](/crec/reference/rest-api#error-responses) for the full table. ## Transient vs permanent errors The SDK does **not** layer automatic HTTP-level retries on top of API calls. Each method on `wallets.Client`, `channels.Client`, `events.Client`, and `transact.Client` issues its request once and surfaces any error to the caller. The exception is the `watchers.Client` polling helpers, `WaitForActive` and `WaitForArchived`. These methods poll `Get` on a `time.Ticker` (default `2 * time.Second`, configurable via `crec.WithWatcherPolling`). When `Get` returns a transient error, the loop logs it and continues to the next tick instead of aborting; permanent errors abort the wait immediately. The transient/permanent classification is implemented in [`watchers.go`](https://github.com/smartcontractkit/crec-sdk/blob/main/watchers/watchers.go) (`isTransientError` / `isTransientStatusCode`): | Status code | Classification | Behaviour inside `WaitForActive` / `WaitForArchived` | | -------------------------------------------------------------------------------------------------------------------------- | -------------------- | ---------------------------------------------------- | | `429 Too Many Requests` | Transient | Loop continues to the next poll tick | | `500`–`599` | Transient | Loop continues to the next poll tick | | Network errors (connection refused/reset, timeout, EOF, no such host, network unreachable, broken pipe, temporary failure) | Transient | Loop continues to the next poll tick | | `400`–`499` (excluding `429`) | Permanent | Wait aborts; error returned to caller | | Any other error not matched above | Treated as permanent | Wait aborts | For all other sub-clients, retry behaviour is your responsibility. If you wrap calls in your own retry loop, base the transient check on `errors.Is(err, …)` against the sentinel errors documented below, or on the HTTP status code returned in the REST error envelope. ## Common symptoms: what to do If you're hitting a specific error and just want to know what to fix, find the symptom below first. The full sentinel-error catalog (organised per SDK package) is in the next section. | Symptom (what you see) | Likely cause | Fix | | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `crec.ErrBaseURLRequired` / `ErrAPIKeyRequired` on `NewClient` | `CREC_BASE_URL` / `CREC_API_KEY` env var unset or passed as `""`. | Set both before constructing the client; see [Authentication](/crec/getting-started/authentication). | | `401 Unauthorized` from any endpoint | API key not sent, sent in the wrong header, or for the wrong environment. | The header is `Authorization: Apikey `. The Go SDK sets it for you; for raw `curl` see [REST API Reference](/crec/reference/rest-api#authentication). | | `wallets.ErrStatusChannelIDZero` on `Wallets.Create` | `StatusChannelId` was supplied as the zero UUID. | Pass a real channel ID, or omit the field entirely; see [Create and Manage Wallets](/crec/guides/wallets/create-and-manage). | | `transact.ErrInvalidDeadline` or `types.ErrOperationDeadlineRequired` on `ExecuteOperation` | `Operation.Deadline` is now mandatory. | Set `Deadline: big.NewInt(0)` for "no expiration" or a Unix-seconds value; see [Build and Sign Operations](/crec/guides/operations/build-and-sign). | | `eip712.ErrUnsupportedChainFamily` on signing | The chain selector resolves to a non-EVM chain. | EVM is the only supported family today; pick a different chain selector via [Supported Networks](/crec/supported-networks). | | `events.ErrVerificationNotConfigured` on `Events.Verify` | Client built without `crec.WithEventVerification(...)`. | Configure verification at construction time; see [Verify Event Signatures](/crec/guides/events/verify-signatures). | | `events.ErrNoOCRProofs` on `Events.Verify` / `VerifyOperationStatus` | The event record returned by the API has no Off-Chain Reporting (OCR) proof attached. | Skip the event with `errors.Is(err, events.ErrNoOCRProofs)` and re-poll on the next cycle. | | `events.ErrInvalidEventHash` / `ErrMultipleOCRProofs` | The event payload is malformed, tampered, or wasn't produced by your tenant's watcher. | Re-fetch the event with `Events.SearchEvents` (filtered by `EventId`) or `GET /channels/{channel_id}/events/search/{event_id}`; if it persists, the watcher source is the problem. See [Event Verification](/crec/concepts/event-verification). | | `events.ErrOrgIDOrWorkflowOwnerReq` / `ErrWorkflowOwnerRequired` | Verification needs an org context the SDK couldn't resolve. | Pass `crec.WithOrgID(os.Getenv("CREC_ORG_ID"))` or `WithWorkflowOwner(...)` at client construction time. There is no default. | | `watchers.ErrWaitForActiveTimeout` / `ErrWatcherDeploymentFailed` | `WaitForActive` exceeded its deadline, or the watcher transitioned to `failed`. | Inspect the latest `watcher.status` event for `status_reason`. Most failures are bad ABI / address / chain selector; see [Manage Watcher Lifecycle](/crec/guides/watchers/manage-lifecycle). | | `watchers.ErrWatcherIsArchiving` / `ErrWatcherAlreadyArchived` | You called `WaitForActive` on a watcher that's already being torn down. | Recreate the watcher; archived watchers cannot be reactivated. | | `wallets.ErrDuplicateEcdsaSigner` / `ErrDuplicateRsaSigner` | Two identical entries in the signer list. | Deduplicate before calling `Create`. | | `transact.ErrChannelNotFound` on `ExecuteOperation` | Wrong `channelID`, or the channel was archived. | Confirm with `Channels.Get`; recreate or pick a different channel. | | `fireblocks.ErrEnvFireblocksAPIKey` (etc.) | `FIREBLOCKS_*` env var missing when calling `FromEnv`. | Set every `FIREBLOCKS_API_KEY` / `_API_SECRET` / `_VAULT_ACCOUNT_ID` / `_ASSET_ID`; see [Fireblocks Signer](/crec/guides/signers/fireblocks). | | `privy.ErrEnvPrivyAppIDNotSet` (etc.) | `PRIVY_*` env var missing when calling `FromEnv`. | Set `PRIVY_APP_ID` / `_APP_SECRET` / `_WALLET_ID`; see [Privy Signer](/crec/guides/signers/privy). | | `429 Too Many Requests` on watcher endpoints | Rate-limited. Inside `WaitForActive` / `WaitForArchived` the polling loop classifies this as transient and continues to the next tick. | Increase the poll interval with `crec.WithWatcherPolling(...)`. For all other client methods, the SDK does not retry: wrap the call in your own retry logic. | | `404 Not Found` on a `wallet_id` / `watcher_id` / `operation_id` | The resource was archived, never existed, or belongs to a different channel. | Re-list within the right channel; check archive status. | If the symptom isn't here, look up the exact `Err…` value in the per-package catalog below. ## Sentinel errors by package All sentinel errors are exported `var Err…` values. Check with `errors.Is(err, pkg.ErrSomething)`. ### `crec` (root) | Error | Returned by | | ---------------------------------------- | ------------------------------------------------------------------------- | | `crec.ErrBaseURLRequired` | `NewClient` / `NewAPIClient` when `baseURL == ""`. | | `crec.ErrAPIKeyRequired` | `NewClient` / `NewAPIClient` when `apiKey == ""`. | | `crec.ErrInvalidEventVerificationConfig` | `NewClient` when `validSigners` is set but `minRequiredSignatures <= 0`. | | `crec.ErrIncompleteDONConfig` | `NewClient` when `WithDONConfig` was used but the DON unit is incomplete. | | `crec.ErrListNetworks` | `Client.ListNetworks` when the underlying API call fails. | | `crec.ErrNilResponse` | `Client.ListNetworks` when the API client returns a nil response. | ### `apierror` (conflict sentinels) HTTP **409** responses are mapped to canonical sentinels based on `ApplicationError.code`, so `errors.Is` works across packages: | Error | Trigger (`ApplicationError.code`) | | -------------------------------------- | --------------------------------- | | `apierror.ErrChannelAlreadyExists` | `CHANNEL_ALREADY_EXISTS` | | `apierror.ErrWalletAlreadyExists` | `WALLET_ALREADY_EXISTS` | | `apierror.ErrWatcherAlreadyExists` | `WATCHER_ALREADY_EXISTS` | | `apierror.ErrIdempotencyKeyMismatch` | `IDEMPOTENCY_KEY_MISMATCH` | | `apierror.ErrOperationNotFinalizable` | `OPERATION_NOT_FINALIZABLE` | | `apierror.ErrOperationNotCancellable` | `OPERATION_NOT_CANCELLABLE` | | `apierror.ErrOperationDeadlineElapsed` | `OPERATION_DEADLINE_ELAPSED` | | `apierror.ErrWalletAlreadyArchived` | `WALLET_ALREADY_ARCHIVED` | | `apierror.ErrChainUnavailable` | `CHAIN_UNAVAILABLE` | An unrecognized or missing code returns no sentinel (forward-compatible for codes added after this SDK release). ### `channels` | Error | Notes | | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | | `channels.ErrChannelNotFound` | 404 lookup. | | `channels.ErrOptionsRequired`, `ErrAPIClientRequired` | Constructor validation. | | `channels.ErrChannelNameRequired`, `ErrChannelNameTooLong` | Channel name validation. | | `channels.ErrCreateChannel`, `ErrGetChannel`, `ErrListChannels`, `ErrUpdateChannel`, `ErrArchiveChannel` | API call failures (wrap the underlying error). | | `channels.ErrUnexpectedStatusCode`, `ErrNilResponse`, `ErrNilResponseBody` | HTTP-layer issues. | ### `events` | Error | Notes | | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `events.ErrChannelIDRequired` | Missing `channel_id` parameter. | | `events.ErrOptionsRequired`, `ErrCRECClientRequired` | Constructor validation. | | `events.ErrChannelNotFound` | Channel does not exist (404). | | `events.ErrPollEvents`, `ErrSearchEvents`, `ErrGetEvents` | API call failed; wraps the underlying status. | | `events.ErrVerifyEvent` | Verification failed; check the wrapped cause. | | `events.ErrWorkflowOwnerMismatch` | The workflow owner embedded in the OCR report differs from the expected owner. | | `events.ErrInsufficientValidSignatures` | The OCR proof does not reach the configured signature threshold. | | `events.ErrInvalidEventHash` | The supplied event hash doesn't match the verifiable payload. | | `events.ErrNoOCRProofs` / `ErrMultipleOCRProofs` | The verifiable event has zero or more than one OCR proof. | | `events.ErrParseSignature`, `ErrRecoverPubKeyFromSignature` | Signature mechanics failed (corrupted payload). | | `events.ErrParseOCRReport`, `ErrParseOCRContext`, `ErrOCRReportTooShort` | Malformed OCR report. | | `events.ErrParseEventPayload`, `ErrMarshalEventPayload`, `ErrMarshalEventToJSON` | Encoding round-trip failure. | | `events.ErrDecodeEvent`, `ErrDecodeVerifiableEvent` | Decoding failed; check ABI / payload. | | `events.ErrOnlyWatcherEventsSupported` | `Verify` was called on a non-`watcher.event` event. | | `events.ErrOnlyOperationStatusSupported` | `VerifyOperationStatus` was called on a non-`operation.status` event. | | `events.ErrOnlyQueryStatusSupported` | `VerifyQueryStatus` was called on a non-`query.status` event. | | `events.ErrVerificationNotConfigured` | Empty signer set; configure `WithEventVerification`. | | `events.ErrOrgIDRequired`, `ErrWorkflowOwnerRequired`, `ErrOrgIDOrWorkflowOwnerReq` | Missing identity context for verification: supply via options or per-call. | | `events.ErrDeriveWorkflowOwner` | `WithCRETenantID` derivation failed. | | `events.ErrUnexpectedStatusCode`, `ErrNilResponse`, `ErrNilResponseBody`, `ErrBadRequest` | HTTP-layer issues. | | `events.ErrInvalidMinRequiredSignatures`, `ErrInvalidSignerAddress`, `ErrDuplicateSigner`, `ErrMinSignersExceedsUnique` | Verification configuration validation (`WithEventVerification`). | | `events.ErrNilWatcherEventPayload`, `ErrVerifiableEventRequired`, `ErrNilVerifiablePayload` | Hashing/decoding called with nil or empty payload. | | `events.ErrDecodeNilEvent`, `ErrDecodeNilEventID`, `ErrDecodeNilEventProofs` | `Decode` was called with an event missing required fields. | | `events.ErrDecodeVerifiableEmpty`, `ErrDecodeVerifiableNilOrEmpty`, `ErrDecodeVerifiableInvalidBase64`, `ErrDecodeVerifiableInvalidJSON` | `DecodeVerifiableEvent` was called with a malformed verifiable payload. | | `events.ErrInvalidOCRSignatureLength`, `ErrInvalidOCRSignatureRecovery` | OCR signature is not 65 bytes or has an invalid recovery byte. | ### `watchers` | Error | Notes | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `watchers.ErrWatcherNotFound` | 404 lookup. | | `watchers.ErrChannelIDRequired`, `ErrWatcherIDRequired`, `ErrNameRequired` | Argument validation. | | `watchers.ErrWatcherNameTooShort` | Name must be ≥4 characters. | | `watchers.ErrServiceRequired`, `ErrAddressRequired`, `ErrEventsRequired` | Missing required fields on `CreateWithService`. | | `watchers.ErrABIRequired`, `ErrInvalidABIType`, `ErrEventNotInABI` | `CreateWithABI` validation. | | `watchers.ErrChainSelectorRequired` | Missing `chain_selector`. | | `watchers.ErrWaitForActiveTimeout` | `WaitForActive` exceeded its deadline. | | `watchers.ErrWaitForArchivedTimeout` | `WaitForArchived` exceeded its deadline. | | `watchers.ErrWatcherDeploymentFailed` | Watcher transitioned to `failed`. | | `watchers.ErrWatcherIsArchiving`, `ErrWatcherAlreadyArchived`, `ErrWatcherArchiveFailed` | Terminal-state errors during waits. | | `watchers.ErrUnexpectedStatus`, `ErrEmptyResponse`, `ErrNilResponse` | HTTP-layer issues. | | `watchers.ErrCreateWatcherRequest`, `ErrCreateWatcherService`, `ErrCreateWatcherABI`, `ErrListWatchers`, `ErrGetWatcher`, `ErrUpdateWatcher`, `ErrArchiveWatcher`, `ErrCheckWatcherStatus`, `ErrUnexpectedStatusCode` | API-call failures (wrap the underlying error). | ### `wallets` | Error | Notes | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `wallets.ErrWalletNotFound` | 404 lookup. | | `wallets.ErrNameRequired`, `ErrNameTooLong` | Name validation. | | `wallets.ErrChainSelectorRequired`, `ErrWalletOwnerAddressRequired`, `ErrInvalidWalletOwnerAddress` | Required fields. | | `wallets.ErrWalletTypeRequired`, `ErrUnsupportedWalletType` | Wallet type validation. | | `wallets.ErrWalletIDRequired` | Missing `wallet_id`. | | `wallets.ErrStatusChannelIDZero` | `StatusChannelId` was supplied as the zero UUID on `Create`. Omit the field or pass a real channel ID. | | `wallets.ErrEcdsaSignersRequired`, `ErrRsaSignersRequired` | The matching signer list is required for the chosen wallet type. | | `wallets.ErrInvalidSignersForEcdsa`, `ErrInvalidSignersForRsa` | Wrong signer-list field for the wallet type. | | `wallets.ErrDuplicateEcdsaSigner`, `ErrDuplicateRsaSigner` | The signer list contains duplicate entries. | | `wallets.ErrInvalidEcdsaSigner`, `ErrInvalidRsaSigner` | Malformed signer entries. | | `wallets.ErrInvalidLimit`, `ErrInvalidOffset`, `ErrInvalidOwnerAddress` | List-filter validation. | | `wallets.ErrCreateWallet`, `ErrGetWallet`, `ErrListWallets`, `ErrUpdateWallet`, `ErrArchiveWallet`, `ErrUnexpectedStatusCode`, `ErrNilResponse`, `ErrNilResponseBody` | API-call failures. | ### `transact` | Error | Notes | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `transact.ErrChannelIDRequired`, `ErrChainSelectorRequired`, `ErrAddressRequired`, `ErrWalletOperationIDRequired`, `ErrAtLeastOneTransactionRequired`, `ErrSignatureRequired` | Argument validation. The SDK also validates `Operation.Deadline`/`ID`/`Transactions` upstream; see [`transact/types`](#transacttypes). | | `transact.ErrInvalidDeadline` | Deadline must fit `int64` and be ≥0. | | `transact.ErrChannelNotFound`, `ErrOperationNotFound` | 404 lookup. | | `transact.ErrDraftNotFound` | Draft operation not found (404). | | `transact.ErrDraftNotFinalizable` | Operation is not in `pending_signature` state (409 `OPERATION_NOT_FINALIZABLE`). | | `transact.ErrDraftNotCancellable` | Operation is not in `pending_signature` state (409 `OPERATION_NOT_CANCELLABLE`). | | `transact.ErrDigestRequired`, `ErrSignatureRequired` | Missing digest or signature on finalize. | | `transact.ErrCreateOperation`, `ErrGetOperation`, `ErrListOperations`, `ErrSendOperation`, `ErrUnexpectedStatusCode`, `ErrNilResponse`, `ErrNilResponseBody` | API-call failures. | ### `transact/types` | Error | Notes | | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | `types.ErrOperationIDRequired`, `ErrOperationIDNonNegative` | Operation `ID` is missing or negative. | | `types.ErrOperationDeadlineRequired`, `ErrOperationDeadlineNonNegative` | Operation `Deadline` is missing or negative (`big.NewInt(0)` means no expiration). | | `types.ErrNoTransactions` | Operation must contain at least one transaction. | | `types.ErrTransactionValueRequired`, `ErrTransactionValueNonNegative` | Each transaction must set a non-negative `Value`. | | `types.ErrChainIDNonNegative`, `ErrFailedParseChainID` | EIP-712 domain chain ID validation. | ### `transact/eip712` | Error | Notes | | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | | `eip712.ErrOperationRequired`, `ErrSignerRequired` | Constructor validation. | | `eip712.ErrParseChainSelector`, `ErrGetChainFamily`, `ErrUnsupportedChainFamily`, `ErrGetChainID`, `ErrInvalidChainIDString` | Chain selector lookup failed. | | `eip712.ErrCreateTypedData`, `ErrComputeOperationHash`, `ErrHashOperation`, `ErrSignOperation` | Typed-data assembly, hashing, or signing failed. | ### `transact/signer/fireblocks` | Error | Notes | | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `fireblocks.ErrAPIKeyRequired`, `ErrPrivateKeyPEMRequired`, `ErrVaultAccountIDRequired`, `ErrAssetIDRequired` | Constructor validation when configuring the signer programmatically. | | `fireblocks.ErrEnvFireblocksAPIKey`, `ErrEnvFireblocksAPISecret`, `ErrEnvFireblocksVaultAcct`, `ErrEnvFireblocksAssetID` | Required `FIREBLOCKS_*` environment variable was not set when using `FromEnv`. | | `fireblocks.ErrFailedParsePEMBlock`, `ErrTrailingGarbageAfterPEM`, `ErrPrivateKeyNotRSA`, `ErrFailedParsePrivateKey` | Provided API secret is not a valid RSA PEM. | | `fireblocks.ErrTypedDataNil`, `ErrNegativeUnsignedTypedValue`, `ErrFloat64PrecisionLoss`, `ErrParseTypedDataIntegerString` | Typed-data encoding / numeric conversion error. | | `fireblocks.ErrCreateSigningOperationFailed`, `ErrCreateTypedMessageOperationFailed`, `ErrFireblocksOperationTerminal`, `ErrGetVaultAccountNonOK` | Fireblocks API call failed or operation ended in a terminal non-success state. | ### `transact/signer/privy` | Error | Notes | | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | | `privy.ErrAppIDRequired`, `ErrAppSecretRequired`, `ErrWalletIDRequired` | Constructor validation. | | `privy.ErrEnvPrivyAppIDNotSet`, `ErrEnvPrivyAppSecretNotSet`, `ErrEnvPrivyWalletIDNotSet` | Required `PRIVY_*` environment variable was not set when using `FromEnv`. | | `privy.ErrPrivyUnauthorized`, `ErrPrivyRPCUnexpectedStatus`, `ErrPrivyGetWalletUnexpectedStatus` | Privy API call failed (auth or non-2xx response). | ## Async error states Some failures don't surface synchronously: they appear as a state transition on a `*.status` event: | State | Meaning | Recovery | | ----------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `WatcherStatus.failed` | Watcher provisioning failed. | Inspect `WatcherStatusPayload.status_reason`; usually requires re-creating the watcher with corrected parameters. | | `WatcherEventStatus.archive_failed` | Archive teardown failed. | Retry the archive `PATCH`; if it persists, contact support. | | `WalletStatus.failed` | Smart Account deploy failed. | Inspect `WalletStatusPayload.status_reason`; provision a fresh wallet. | | `OperationStatus.failed` | Operation execution failed (revert, gas, …). | Inspect `OperationStatusPayload.status_reason`; resubmit with corrected calldata. | | `OperationStatus.expired` | Draft deadline elapsed before finalization. | Create a new draft with a fresh `wallet_operation_id`. See [Drafts](/crec/concepts/drafts). | | `QueryStatus.failed` | Chain query execution failed. | Inspect `QueryError` in the result; check contract address and calldata. See [Chain Queries](/crec/concepts/queries). | | `QueryStatus.expired` | Query TTL elapsed before terminal callback. | Resubmit the query with a new idempotency key. See [Chain Queries](/crec/concepts/queries). | Subscribe to the appropriate `*.status` event stream to react to these transitions in real time. See [Lifecycles](/crec/reference/lifecycles). ## Worked example ```go op, err := ext.PrepareRequestSubscriptionWithTokenApprovalOperation( fundAdminAddr, fundTokenId, amount, refID, paymentToken, ) if err != nil { return fmt.Errorf("build op: %w", err) } resp, err := client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector) if err != nil { switch { case errors.Is(err, transact.ErrChannelNotFound): // bad channel id case errors.Is(err, eip712.ErrUnsupportedChainFamily): // unsupported chain default: return err } } ``` ## See also - [REST API Reference](/crec/reference/rest-api): REST error envelope. - [Lifecycles](/crec/reference/lifecycles): async state transitions. - [Event Verification](/crec/concepts/event-verification): what `events.ErrVerifyEvent` actually proves. --- # Service Limits Source: https://docs.chain.link/crec/reference/service-limits Last Updated: 2026-08-31 This page lists the hard and soft limits that apply to CRE Connect resources. All hard limits are enforced server-side and are reflected in the OpenAPI specification ([`/api/crec/openapi.json`](/api/crec/openapi.json)); rate limits and per-environment quotas are negotiated with the Chainlink team. ## Wallet (Smart Account) limits Enforced via the OpenAPI `RSASignersList` and `ECDSASignersList` schemas (`maxItems: 10`, `minItems: 0`, `uniqueItems: true`). | Limit | Value | Notes | | ---------------------------------- | ----------- | --------------------------------------------------- | | `allowed_ecdsa_signers` per wallet | **0–10** | Unique 42-character `0x` Ethereum addresses. | | `allowed_rsa_signers` per wallet | **0–10** | Unique RSA public keys (`{e, n}` hex). | | RSA modulus minimum | 2048 bits | `n` must be hex of ≥ 256 bytes. | | RSA exponent | 2–17 bytes | `e` is hex; common values are `0x010001` (= 65537). | | Wallet `name` | 1–255 chars | Non-blank. | | Wallet `description` | 1–255 chars | Non-blank. | A wallet's signer set is **fixed at creation time**. To rotate signers today, archive the old wallet and provision a new one with the updated lists; see [Manage Signers](/crec/guides/wallets/manage-signers). ## Watcher limits | Limit | Value | Notes | | ---------------------------- | --------------- | ---------------------------------------------------------- | | Watcher `name` | 4–255 chars | Validated by the SDK (`ErrWatcherNameTooShort`). | | Service watchers per channel | per-environment | Soft limit: contact support to raise. | | ABI watchers per channel | per-environment | Soft limit. | | Events list per ABI watcher | 1+ | At least one event must be supplied (`ErrEventsRequired`). | | ABI types accepted | only `event` | `ErrInvalidABIType` for any other ABI fragment. | ## Channel limits | Limit | Value | Notes | | ----------------------------------- | ----------- | --------------------------------------------------------- | | Channel `name` | 1–255 chars | Non-blank. | | Channel `description` | 1–255 chars | Non-blank. | | Active watchers required to archive | 0 | A channel cannot be archived while any watcher is active. | ## Operation limits | Limit | Value | Notes | | -------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------- | | Transactions per operation | 1–16 | At least one transaction (`ErrAtLeastOneTransactionRequired`). | | `wallet_operation_id` | unique per (wallet, chain) among non-terminal statuses | Enforces single-use idempotency. Can be reused after `cancelled` / `expired` / `failed`. | | Deadline | non-negative `int64` | `ErrInvalidDeadline` for negative or overflowing values. `0` = no expiration. | | Deadline buffer | 60 seconds | Server rejects finalize 60s before on-chain deadline to allow relay time. | ## Query limits | Limit | Value | Notes | | --------------- | ------------------------------------- | ------------------------------------------------------------------ | | Default TTL | 5 minutes | Queries expire if no terminal callback arrives within this window. | | Metadata size | max 16 KB | Optional `metadata` field on query create. | | `from_address` | optional | Defaults to zero address (`0x0000…0000`) if omitted. | | Block selection | `latest`, `finalized`, `block_number` | Note: `safe` is not available for queries (only for watchers). | ## Pagination All listing endpoints (`/wallets`, `/channels`, `/channels/{id}/watchers`, `/channels/{id}/operations`, `/channels/{id}/queries`, `/channels/{id}/events`) accept `limit` and either `offset` or a time-based cursor. Per-page maxima are enforced server-side; the response always includes `has_more: bool` so applications can paginate without knowing the cap. Refer to the [interactive REST reference](/api/crec/docs) for each endpoint's exact `limit` parameter and any server-side cap. ## Rate limits When you exceed a rate limit, the API returns `429 Too Many Requests`. The Go SDK's watcher polling helpers (`WaitForActive` / `WaitForArchived`) classify `429` as transient and continue to the next poll tick; for every other endpoint the SDK does not retry: implement your own retry logic. See [Error Handling](/crec/reference/error-handling) for the full classification. ## Connectivity limits | Limit | Value | Notes | | -------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | HTTP request timeout | none unless you set one | When you don't pass `crec.WithHTTPClient`, the SDK uses Go's `http.DefaultClient`, which has no timeout. Provide an `*http.Client` with `Timeout` set in production. | | WebSocket support | not currently exposed | All event delivery is over HTTP polling. | ## Cryptographic limits | Limit | Value | Notes | | ------------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DefaultMinRequiredSignatures` | 4 | F+1 where F = 3 (production DON BFT). Don't raise; see [SDK Configuration](/crec/reference/sdk-configuration#witheventverificationmin-int-signers-string). | | `DefaultValidSigners` | 10 production DON keys | Override with `WithEventVerification` for non-production environments. | ## See also - [REST API Reference](/crec/reference/rest-api): endpoint contracts. - [Error Handling](/crec/reference/error-handling): what to do when you hit a limit. - [SDK Configuration](/crec/reference/sdk-configuration): tunables that adjust client-side behaviour around limits.