Skip to content
119 changes: 103 additions & 16 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

## What It Provides
- **UCAN Invocation Handling**: Creates and signs capability invocations.
- **Connection Management**: Manages communication with UCAN services.
- **Batch Invocation Support**: Enables multiple invocations in a single request.
- **Secure Communication**: Ensures interactions are cryptographically signed and verified.
- **Delegation Support**: Creates and uses authorization delegations.

## How It Fits with Other Modules
- [`@ucanto/core`](../core/README.md): Defines capability structures and execution logic.
- [`@ucanto/core`](../core/README.md): Provides capability invocation and delegation logic.
- [`@ucanto/server`](../server/README.md): Processes invocations received from the client.
- [`@ucanto/interface`](../interface/README.md): Provides shared types for request and response handling.
- [`@ucanto/principal`](../principal/README.md): Manages cryptographic signing for invocations.
Expand All @@ -22,28 +23,114 @@ npm install @ucanto/client
```

## Example Usage
```ts
import * as Client from '@ucanto/client';
import { ed25519 } from '@ucanto/principal';
```js
import * as Client from '@ucanto/client'
import * as HTTP from '@ucanto/transport/http'
import { CAR } from '@ucanto/transport'
import { ed25519 } from '@ucanto/principal'
import { Receipt, Message } from '@ucanto/core'

const service = ed25519.Verifier.parse(process.env.SERVICE_ID);
const issuer = ed25519.Signer.parse(process.env.CLIENT_KEYPAIR);
// Parse the service DID (public key)
// SERVICE_DID should be a DID like: did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi
const service = ed25519.Verifier.parse(process.env.SERVICE_DID)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const service = ed25519.Verifier.parse(process.env.SERVICE_DID)
const service = DID.parse(process.env.SERVICE_DID)

// Parse the agent's private key
// AGENT_PRIVATE_KEY should be a base64 private key like: Mg..
const issuer = ed25519.parse(process.env.AGENT_PRIVATE_KEY)

const invocation = await Client.invoke({
// Mock fetch that simulates a UCAN service
const mockFetch = async (url, input) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's a good idea for a mock to be in the README, perhaps in a runnable code example in the repo. IMHO the README should shown how to use the library against an already running server - i.e. just the code you'd need to use the client. I'd link to the server README for actually setting up and running a server component.

const { invocations } = await CAR.request.decode(input)

const receipts = await Promise.all(
invocations.map(inv => Receipt.issue({
ran: inv.cid, // Link to the invocation
issuer: service, // Service signs the receipt
result: { ok: { status: 'success' } } // Fake success
}))
)

const message = await Message.build({ receipts })
const response = await CAR.response.encode(message)

return new Response(response.body, { headers: response.headers })
}

// Connect to mock service
const connection = Client.connect({
id: service,
channel: HTTP.open({ url: new URL('https://api.example.com'), fetch: mockFetch }),
codec: CAR.outbound,
})

// Create and execute invocation
const invocation = Client.invoke({
issuer,
audience: service,
capability: {
can: 'file/read',
with: 'file://example.txt'
can: 'store/add',
with: issuer.did(),
nb: { link: 'bafybeigwflfnv7tjgpuy52ep45cbbgkkb2makd3bwhbj3ueabvt3eq43ca' }
}
});
})

const [receipt] = await connection.execute(invocation)
// A receipt is a signed result from the service proving the invocation was processed.
console.log(receipt.out.error ? 'Failed:' : 'Success:', receipt.out)
```

## Setup Instructions

### Environment Variables

**AGENT_PRIVATE_KEY**
Set the key your client should use to sign UCAN invocations. You can generate Ed25519 keys with the ucanto library.

#### Usage

Create a file called `generate-keys.js`:

```javascript
import { ed25519 } from '@ucanto/principal'

const response = await client.execute(invocation);
if (response.error) {
console.error('Invocation failed:', response.error);
} else {
console.log('Invocation succeeded:', response.result);
async function generateKeys() {
const keypair = await ed25519.generate()

const privateKey = ed25519.format(keypair)

console.log('AGENT_PRIVATE_KEY=' + privateKey)
}

generateKeys().catch(console.error)
```

Then run it:

```bash
node generate-keys.js
```

**SERVICE_DID**
Set the DID of the service you want to connect to. Check the service's documentation for their public DID.

**SERVICE_URL** (Optional)
If you're connecting to a custom service, set both `SERVICE_DID` and `SERVICE_URL` environment variables.


For example, Storacha has following `SERVICE_DID` and `SERVICE_URL`:

```bash
# Storacha uses these default values:
SERVICE_DID="did:web:up.storacha.network"
SERVICE_URL="https://up.storacha.network"
```

Set your environment variables like so:
```bash
AGENT_PRIVATE_KEY="your_generated_private_key_here" \
SERVICE_DID="did:key:service_provider_did_here" \
```


**What's happening:** UCAN services expect CAR-encoded requests and return CAR-encoded receipts with cryptographic signatures. The mock simulates this entire flow so the example works without a real service.

