Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/blockchain-applied-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@evolution-sdk/evolution": minor
---

Add Blockchain Applied (BCA) as a built-in provider (`Client.make(...).withBlockchainApplied({ baseUrl, token })`, or `BlockchainAppliedProvider` / `mainnet` / `preprod` / `preview` / `custom` directly). Covers the full `ProviderEffect` interface: `getProtocolParameters`, `getUtxos`, `getUtxosWithUnit`, `getUtxoByUnit`, `getUtxosByOutRef`, `getDelegation`, `getDatum`, `awaitTx`, `submitTx`, and `evaluateTx`.
6 changes: 6 additions & 0 deletions .env.test.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ MAESTRO_PREPROD_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Optional: override the default preprod endpoint
# MAESTRO_PREPROD_URL=https://preprod.gomaestro-api.org/v1

# ── Blockchain Applied (BCA) ──────────────────────────────────────────────────
# Required to run BCA tests. Ask the BCA team for a preprod bearer token.
BCA_PREPROD_KEY=bca_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Optional: override the default preprod endpoint
# BCA_PREPROD_URL=https://staging.blockchain-applied.com/api_preprod/v1

# ── Kupmios (self-hosted or Demeter.run) ──────────────────────────────────────
# Both URLs are required to run Kupmios tests.
# If you use Demeter, provide one Kupo key and one Ogmios key.
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ CLAUDE.md
AGENTS.md

# Local env overrides — never commit API keys
**/*.local
.env.local
.env.test.local
42 changes: 24 additions & 18 deletions docs/content/docs/advanced/custom-providers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ description: Implement your own blockchain provider
# Custom Providers

<Callout type="info">
The SDK currently provides four built-in providers. Custom provider implementation requires working with the internal `ProviderEffect` interface — this is an advanced topic and the API may change.
The SDK currently provides five built-in providers. Custom provider implementation requires working with the internal
`ProviderEffect` interface — this is an advanced topic and the API may change.
</Callout>

## Provider Interface

All providers implement the `ProviderEffect` interface internally. This is what each built-in provider (Blockfrost, Kupmios, Maestro, Koios) satisfies:
All providers implement the `ProviderEffect` interface internally. This is what each built-in provider (Blockfrost, Kupmios, Maestro, Koios, Blockchain Applied) satisfies:

```typescript
interface ProviderEffect {
Expand All @@ -32,31 +33,36 @@ Understanding this interface is useful for knowing what data each provider must

## Built-in Providers

| Provider | Connection | Best For |
| --------------- | ---------------- | --------------------------------- |
| **Blockfrost** | REST API | Production apps, simple setup |
| **Maestro** | REST API | Advanced queries, data analytics |
| **Koios** | REST API | Open source, community maintained |
| **Kupo/Ogmios** | WebSocket + REST | Self-hosted, devnet, full control |
| Provider | Connection | Best For |
| ---------------------- | ---------------- | -------------------------------------- |
| **Blockfrost** | REST API | Production apps, simple setup |
| **Maestro** | REST API | Advanced queries, data analytics |
| **Koios** | REST API | Open source, community maintained |
| **Kupo/Ogmios** | WebSocket + REST | Self-hosted, devnet, full control |
| **Blockchain Applied** | REST API | Full-featured Cardano indexer/node API |

## Using a Provider

```typescript twoslash
import { preprod, Client } from "@evolution-sdk/evolution"

// Blockfrost
const blockfrostClient = Client.make(preprod)
.withBlockfrost({
baseUrl: "https://cardano-preprod.blockfrost.io/api/v0",
projectId: process.env.BLOCKFROST_API_KEY!
})
const blockfrostClient = Client.make(preprod).withBlockfrost({
baseUrl: "https://cardano-preprod.blockfrost.io/api/v0",
projectId: process.env.BLOCKFROST_API_KEY!
})

// Kupo/Ogmios
const kupmiosClient = Client.make(preprod)
.withKupmios({
kupoUrl: "http://localhost:1442",
ogmiosUrl: "http://localhost:1337"
})
const kupmiosClient = Client.make(preprod).withKupmios({
kupoUrl: "http://localhost:1442",
ogmiosUrl: "http://localhost:1337"
})

// Blockchain Applied
const bcaClient = Client.make(preprod).withBlockchainApplied({
baseUrl: "https://api.blockchain-applied.com/api_preprod/v1",
token: process.env.BCA_API_KEY!
})
```

## Next Steps
Expand Down
13 changes: 12 additions & 1 deletion examples/with-vite-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,22 @@ const txHash = await sdk
.then(tx => tx.submit());
```

Or with the Blockchain Applied (BCA) provider:

```typescript
const sdk = client(preprod)
.withBlockchainApplied({
baseUrl: "https://api.blockchain-applied.com/api_preprod/v1",
token: "your_bca_token"
})
.withCip30(walletApi);
```

### Key Concepts

- **Client Assembly**: Start with `client(chain)` and add capabilities with `.withX(...)`
- **Wallet Capability**: Connect a CIP-30 wallet with `.withCip30(walletApi)`
- **Provider Capability**: Add Blockfrost, Maestro, Kupmios, or Koios with `.withBlockfrost(...)` and the related methods
- **Provider Capability**: Add Blockfrost, Maestro, Kupmios, Koios, or Blockchain Applied with `.withBlockfrost(...)` and the related methods
- **Transaction Building**: Chain operations like `payToAddress()`, `collectFrom()`, etc.
- **Signing & Submission**: Build → Sign → Submit pipeline

Expand Down
14 changes: 14 additions & 0 deletions packages/evolution/src/sdk/client/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ export interface KoiosConfig {
readonly token?: string
}

/**
* Configuration for the Blockchain Applied (BCA) provider.
*
* @since 2.1.0
* @category model
*/
export interface BlockchainAppliedConfig {
readonly baseUrl: string
readonly token?: string
}

