(u)canto is a library for UCAN based RPC that provides:
- A declarative system for defining [capabilities][] and [abilities][] (roughly equivalent to HTTP routes in REST).
- A system for binding capability handles (a.k.a providers) to form services with built-in routing.
- A UCAN validation system.
- A runtime for executing UCAN capability invocations.
- A pluggable transport layer.
- 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)
Most developers will use ucanto to connect to existing UCAN services. Here's how to get started:
npm install @ucanto/client @ucanto/principal @ucanto/transportimport*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
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:
packages/client/test/client.spec.js:70- Delegation creation and usagepackages/server/test/readme-integration.spec.js:160- Delegation with server validation
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
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
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
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
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})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
@ucanto/client- Connect to and invoke UCAN services@ucanto/server- Build your own UCAN services@ucanto/transport- Transport layer implementations@ucanto/principal- Cryptographic identity management@ucanto/core- Core UCAN primitives@ucanto/validator- UCAN validation logic