# 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:

## 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:

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:

SDK component organization:

[🔝 _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**

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:

# 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)