/**
* Configuration for the Kupmios provider (Kupo + Ogmios).
*
Expand Down Expand Up @@ -156,6 +167,7 @@ export interface ClientAssembly {
readonly withKoios: (config: KoiosConfig) => ReadClient
readonly withKupmios: (config: KupmiosConfig) => ReadClient
readonly withMaestro: (config: MaestroConfig) => ReadClient
readonly withBlockchainApplied: (config: BlockchainAppliedConfig) => ReadClient
readonly withAddress: (address: string, rewardAddress?: string) => AddressClient
readonly withSeed: (config: SeedWalletConfig) => OfflineSignerClient
readonly withPrivateKey: (config: PrivateKeyWalletConfig) => OfflineSignerClient
Expand Down Expand Up @@ -190,6 +202,7 @@ export type AddressClient = EffectToPromiseAPI<AddressClientEffect> & {
readonly withKoios: (config: KoiosConfig) => ReadOnlyClient
readonly withKupmios: (config: KupmiosConfig) => ReadOnlyClient
readonly withMaestro: (config: MaestroConfig) => ReadOnlyClient
readonly withBlockchainApplied: (config: BlockchainAppliedConfig) => ReadOnlyClient
readonly effect: AddressClientEffect
}

Expand All @@ -205,6 +218,7 @@ export type OfflineSignerClient = EffectToPromiseAPI<OfflineSignerClientEffect>
readonly withKoios: (config: KoiosConfig) => SigningClient
readonly withKupmios: (config: KupmiosConfig) => SigningClient
readonly withMaestro: (config: MaestroConfig) => SigningClient
readonly withBlockchainApplied: (config: BlockchainAppliedConfig) => SigningClient
readonly effect: OfflineSignerClientEffect
}

Expand Down
11 changes: 8 additions & 3 deletions packages/evolution/src/sdk/client/internal/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const createAddressClient = (chain: Chain, wallet: Wallet.ReadOnlyWallet): Addre
withKoios: (config) => createReadOnlyClient(chain, Providers.koios(config), wallet),
withKupmios: (config) => createReadOnlyClient(chain, Providers.kupmios(config), wallet),
withMaestro: (config) => createReadOnlyClient(chain, Providers.maestro(config), wallet),
withBlockchainApplied: (config) => createReadOnlyClient(chain, Providers.blockchainApplied(config), wallet),
effect: wallet.effect satisfies AddressClientEffect
})

Expand All @@ -58,6 +59,7 @@ const createOfflineSignerClient = (chain: Chain, wallet: Signing.ResolvedSignerW
withKoios: (config) => createSigningClient(chain, Providers.koios(config), wallet),
withKupmios: (config) => createSigningClient(chain, Providers.kupmios(config), wallet),
withMaestro: (config) => createSigningClient(chain, Providers.maestro(config), wallet),
withBlockchainApplied: (config) => createSigningClient(chain, Providers.blockchainApplied(config), wallet),
effect: effectInterface
}
}
Expand Down Expand Up @@ -141,7 +143,8 @@ const createSigningClient = (
const createReadClient = (chain: Chain, provider: Provider.Provider): ReadClient => ({
...provider,
chain,
withAddress: (address, rewardAddress) => createReadOnlyClient(chain, provider, Wallets.readOnlyWallet(address, rewardAddress)(chain)),
withAddress: (address, rewardAddress) =>
createReadOnlyClient(chain, provider, Wallets.readOnlyWallet(address, rewardAddress)(chain)),
withSeed: (config) => createSigningClient(chain, provider, Wallets.seedWallet(config)(chain)),
withPrivateKey: (config) => createSigningClient(chain, provider, Wallets.privateKeyWallet(config)(chain)),
withCip30: (api) => createSigningClient(chain, provider, Wallets.cip30Wallet(api)(chain)),
Expand All @@ -155,8 +158,10 @@ export const client = (chain: Chain = mainnet): ClientAssembly => ({
withKoios: (config) => createReadClient(chain, Providers.koios(config)),
withKupmios: (config) => createReadClient(chain, Providers.kupmios(config)),
withMaestro: (config) => createReadClient(chain, Providers.maestro(config)),
withAddress: (address, rewardAddress) => createAddressClient(chain, Wallets.readOnlyWallet(address, rewardAddress)(chain)),
withBlockchainApplied: (config) => createReadClient(chain, Providers.blockchainApplied(config)),
withAddress: (address, rewardAddress) =>
createAddressClient(chain, Wallets.readOnlyWallet(address, rewardAddress)(chain)),
withSeed: (config) => createOfflineSignerClient(chain, Wallets.seedWallet(config)(chain)),
withPrivateKey: (config) => createOfflineSignerClient(chain, Wallets.privateKeyWallet(config)(chain)),
withCip30: (api) => createOfflineSignerClient(chain, Wallets.cip30Wallet(api)(chain))
})
})
7 changes: 6 additions & 1 deletion packages/evolution/src/sdk/client/internal/Providers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as BlockchainApplied from "../../provider/BlockchainApplied.js"
import * as Blockfrost from "../../provider/Blockfrost.js"
import * as Koios from "../../provider/Koios.js"
import * as Kupmios from "../../provider/Kupmios.js"
import * as Maestro from "../../provider/Maestro.js"
import type { Provider } from "../../provider/Provider.js"
import type { BlockfrostConfig, KoiosConfig, KupmiosConfig, MaestroConfig } from "../Client.js"
import type { BlockchainAppliedConfig, BlockfrostConfig, KoiosConfig, KupmiosConfig, MaestroConfig } from "../Client.js"

// ── Blockfrost ────────────────────────────────────────────────────────────────
export const blockfrost = (config: BlockfrostConfig): Provider => Blockfrost.custom(config.baseUrl, config.projectId)
Expand All @@ -18,3 +19,7 @@ export const kupmios = (config: KupmiosConfig): Provider =>
// ── Maestro ───────────────────────────────────────────────────────────────────
export const maestro = (config: MaestroConfig): Provider =>
new Maestro.MaestroProvider(config.baseUrl, config.apiKey, config.turboSubmit)

// ── Blockchain Applied ───────────────────────────────────────────────────────
export const blockchainApplied = (config: BlockchainAppliedConfig): Provider =>
BlockchainApplied.custom(config.baseUrl, config.token)
104 changes: 104 additions & 0 deletions packages/evolution/src/sdk/provider/BlockchainApplied.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Effect } from "effect"

import * as BlockchainAppliedEffect from "./internal/BlockchainAppliedEffect.js"
import type { Provider, ProviderEffect } from "./Provider.js"

const HOST = "https://api.blockchain-applied.com"

/**
* Blockchain Applied (BCA) provider for Cardano blockchain data access.
* Supports Bearer token authentication.
*
* @since 2.0.0
* @category constructors
*/
export class BlockchainAppliedProvider implements Provider {
readonly effect: ProviderEffect
readonly baseUrl: string
readonly token?: string

constructor(baseUrl: string, token?: string) {
this.baseUrl = baseUrl
this.token = token

this.effect = {
getProtocolParameters: () => BlockchainAppliedEffect.getProtocolParameters(baseUrl, token),
getUtxos: BlockchainAppliedEffect.getUtxos(baseUrl, token),
getUtxosWithUnit: BlockchainAppliedEffect.getUtxosWithUnit(baseUrl, token),
getUtxoByUnit: BlockchainAppliedEffect.getUtxoByUnit(baseUrl, token),
getUtxosByOutRef: BlockchainAppliedEffect.getUtxosByOutRef(baseUrl, token),
getDelegation: BlockchainAppliedEffect.getDelegation(baseUrl, token),
getDatum: BlockchainAppliedEffect.getDatum(baseUrl, token),
awaitTx: BlockchainAppliedEffect.awaitTx(baseUrl, token),
submitTx: BlockchainAppliedEffect.submitTx(baseUrl, token),
evaluateTx: BlockchainAppliedEffect.evaluateTx(baseUrl, token)
}
}

getProtocolParameters = () => Effect.runPromise(this.effect.getProtocolParameters())

getUtxos = (addressOrCredential: Parameters<Provider["getUtxos"]>[0]) =>
Effect.runPromise(this.effect.getUtxos(addressOrCredential))

getUtxosWithUnit = (
addressOrCredential: Parameters<Provider["getUtxosWithUnit"]>[0],
unit: Parameters<Provider["getUtxosWithUnit"]>[1]
) => Effect.runPromise(this.effect.getUtxosWithUnit(addressOrCredential, unit))

getUtxoByUnit = (unit: Parameters<Provider["getUtxoByUnit"]>[0]) => Effect.runPromise(this.effect.getUtxoByUnit(unit))

getUtxosByOutRef = (outRefs: Parameters<Provider["getUtxosByOutRef"]>[0]) =>
Effect.runPromise(this.effect.getUtxosByOutRef(outRefs))

getDelegation = (rewardAddress: Parameters<Provider["getDelegation"]>[0]) =>
Effect.runPromise(this.effect.getDelegation(rewardAddress))

getDatum = (datumHash: Parameters<Provider["getDatum"]>[0]) => Effect.runPromise(this.effect.getDatum(datumHash))

awaitTx = (
txHash: Parameters<Provider["awaitTx"]>[0],
checkInterval?: Parameters<Provider["awaitTx"]>[1],
timeout?: Parameters<Provider["awaitTx"]>[2]
) => Effect.runPromise(this.effect.awaitTx(txHash, checkInterval, timeout))

submitTx = (tx: Parameters<Provider["submitTx"]>[0]) => Effect.runPromise(this.effect.submitTx(tx))

evaluateTx = (tx: Parameters<Provider["evaluateTx"]>[0], additionalUTxOs?: Parameters<Provider["evaluateTx"]>[1]) =>
Effect.runPromise(this.effect.evaluateTx(tx, additionalUTxOs))
}

/**
* Pre-configured Blockchain Applied provider for Cardano mainnet.
*
* @since 2.0.0
* @category constructors
*/
export const mainnet = (token?: string): BlockchainAppliedProvider =>
new BlockchainAppliedProvider(`${HOST}/api_ada/v1`, token)

/**
* Pre-configured Blockchain Applied provider for Cardano preprod testnet.
*
* @since 2.0.0
* @category constructors
*/
export const preprod = (token?: string): BlockchainAppliedProvider =>
new BlockchainAppliedProvider(`${HOST}/api_preprod/v1`, token)

/**
* Pre-configured Blockchain Applied provider for Cardano preview testnet.
*
* @since 2.0.0
* @category constructors
*/
export const preview = (token?: string): BlockchainAppliedProvider =>
new BlockchainAppliedProvider(`${HOST}/api_preview/v1`, token)

/**
* Create a custom Blockchain Applied provider with a custom base URL.
*
* @since 2.0.0
* @category constructors
*/
export const custom = (baseUrl: string, token?: string): BlockchainAppliedProvider =>
new BlockchainAppliedProvider(baseUrl, token)
Loading