From 038b3c4499248ee8c27896b1f87b81d781782ab4 Mon Sep 17 00:00:00 2001 From: Dhruv-Varshney-developer Date: Tue, 19 Aug 2025 07:57:15 +0530 Subject: [PATCH 1/7] docs: Update Readme and code snippets for @ucanto/transport --- packages/transport/README.md | 113 +++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/packages/transport/README.md b/packages/transport/README.md index e243cadb..75ca49a1 100644 --- a/packages/transport/README.md +++ b/packages/transport/README.md @@ -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). @@ -21,12 +22,98 @@ 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); +const service = ed25519.parse(process.env.SERVICE_ID) +const issuer = ed25519.parse(process.env.CLIENT_KEYPAIR) + +// Mock fetch that simulates a UCAN service +const mockFetch = async (url, init) => { + 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 { + ok: true, + headers: new Map(Object.entries(response.headers)), + arrayBuffer: () => response.body, + } +} + +// 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') ``` +### Run it: +⚠️ These are test keys. Replace them with your own service ID and client keypair before using in production. + +```bash +SERVICE_ID="MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=" \ +CLIENT_KEYPAIR="MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM=" \ +node example.js +``` + + + +### 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). \ No newline at end of file From 6fd66a30a4972371d1a9e3224e9cbf623a08609c Mon Sep 17 00:00:00 2001 From: Dhruv-Varshney-developer Date: Tue, 19 Aug 2025 07:57:28 +0530 Subject: [PATCH 2/7] docs: Update Readme and code snippets for @ucanto/client --- packages/client/README.md | 87 +++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/packages/client/README.md b/packages/client/README.md index 800a37e2..7d9822ec 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -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. @@ -22,28 +23,80 @@ 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); +const service = ed25519.parse(process.env.SERVICE_ID) +const issuer = ed25519.parse(process.env.CLIENT_KEYPAIR) -const invocation = await Client.invoke({ +// Mock fetch that simulates a UCAN service +const mockFetch = async (url, input) => { + 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 { + ok: true, + headers: new Map(Object.entries(response.headers)), + arrayBuffer: () => response.body, + } +} + +// 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 response = await client.execute(invocation); -if (response.error) { - console.error('Invocation failed:', response.error); -} else { - console.log('Invocation succeeded:', response.result); -} +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) +``` + +### Run it: + +⚠️ These are test keys. Replace them with your own service ID and client keypair before using in production. + +```bash +SERVICE_ID="MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=" \ +CLIENT_KEYPAIR="MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM=" \ +node example.js ``` + +### Connecting to a Real Service + +Replace the `mockFetch` in the example with the real `fetch` and a valid service URL: + +```js +channel: HTTP.open({ url: new URL('https://api.example.com'), fetch }) +``` + +**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). \ No newline at end of file From d80540e723ea87a47749fa52d09fdd33e61c1df4 Mon Sep 17 00:00:00 2001 From: Dhruv Varshney Date: Tue, 26 Aug 2025 02:18:14 +0530 Subject: [PATCH 3/7] refactor: use native Response Constructor instead of custom constructor object Co-authored-by: ash --- packages/client/README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/client/README.md b/packages/client/README.md index 7d9822ec..cde21cd0 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -48,11 +48,7 @@ const mockFetch = async (url, input) => { const message = await Message.build({ receipts }) const response = await CAR.response.encode(message) - return { - ok: true, - headers: new Map(Object.entries(response.headers)), - arrayBuffer: () => response.body, - } + return new Response(response.body, { headers: response.headers }) } // Connect to mock service From 4a0a38db292485c7e57de53e50dbce4f2db1540f Mon Sep 17 00:00:00 2001 From: Dhruv-Varshney-developer Date: Tue, 26 Aug 2025 02:30:33 +0530 Subject: [PATCH 4/7] refactor: use native Response constructor instead of custom response object --- packages/transport/README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/transport/README.md b/packages/transport/README.md index 75ca49a1..e86143ae 100644 --- a/packages/transport/README.md +++ b/packages/transport/README.md @@ -47,13 +47,8 @@ const mockFetch = async (url, init) => { ) const responseMessage = await Message.build({ receipts }) - const response = await CAR.response.encode(responseMessage) - - return { - ok: true, - headers: new Map(Object.entries(response.headers)), - arrayBuffer: () => response.body, - } + const response = await CAR.response.encode(responseMessage) + return new Response(response.body, { headers: response.headers }) } // Create UCAN invocation From 2a896485b2ab2a9d202a27e1441b2efd539917a6 Mon Sep 17 00:00:00 2001 From: Dhruv-Varshney-developer Date: Tue, 26 Aug 2025 05:45:06 +0530 Subject: [PATCH 5/7] refactor: rename from CLIENT_KEYPAIR to AGENT_PRIVATE_KEY. --- packages/client/README.md | 4 ++-- packages/transport/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/client/README.md b/packages/client/README.md index cde21cd0..df1d4a00 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -31,7 +31,7 @@ import { ed25519 } from '@ucanto/principal' import { Receipt, Message } from '@ucanto/core' const service = ed25519.parse(process.env.SERVICE_ID) -const issuer = ed25519.parse(process.env.CLIENT_KEYPAIR) +const issuer = ed25519.parse(process.env.AGENT_PRIVATE_KEY) // Mock fetch that simulates a UCAN service const mockFetch = async (url, input) => { @@ -80,7 +80,7 @@ console.log(receipt.out.error ? 'Failed:' : 'Success:', receipt.out) ```bash SERVICE_ID="MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=" \ -CLIENT_KEYPAIR="MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM=" \ +AGENT_PRIVATE_KEY="MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM=" \ node example.js ``` diff --git a/packages/transport/README.md b/packages/transport/README.md index e86143ae..9066b9cf 100644 --- a/packages/transport/README.md +++ b/packages/transport/README.md @@ -29,7 +29,7 @@ import { ed25519 } from '@ucanto/principal' import { invoke, Message, Receipt } from '@ucanto/core' const service = ed25519.parse(process.env.SERVICE_ID) -const issuer = ed25519.parse(process.env.CLIENT_KEYPAIR) +const issuer = ed25519.parse(process.env.AGENT_PRIVATE_KEY) // Mock fetch that simulates a UCAN service const mockFetch = async (url, init) => { @@ -83,7 +83,7 @@ console.log('Received:', replyMessage.receipts.size, 'receipts') ```bash SERVICE_ID="MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=" \ -CLIENT_KEYPAIR="MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM=" \ +AGENT_PRIVATE_KEY="MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM=" \ node example.js ``` From 7abf3c3666da7d70b1027d231900d218dcf6305d Mon Sep 17 00:00:00 2001 From: Dhruv-Varshney-developer Date: Tue, 26 Aug 2025 07:24:37 +0530 Subject: [PATCH 6/7] fix: Replace Service private key with SERVICE DID. --- packages/client/README.md | 6 +++++- packages/transport/README.md | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/client/README.md b/packages/client/README.md index df1d4a00..abd5b980 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -30,7 +30,11 @@ import { CAR } from '@ucanto/transport' import { ed25519 } from '@ucanto/principal' import { Receipt, Message } from '@ucanto/core' -const service = ed25519.parse(process.env.SERVICE_ID) +// 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 like: Mg.. const issuer = ed25519.parse(process.env.AGENT_PRIVATE_KEY) // Mock fetch that simulates a UCAN service diff --git a/packages/transport/README.md b/packages/transport/README.md index 9066b9cf..0b902188 100644 --- a/packages/transport/README.md +++ b/packages/transport/README.md @@ -28,7 +28,11 @@ import { CAR } from '@ucanto/transport' import { ed25519 } from '@ucanto/principal' import { invoke, Message, Receipt } from '@ucanto/core' -const service = ed25519.parse(process.env.SERVICE_ID) +// 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 like: Mg.. const issuer = ed25519.parse(process.env.AGENT_PRIVATE_KEY) // Mock fetch that simulates a UCAN service From 5dc05d33f190086481fd4a63f850e12e4100c446 Mon Sep 17 00:00:00 2001 From: Dhruv-Varshney-developer Date: Tue, 26 Aug 2025 15:23:17 +0530 Subject: [PATCH 7/7] fix: Replace .env variables with setup instructions. --- packages/client/README.md | 52 ++++++++++++++++++++++++++++------ packages/transport/README.md | 54 ++++++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/packages/client/README.md b/packages/client/README.md index abd5b980..30926d15 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -78,25 +78,59 @@ const [receipt] = await connection.execute(invocation) console.log(receipt.out.error ? 'Failed:' : 'Success:', receipt.out) ``` -### Run it: +## Setup Instructions -⚠️ These are test keys. Replace them with your own service ID and client keypair before using in production. +### 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 -SERVICE_ID="MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=" \ -AGENT_PRIVATE_KEY="MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM=" \ -node example.js +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. -### Connecting to a Real Service +**SERVICE_URL** (Optional) +If you're connecting to a custom service, set both `SERVICE_DID` and `SERVICE_URL` environment variables. -Replace the `mockFetch` in the example with the real `fetch` and a valid service URL: -```js -channel: HTTP.open({ url: new URL('https://api.example.com'), fetch }) +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). \ No newline at end of file diff --git a/packages/transport/README.md b/packages/transport/README.md index 0b902188..31fc0b47 100644 --- a/packages/transport/README.md +++ b/packages/transport/README.md @@ -32,7 +32,7 @@ import { invoke, Message, Receipt } from '@ucanto/core' // 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 like: Mg.. +// 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 @@ -82,15 +82,57 @@ const replyMessage = await CAR.response.decode(response) console.log('Received:', replyMessage.receipts.size, 'receipts') ``` -### Run it: -⚠️ These are test keys. Replace them with your own service ID and client keypair before using in production. +## 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 -SERVICE_ID="MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=" \ -AGENT_PRIVATE_KEY="MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM=" \ -node example.js +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