For more details, see the [`ucanto` documentation](https://github.com/storacha/ucanto).
154 changes: 141 additions & 13 deletions packages/transport/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# @ucanto/transport

`@ucanto/transport` provides encoding, decoding, and transport mechanisms for UCAN-based RPC. It ensures reliable communication between clients and servers using standardized serialization formats.
`@ucanto/transport` provides encoding, decoding, and transport mechanisms for UCAN-based RPC. It handles the serialization and network communication needed for secure UCAN message exchange between clients and servers.

## What It Provides
- **Pluggable Transport Layer**: Supports multiple encoding formats like CAR and CBOR.
- **Standardized Encoding**: Ensures consistency in UCAN invocation serialization.
- **Extensible Communication**: Enables integration with various network protocols.
- **CAR Encoding/Decoding**: Serializes UCAN messages in Content Addressable Archive format.
- **HTTP Transport**: Enables UCAN communication over HTTP with proper content negotiation.
- **Pluggable Codec System**: Supports multiple encoding formats with inbound/outbound codecs.
- **Legacy Support**: Maintains compatibility with older CBOR-based UCAN message formats.

## How It Fits with Other Modules
- [`@ucanto/core`](../core/README.md): Uses transport mechanisms for executing capabilities.
- [`@ucanto/server`](../server/README.md): Relies on transport modules for request handling.
- [`@ucanto/interface`](../interface/README.md): Defines standard transport-related types.
- [`@ucanto/principal`](../principal/README.md): Facilitates secure communication using identity-based encryption.
- [`@ucanto/client`](../client/README.md): Uses transport to communicate with services.
- [`@ucanto/server`](../server/README.md): Uses transport to receive and respond to requests.
- [`@ucanto/core`](../core/README.md): Provides the UCAN message structures that get transported.
- [`@ucanto/interface`](../interface/README.md): Defines transport-related types and interfaces.

For an overview and detailed usage information, refer to the [main `ucanto` README](../../Readme.md).

Expand All @@ -21,12 +22,139 @@ npm install @ucanto/transport
```

## Example Usage
```ts
import * as CAR from '@ucanto/transport/car';
import * as CBOR from '@ucanto/transport/cbor';
```js
import * as HTTP from '@ucanto/transport/http'
import { CAR } from '@ucanto/transport'
import { ed25519 } from '@ucanto/principal'
import { invoke, Message, Receipt } from '@ucanto/core'

const encoded = CAR.encode({ invocations: [] });
const decoded = CBOR.decode(encoded);
// Parse the service DID (public key)
// SERVICE_DID should be a DID like: did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi
const service = ed25519.Verifier.parse(process.env.SERVICE_DID)
// Parse the agent's private key
// AGENT_PRIVATE_KEY should be a base64 private key starting with: Mg..
const issuer = ed25519.parse(process.env.AGENT_PRIVATE_KEY)

// Mock fetch that simulates a UCAN service
const mockFetch = async (url, init) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'd save this for a runnable example in the repo, or better yet a runnable example with an actual server. Mocking the server like this is not great even for tests as it bypasses all delegation chain validation.

Note: in tests, you don't even need a HTTP server running - a Ucanto server is a channel, so you can pass it to the client as the channel to use.

console.log('Sending request to:', url)
console.log('Request headers:', init.headers)

// Simulate a service response with a receipt
const { invocations } = await CAR.request.decode(init)
const receipts = await Promise.all(
invocations.map(inv => Receipt.issue({
ran: inv.cid,
issuer: service,
result: { ok: { status: 'added' } }
}))
)

const responseMessage = await Message.build({ receipts })
const response = await CAR.response.encode(responseMessage)
return new Response(response.body, { headers: response.headers })
}

// Create UCAN invocation
const invocation = invoke({
issuer,
audience: service,
capability: {
can: 'store/add',
with: issuer.did(),
nb: { link: 'bafybeigwflfnv7tjgpuy52ep45cbbgkkb2makd3bwhbj3ueabvt3eq43ca' }
}
})

// Package for transport
const message = await Message.build({ invocations: [invocation] })
const request = await CAR.request.encode(message)

// Create HTTP channel and send
const channel = HTTP.open({
url: new URL('https://api.example.com'),
fetch: mockFetch
})
const response = await channel.request(request)

// Unpack response
const replyMessage = await CAR.response.decode(response)
console.log('Received:', replyMessage.receipts.size, 'receipts')
```

## Setup Instructions

### Environment Variables

**AGENT_PRIVATE_KEY**
Set the key your client should use to sign UCAN invocations. You can generate Ed25519 keys with the ucanto library.

#### Usage

Create a file called `generate-keys.js`:

```javascript
import { ed25519 } from '@ucanto/principal'

async function generateKeys() {
const keypair = await ed25519.generate()

const privateKey = ed25519.format(keypair)

console.log('AGENT_PRIVATE_KEY=' + privateKey)
}

generateKeys().catch(console.error)
```

Then run it:

```bash
node generate-keys.js
```

**SERVICE_DID**
Set the DID of the service you want to connect to. Check the service's documentation for their public DID.

**SERVICE_URL** (Optional)
If you're connecting to a custom service, set both `SERVICE_DID` and `SERVICE_URL` environment variables.


For example, Storacha has following `SERVICE_DID` and `SERVICE_URL`:

```bash
# Storacha uses these default values:
SERVICE_DID="did:web:up.storacha.network"
SERVICE_URL="https://up.storacha.network"
```

Set your environment variables like so:
```bash
AGENT_PRIVATE_KEY="your_generated_private_key_here" \
SERVICE_DID="did:key:service_provider_did_here" \
```


### Advanced: Pluggable Codecs

Transport provides a codec system for different encoding strategies:

```js
import { Codec, CAR } from '@ucanto/transport'

// Outbound codec (client-side)
const outbound = Codec.outbound({
encoders: { 'application/vnd.ipld.car': CAR.request },
decoders: { 'application/vnd.ipld.car': CAR.response },
})

// Inbound codec (server-side)
const inbound = Codec.inbound({
decoders: { 'application/vnd.ipld.car': CAR.request },
encoders: { 'application/vnd.ipld.car': CAR.response },
})
```

**What's happening:** Transport handles the low-level details of UCAN communication - encoding messages into CAR format, managing HTTP headers, content negotiation, and error handling. Most developers use `@ucanto/client` which handles this automatically.

For more details, see the [`ucanto` documentation](https://github.com/storacha/ucanto).
Loading