---
# System prepended metadata

title: Web5 JS Architecture

---

# Web5 JS Architecture

### Table of Contents
- [Current State](#Current-State)
- [Proposed Architecture](#Proposed-Architecture)
  - [Context](#Context)
  - [Proposal Diagrams](#Proposal-Diagrams)
  - [App Developer Experience](#App-Developer-Experience)
  - [SDK Developer Experience](#SDK-Developer-Experience)
- [Open Questions](#Open-Questions)

---

The current Web5 JS architecture looks something like the following:

![](https://hackmd.io/_uploads/SyuRQhE8h.png)

## Current State

### Connect

A developer getting started begins with:

```typescript
const { web5, did } = Web5.connect()
```

- The `did` string that is returned is the ION DID of the Web5 User Agent that was connected to.
- The `web5` instance that is returned provides `web5.did.*` and `web5.dwn.*` methods, which interface with the `DwnApi` and `DidApi`.
  - `DwnApi` takes a Web5 Agent and connected DID as input and enables interaction with the Protocols and Records of the Agent's embedded DWN.
  - `DidApi` takes a DID Resolver and map of DID Method APIs as input and enables creation, update, and resolution of DIDs.

### Web5 Agent

The Web5 Agent instance that is provided as input when creating a `Web5` instance is currently a Web5 User Agent which has several capabilities:

- `didResolver`
- `dwn`
- `dwnRpcClient`
- `profileManager`
- `syncManager`

#### `didResolver`

An instance of `DidResolver` from the `@tbd54566975/dids` package that provides a single method, `resolve()`, which currently resolves `did:key` and `did:ion` DIDs.  Resolved DIDs are cached for 15 minutes.  As implemented, the same `DidResolver` instance is used by both the Web5 User Agent and the `DidApi` when accessed via `web5.did.resolve()`.

#### `dwn`

An instance of `Dwn` from the `@tbd54566975/dwn` package that is "embedded" in the Web5 User Agent.  All `web5.dwn.*` method invocations interact with the Agent's embedded DWN using the connected DID as the tenant target.

#### `dwnRpcClient`

The `@tbd54566975/web5-agent` package defines a `DwnRpc` interface:
- `transportProtocols()`: Getter that returns an array of implemented protocol schemes (e.g., `http:`, `https:`) to implementations of `DwnRpc`
- `sendDwnRequest()`: Method that takes a `DwnRpcRequest` and returns a `DwnRpcResponse`.

The agent's `dwnRpcClient` is an instance of `DwnRpcClient` which implements the `DwnRpc` interface.  By default, it is instantiated with a single client, `HttpDwnRpcClient`, which can be used to communicate with `dwn-server` instances via HTTP or HTTPS.

#### `profileManager`
`ProfileManager` is an interface from the `@tbd54566975/web5-user-agent` package which defines three methods:
  - `createProfile()`
  - `getProfile()`
  - `listProfiles()`

`ProfileApi` implements the `ProfileManager` interface, and by default, uses a LevelDB backed store implemented as `ProfileStore`.

#### `syncManager`

`SyncManager` is an interface from the `@tbd54566975/web5-user-agent` package which defines three methods:
- `registerProfile()`
- `pull()`
- `push()`

`SyncApi` implements the `SyncManager` interface and uses a LevelDB backed store that cannot be overriden upon instantiation (or than specifying the store location/path).

[🔝 _Back To Top_ ](#Web5-JS-Architecture)

## Proposed Architecture

### Context

If we think about Web5 as a platform consisting three major pillars:
1. **identifiers**
1. **credentials**
1. **data stores**

Which the Web5 SDK is implementing using:
1. **Decentralized Identifiers** (DID)
1. **Verifiable Credentials** (VC)
1. **Decentralized Web Nodes** (DWN)

The following picture emerges:

![](https://hackmd.io/_uploads/BkhRXRZvh.png)


with **Networks** enabling the resolution of DIDs and transport of credential exchange and DWeb Messages over network boundaries.

[🔝 _Back To Top_ ](#Web5-JS-Architecture)

### Proposal Diagrams

Mapping the Web5 SDK components to the diagram:

![](https://hackmd.io/_uploads/B12FlGmD2.png)

SDK component organization:

![](https://hackmd.io/_uploads/SyimezQD3.png)

[🔝 _Back To Top_ ](#Web5-JS-Architecture)

### App Developer Experience

**In-Browser or Desktop User Agent**

```typescript
const { web5, did } = Web5.connect();
```

When no options are passed, `Web5.connect()` will try each of the following, terminating at a given step if an agent connection is successful:
1. Check to see if local AppData is present (i.e., in LocalStorage/IndexedDb), and if so, load the stored app data and attempt to reconnect to the most recently used agent (e.g., desktop agent via `web5://`, local in-browser agent, or a remote agent via a supported protocol, such as `https://` or `wss://`).
1. Initiate **`DID Connect`** / **`Web5 Connect`** to `web5://`. This begins by scanning local ports for a listening agent, and if found, triggering a custom URL handler. The rest of the **`Connect`** protocol is nearly identical for every supported transport medium (i.e., `web5://` vs. `https://` vs. `wss://`).  If successfully connected, a Web5 Proxy Agent is instantiated with the connected agent details.
1. If there is no listening agent OR the connection attempt was denied/blocked, then instantiate an in-browser User Agent using any `Web5ConnectOptions` passed in at runtime and a default agent configuration.

**Remote Agent**

```typescript
// Connect by dereferencing a DID and using a transport client based on
// the service endpoint URL.
//   Examples: https://dwn.yourdomain.com, wss://dwnhostingprovider.com
const { web5, did } = Web5.connect({
  url: 'did:ion:abcd1234'
});

// Connect to a specific http/https URL
const { web5, did } = Web5.connect({
  url: 'https://dwn.yourdomain.com'
});
```

When a DID or URL are provided as input, `Web5.connect()` will try each of the following, terminating at a given step if an agent connection is successful:
1. Check to see if local AppStorage is present (e.g., LocalStorage/IndexedDb), and if so, load the stored app data, look for the specified DID/URL, and if found, attempt to reconnect to the specified agent endpoint.
1. Initiate **`DID Connect`** / **`Web5 Connect`** using the specified transport protocol.  If successfully connected, a Web5 Proxy Agent is instantiated with the connected agent details.
1. If the connection attempt failed (e.g., no or bad response) OR was denied/blocked, then instantiate an in-browser User Agent using any `Web5ConnectOptions` passed in at runtime and a default agent configuration.

**Using a Secure `AppDataVault`**

To a passphrase protected and encrypted [`AppDataVault`](#Secure-AppDataVault), the developer must initialize and unlock the application data vault and then pass the instance to `Web5.connect()`.

```typescript
// Initialize secure AppDataVault.
const appData = new AppDataVault();

// Attempt to load saved app data.
const appDataKey = 'WEB5_APP_DATA';
if (appData.load(appDataKey)) {
  // Present UI to end user to enter passphrase.
  // In future, the passphrase could be protected by fingerprint/face unlock.
  const passphrase = // Get passphrase.
  if (!appData.unlock(passphrase)) {
    // retry and eventually fail after N attempts.
  }
} else {
  // If no app data was present, or loading failed, initialize defaults.
  appData.initialize(appDataKey);
  // Present UI to end user to enter passphrase.
  const passphrase = // Get passphrase.
  appData.changePassphrase(null, passphrase);
  // In future, present UI to optionally enable fingerprint/face unlock.
}

const { web5 } = Web5.connect({ appData });
```

[🔝 _Back To Top_ ](#Web5-JS-Architecture)

### SDK Developer Experience

#### `Web5.connect()`

If the `{ appData }` property of the `options` object is undefined, a default, insecure AppData implementation will be used.

```typescript
static async connect(options: Web5ConnectOptions = {}) {
  // Use specified AppData implementation instance or default.
  let appData;
  if (options.appData) {
    appData = options.appData;
  } else {
    appData = new AppData();
    // Attempt to load saved app data.
    const appDataKey = options.appDataKey ?? Web5.APP_DATA_KEY;
    // If no app data was present, or loading failed, initialize defaults.
    if (!appData.load(appDataKey)) {
      appData.initialize(appDataKey);      
    }
  }
  
  if (appData.getStatus() !== appData.STATUS_READY) {
    throw new Error(`failed to load application data. Status: ${appData.getStatus()}`'`);
  }
  
  let agent;
  
  // Attempt DID Connect to specified endpoint or default to local/fallback.
  agent = Web5ProxyAgent.create({
    did          : appData.getDid(),
    auk          : appData.getPrivateKey(),
    agentOptions : options.agentOptions,
  });
  agent.connect(options.url);
  
  if (!agent.isConnected()) {
    agent = Web5UserAgent.create({
      did          : appData.getDid(),
      auk          : appData.getPrivateKey(),
      agentOptions : options.agentOptions,
    });
  }
  
  const web5 = new Web5({ agent, appData });
  
  return { web5 }
```

[🔝 _Back To Top_ ](#Web5-JS-Architecture)

### AppData

Alternative names:
- `AppConfig` & `AppConfigVault`
- `Insecure` & `Secure`/`Vault` prefix/suffix to drive the point that not securing the appliation configuration/data with encrytion carries risks and should only be done for learning / POC purposes.

#### Type Definitions

```typescript
interface AppData {
  /**
   * Attempts to retrieve the application data associated with the
   * provided key from storage. It returns a Promise that resolves
   * to a boolean indicating whether the operation was successful.
   */
  load(key: string): Promise<boolean>;
  
  /**
   * Initializes the AppData object by creating a new `did:key` DID,
   * setting default configuration options, and saving the data to application
   * storage under the given key. It returns a Promise that resolves
   * to a boolean indicating whether the operation was successful.
   */
  initialize(key: string): Promise<boolean>;
  
  /**
   * Attempts to saves the provided configuration under the given key
   * to application storage. It returns a Promise that resolves to a
   * boolean indicating whether the operation was successful.
   */
  save(key: string, config: AppData): Promise<boolean>;
  
  /**
   * Returns a promise that resolves to a string, which is the App DID.
   */
  getDid(): Promise<string>
  
  /**
   * Returns a promise that resolves to a CryptoKey object, which
   * represents the public key associated with the App DID.
   */
  getPublicKey(): Promise<CryptoKey>
      
  /**
   * Returns a promise that resolves to a CryptoKey object, which
   * represents the private key associated with the App DID.
   */
  getPrivateKey(): Promise<CryptoKey>
  
  /**
   * Returns a promise that resolves to a AppDataStatus object, which
   * provides information about the current status of the AppData instance.
   */
  getStatus(): Promise<AppDataStatus>
  
  /**
   * Creates a backup of the current state of `AppData` and
   * returns a Promise that resolves to an `AppDataBackup` object.
   */
  backup(): Promise<AppDataBackup>;
  
  /**
   * Restores `AppData` to the state in the provided `AppDataBackup`
   * object.  It returns a Promise that resolves to a boolean indicating
   * whether the restore was successful.
   */
  restore(backup: AppDataBackup): Promise<boolean>;
}

interface AppDataVault extends AppData {
  /**
   * Creates an encrypted backup of the current state of `AppData` and
   * returns a Promise that resolves to an `AppDataBackup` object.
   */
  backup(passphrase: string): Promise<AppDataBackup>;
  
  /**
   * Restores `AppData` to the state in the provided `AppDataBackup` object.
   * It requires a passphrase to decrypt the backup and returns a Promise that
   * resolves to a boolean indicating whether the restore was successful.
   */
  restore(backup: AppDataBackup, passphrase: string): Promise<boolean>;
  
  /**
   * Locks the `AppDataVault`, secured by a passphrase
   * that must be entered to unlock.
   */
  lock(): Promise<void>;
    
  /**
   * Attempts to unlock the `AppDataVault` with the provided
   * passphrase.  It returns a Promise that resolves to a
   * boolean indicating whether the unlock was successful.
   */
  unlock(passphrase: string): Promise<boolean>;
  
  /**
   * Attempts to change the passphrase of the `AppDataVault`.
   * It requires the old passphrase for verification and returns
   * a Promise that resolves to a boolean indicating whether the
   * passphrase change was successful.
   */
  changePassphrase(oldPassphrase: string, newPassphrase: string): Promise<boolean>;
}
```

#### Secure AppDataVault

**Key Derivation Overview**

![](https://hackmd.io/_uploads/HyG46uzv2.png)

1. First an Ed25519 key pair is generated that is unique[^1] to the runtime environment of the Web5 application.

1. A `did:key` DID is created from the key pair generated in Step 1.  This DID will be subsequently referred to as the _**App DID**_.

1.  A _non-secret_ static `info` value is combined with the _non-secret_ App DID `public key` as input to a Hash-based Key Derivation Function (HKDF) to create a new 32-byte `salt`.

1. The `salt` value derived in the prior step and a `passphrase` entered by the end user are used as input to the PBKDF2 algorithm to generate a secret key that will be referred to as your `Account Unlock Key` (AUK).

1. The private key associated with the App DID is then encrypted using the derived AUK using AES-GCM and written to the AppDataVault store.

**App Config Data Encryption and Decryption**

To encrypt data:
- The AUK, a non-secret randomly generated 12-byte initialization vector (IV), and the plaintext data are input to a AES-GCM encrypt() function to produce encrypted ciphertext.

To decrypt data:
- The AUK, the stored IV, and the encrypted ciphertext are input to a AES-GCM decrypt() function to recover the original plaintext.

[^1]: Accessing a DWA (e.g., https://dignal.xyz) in Chrome and Firefox on the same computer would result in each browser generating and using a different key pair.

[🔝 _Back To Top_ ](#Web5-JS-Architecture)

## Migration / Recovery

### Association Between Account and App DIDs

Each time a Web5 App is accessed on a device, a new App DID is generated.  For decentralized web applications (DWA), a new App DID is generated each time the DWA is accessed from a different web browser. To detail how this would work in practice, here is an example:

![](https://hackmd.io/_uploads/rJ8Hm-UP2.png)

# Open Questions

### `Web5.connect()`

For the `Web5.connect()` sequence, how is it the same or different on mobile in the following scenarios?
- Web5 app run from a mobile browser
- Native iOS / Android mobile app

[🔝 _Back To Top_ ](#Web5-JS-Architecture)

## Web5 VC

### `web5.vc.*` API:

Objects:
- Verifiable Credentials (create, sign, verify, etc.)
- Credential Manifest (create)
- Credential Schemas (create
- Presentation Exchange (create, )
- Presentation Definition




Previous API scaffolding:
- `web5.vc.request()`
  - args:
     - presentation exchange object
- `web5.vc.apply()`
  - args:
    - DID-relative URL || Cred Manifest Object
- `web5.vc.issue()`
  - args:
    - credential object ("@context", status, subject, expirationDate, id, issuanceDate, issuer, type, etc.)
- `web5.vc.verify()`
  - args:
    - signed credential object

### SDK Internals

**Create a Verifiable Credential**

Switch from this:
```typescript
const vcBuilder = new VerifiableCredentialBuilder(credentialSubject, issuer);
vcBuilder.setId('customID');
vcBuilder.addContext(['newContext']);
vcBuilder.addType('newType');
vcBuilder.setIssuer({ id: 'customIssuer' });
vcBuilder.setIssuanceDate('2023-12-31T23:59:59Z');
vcBuilder.setExpirationDate('2024-12-31T23:59:59Z');
const vc = vcBuilder.build();
```
to this:
```typescript
const vcApi = new VerifiableCredentialApi();
const v1 = {
  id: 'customID',
  context: ['newContext'],
  type: 'newType',
  issuer: { id: 'customIssuer' },
  issuanceDate: '2023-12-31T23:59:59Z',
  expirationDate: 'blahblahblah'
}

vcApi.createCredential(v1)
vcApi.createCredential({ ...v1, expirationDate: 'alsdkfjl')
```

#### SDK Internals

In `packages/src/credentials`:
```typescript
export interface CredentialCreateOptions {
  issuer: ...;
  credentialSubject: ...;
  type?: ...;
  '@context': ...;
  issuanceDate: ...;
  expirationDate?: ...;
  credentialStatus?: ...;
  id?: ...;
}

export class VerifiableCredential {
  async create(options: CredentialCreateOptions = {}): Promise<VerifiableCredential> {
    ...
    return credential;
  }
}
```

In `packages/src/web5`:

```typescript
export class VcApi {
  #agent: Web5Agent;
  #connectedDid: string;

  constructor(agent: Web5Agent, connectedDid: string) {
    this.#agent = agent;
    this.#connectedDid = connectedDid;
  }

  async createCredential<T extends keyof CreateMethodOptions>(type: T, options?: CreateOptions<T>): Promise<VerifiableCredential> {
    const vcCreator = this.credentialCreators.get(type);
    if (!vcCreator) {
      throw new Error(`no creator available for ${type}`);
    }

    return vcCreator.create(options);
  }
  
  async createPresentation
}
```


[🔝 _Back To Top_ ](#Web5-JS-Architecture)