Skip to content

Repository files navigation

ucanto

Core TestsPrincipal TestsTransport TestsInterface TestsServer TestsClient TestsValidator Tests

(u)canto is a library for UCAN based RPC that provides:

  1. A declarative system for defining [capabilities][] and [abilities][] (roughly equivalent to HTTP routes in REST).
  2. A system for binding capability handles (a.k.a providers) to form services with built-in routing.
  3. A UCAN validation system.
  4. A runtime for executing UCAN capability invocations.
  5. A pluggable transport layer.
  6. A client supporting batched invocations and full type inference.

the name ucanto is a word play on UCAN and canto (one of the major divisions of a long poem)

Quick Start - Using a UCAN Service

Most developers will use ucanto to connect to existing UCAN services. Here's how to get started:

Installation

npm install @ucanto/client @ucanto/principal @ucanto/transport

Basic Usage

import*asClientfrom'@ucanto/client'import*asHTTPfrom'@ucanto/transport/http'import{CAR}from'@ucanto/transport'import{ed25519}from'@ucanto/principal'// Connect to a UCAN service (e.g., w3up, your company's API, etc.)constconnection=Client.connect({id: {did: ()=>'did:web:api.example.com'},// Service's public DIDcodec: CAR.outbound,channel: HTTP.open({url: newURL('https://api.example.com')}),})// Generate or load your client keysconstagent=awaited25519.generate()// Invoke a capability on the serviceconstinvocation=Client.invoke({issuer: agent,audience: connection.id,capability: {can: 'store/add',with: agent.did(),nb: {link: 'bafybeigwflfnv7tjgpuy52ep45cbbgkkb2makd3bwhbj3ueabvt3eq43ca'}}})// Execute the invocationconstresult=awaitinvocation.execute(connection)if(result.error){console.error('Operation failed:',result.error)}else{console.log('Success:',result.out)}

πŸ“ Tested in: packages/client/test/client.spec.js:22 - Client invocation and execution

Working with Delegations

UCAN services often require delegated permissions. Here's how to use them:

// Example 1: Using a DID (identity-based resource)constdelegation=awaitClient.delegate({issuer: serviceAgent,// Who granted the permissionaudience: agent,// You (the recipient) capabilities: [{can: 'store/add',with: 'did:key:zAlice'// Resource: Alice's storage (must match serviceAgent.did())}]})// Example 2: Using a resource URI (file-based resource)constfileDelegation=awaitClient.delegate({issuer: alice,// Alice owns the fileaudience: bob,// Bob gets accesscapabilities: [{can: 'file/write',with: 'file:///home/alice/documents/important.txt'// Specific file resource}]})// Use the delegation as proof in your invocationconstinvocation=Client.invoke({issuer: agent,audience: connection.id,capability: {can: 'store/add',with: 'did:key:zAlice',// Must match the delegated resourcenb: {link: 'bafybeig...'}},proofs: [delegation]// Proof you have permission})constresult=awaitinvocation.execute(connection)

πŸ“ Tested in:

Batch Operations

You can send multiple invocations in a single request:

constuploadFile=Client.invoke({issuer: agent,audience: connection.id,capability: {can: 'store/add',with: agent.did(),nb: {link: fileCID}}})constdeleteFile=Client.invoke({issuer: agent,audience: connection.id,capability: {can: 'store/remove',with: agent.did(),nb: {link: oldFileCID}}})// Execute both operations togetherconst[uploadResult,deleteResult]=awaitconnection.execute([uploadFile,deleteFile])

πŸ“ Tested in: packages/client/test/client.spec.js:102 - Batch invocation execution

Advanced Delegation Patterns

UCAN supports complex delegation scenarios where users can grant permissions to others:

// Alice delegates capability to Bob for a specific namespaceconstproof=awaitClient.delegate({issuer: alice,audience: bob,capabilities: [{can: 'file/link',with: `file:///tmp/${alice.did()}/friends/${bob.did()}/`,},],})// Bob can now use the delegated permissionconstaboutBob=Client.invoke({issuer: bob,audience: serviceKey,capability: {can: 'file/link',with: `file:///tmp/${alice.did()}/friends/${bob.did()}/about`,nb: {link: testCID},},proofs: [proof],// Include the delegation proof})// Bob tries to access Mallory's namespace (should fail)constaboutMallory=Client.invoke({issuer: bob,audience: serviceKey,capability: {can: 'file/link',with: `file:///tmp/${alice.did()}/friends/${MALLORY_DID}/about`,nb: {link: malloryCID},},proofs: [proof],// Same proof, but wrong namespace})// Execute both operationsconst[bobResult,malloryResult]=awaitconnection.execute([aboutBob,aboutMallory,])// Bob's operation succeeds, Mallory's failsif(bobResult.error){console.error('Bob operation failed:',bobResult.error)}else{console.log('Bob operation succeeded:',bobResult.out)}if(malloryResult.error){console.log('Mallory operation failed (expected):',malloryResult.error)}else{console.log('Mallory operation succeeded (unexpected)')}

This demonstrates how UCAN's delegation system provides fine-grained access control where:

  • βœ… Bob succeeds - He has delegated permission for his namespace
  • ❌ Mallory fails - Bob doesn't have permission for Mallory's namespace
  • πŸ”’ Security - The service validates the delegation chain and resource ownership

πŸ“ Tested in: packages/server/test/readme-integration.spec.js:99 - Advanced delegation patterns with namespace validation

Service-Specific Examples

Different UCAN services will have different capabilities. Check their documentation for specifics:

  • w3up (Web3.Storage): w3up documentation
  • Custom Services: See your service's API documentation

Building Your Own Service

To create your own UCAN service, see the @ucanto/server documentation. This covers:

  • Defining capabilities
  • Creating service handlers
  • Setting up transport layers
  • Deployment and security

Advanced Topics

Custom Transport

import*asTransportfrom'@ucanto/transport'constconnection=Client.connect({id: service,codec: Transport.outbound({encoders: {'application/car': CAR.request},decoders: {'application/dag-cbor': CBOR.response}}),channel: yourCustomChannel})

Key Management

import{ed25519}from'@ucanto/principal'// Generate new keysconstagent=awaited25519.generate()// Save keys (browser)localStorage.setItem('agent',agent.toString())// Load keys (browser) constsavedAgent=ed25519.parse(localStorage.getItem('agent'))// Save keys (Node.js)importfsfrom'fs/promises'awaitfs.writeFile('agent.key',agent.toString())// Load keys (Node.js)constkeyData=awaitfs.readFile('agent.key','utf-8')constloadedAgent=ed25519.parse(keyData)

πŸ“ Tested in: packages/server/test/readme-examples.spec.js:54 - Key generation, formatting, and parsing

Package Overview

About

🐧 UCAN RPC

Topics

Resources

Stars

62 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages