Skip to content

Commit 5d8c085

Browse files
authored
feat: ontology for accounts (#935)
* feat: ontology for accounts * fix: type casting * fix: ontology type wrong * docs: ecurrency document
1 parent d2efd8b commit 5d8c085

4 files changed

Lines changed: 734 additions & 1 deletion

File tree

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
---
2+
sidebar_position: 6
3+
---
4+
5+
# eCurrency: Accounts and Ledger MetaEnvelopes
6+
7+
This guide covers how eCurrency stores account and transaction data as MetaEnvelopes on user eVaults. If you are building a feature that reads balances, displays transaction history, or initiates transfers, this is the reference you need.
8+
9+
## Ontology IDs
10+
11+
| Type | Ontology ID | Description |
12+
|------|-------------|-------------|
13+
| Ledger | `550e8400-e29b-41d4-a716-446655440006` | Individual transaction entries (debits/credits) |
14+
| Currency | `550e8400-e29b-41d4-a716-446655440008` | Currency definitions |
15+
| Account | `6fda64db-fd14-4fa2-bd38-77d2e5e6136d` | Account snapshots (holder + currency + balance) |
16+
17+
## Data Model Overview
18+
19+
```mermaid
20+
graph TD
21+
subgraph eVault["User's eVault"]
22+
Account["Account MetaEnvelope<br/>ontology: 6fda64db..."]
23+
Ledger1["Ledger MetaEnvelope<br/>ontology: 550e8400...06<br/>(credit)"]
24+
Ledger2["Ledger MetaEnvelope<br/>ontology: 550e8400...06<br/>(debit)"]
25+
end
26+
27+
subgraph GroupVault["Group's eVault"]
28+
Currency["Currency MetaEnvelope<br/>ontology: 550e8400...08"]
29+
TreasuryAccount["Account MetaEnvelope<br/>(group treasury)"]
30+
TreasuryLedger["Ledger MetaEnvelopes<br/>(mints / burns)"]
31+
end
32+
33+
Account -- "currencyEname links to" --> Currency
34+
Ledger1 -- "currencyId links to" --> Currency
35+
Ledger2 -- "currencyId links to" --> Currency
36+
Account -- "accountId matches" --> Ledger1
37+
Account -- "accountId matches" --> Ledger2
38+
```
39+
40+
## Account MetaEnvelope
41+
42+
An account represents a user's (or group's) holdings in a specific currency. One account MetaEnvelope exists per holder-currency pair.
43+
44+
### Payload Fields
45+
46+
| Field | Type | Description |
47+
|-------|------|-------------|
48+
| `accountId` | `string` | The holder's ID. Matches `accountId` on ledger MetaEnvelopes |
49+
| `accountEname` | `string` | Global eName of the holder (prefixed with `@`) |
50+
| `accountType` | `"user"` or `"group"` | Whether the holder is a user or a group treasury |
51+
| `currencyEname` | `string` | Global eName of the currency (prefixed with `@`) |
52+
| `currencyName` | `string` | Display name of the currency |
53+
| `balance` | `number` | Current balance at time of creation |
54+
| `createdAt` | `string` (ISO 8601) | When the first transaction on this account occurred |
55+
56+
### Example
57+
58+
```json
59+
{
60+
"accountId": "f2a6743e-8d5b-43bc-a9f0-1c7a3b9e90d7",
61+
"accountEname": "@35a31f0d-dd76-5780-b383-29f219fcae99",
62+
"accountType": "user",
63+
"currencyEname": "@d8d3fbb7-70d1-46c6-b8ba-ae1ee701060c",
64+
"currencyName": "MetaCoin",
65+
"balance": 741,
66+
"createdAt": "2026-01-15T10:30:00.000Z"
67+
}
68+
```
69+
70+
### Where It Lives
71+
72+
Account MetaEnvelopes are stored on the **account holder's** eVault. A user who holds 3 different currencies will have 3 account MetaEnvelopes on their eVault.
73+
74+
## Ledger MetaEnvelope
75+
76+
Each ledger entry represents a single debit or credit. Transfers produce two ledger entries: one debit on the sender and one credit on the receiver.
77+
78+
### Payload Fields
79+
80+
| Field | Type | Description |
81+
|-------|------|-------------|
82+
| `currencyId` | `string` | ID of the currency (links to currency MetaEnvelope) |
83+
| `accountId` | `string` | The account this entry belongs to |
84+
| `accountType` | `"user"` or `"group"` | Type of account holder |
85+
| `amount` | `number` | Signed amount. Positive for credits, negative for debits |
86+
| `type` | `"credit"` or `"debit"` | Entry type |
87+
| `description` | `string` | Human-readable description of the transaction |
88+
| `senderAccountId` | `string` | Account ID of the sender (for transfers) |
89+
| `senderAccountType` | `"user"` or `"group"` | Sender's account type |
90+
| `receiverAccountId` | `string` | Account ID of the receiver (for transfers) |
91+
| `receiverAccountType` | `"user"` or `"group"` | Receiver's account type |
92+
| `balance` | `number` | Running balance after this entry |
93+
| `hash` | `string` | SHA-256 hash of this entry (integrity chain) |
94+
| `prevHash` | `string` | Hash of the previous entry in the chain |
95+
| `createdAt` | `string` (ISO 8601) | When the entry was created |
96+
97+
### Example: Transfer
98+
99+
When Alice sends 50 MetaCoin to Bob, two ledger MetaEnvelopes are created:
100+
101+
**Debit on Alice's eVault:**
102+
103+
```json
104+
{
105+
"currencyId": "d8d3fbb7-70d1-46c6-b8ba-ae1ee701060c",
106+
"accountId": "a1b2c3d4-...",
107+
"accountType": "user",
108+
"amount": -50,
109+
"type": "debit",
110+
"description": "Transfer to user:e5f6g7h8-...",
111+
"senderAccountId": "a1b2c3d4-...",
112+
"senderAccountType": "user",
113+
"receiverAccountId": "e5f6g7h8-...",
114+
"receiverAccountType": "user",
115+
"balance": 691,
116+
"hash": "a3f7...",
117+
"prevHash": "9c1d...",
118+
"createdAt": "2026-03-28T14:00:00.000Z"
119+
}
120+
```
121+
122+
**Credit on Bob's eVault:**
123+
124+
```json
125+
{
126+
"currencyId": "d8d3fbb7-70d1-46c6-b8ba-ae1ee701060c",
127+
"accountId": "e5f6g7h8-...",
128+
"accountType": "user",
129+
"amount": 50,
130+
"type": "credit",
131+
"description": "Transfer from user:a1b2c3d4-...",
132+
"senderAccountId": "a1b2c3d4-...",
133+
"senderAccountType": "user",
134+
"receiverAccountId": "e5f6g7h8-...",
135+
"receiverAccountType": "user",
136+
"balance": 150,
137+
"hash": "b4e8...",
138+
"prevHash": "d2f0...",
139+
"createdAt": "2026-03-28T14:00:00.000Z"
140+
}
141+
```
142+
143+
### Where It Lives
144+
145+
Ledger MetaEnvelopes are stored on the eVault of the account holder for that entry. In a transfer, the debit lives on the sender's eVault and the credit lives on the receiver's eVault.
146+
147+
## Currency MetaEnvelope
148+
149+
Currencies are defined per group and stored on the eVaults of group admins.
150+
151+
### Payload Fields
152+
153+
| Field | Type | Description |
154+
|-------|------|-------------|
155+
| `name` | `string` | Currency display name |
156+
| `description` | `string` | Currency description |
157+
| `ename` | `string` | Global eName of the currency |
158+
| `groupId` | `string` | ID of the group that owns this currency |
159+
| `allowNegative` | `boolean` | Whether accounts can go below zero |
160+
| `maxNegativeBalance` | `number` | Floor for negative balances (if allowed) |
161+
| `allowNegativeGroupOnly` | `boolean` | If true, only group members can overdraft |
162+
| `createdBy` | `string` | ID of the admin who created it |
163+
| `createdAt` | `string` (ISO 8601) | Creation timestamp |
164+
165+
## Querying an eVault
166+
167+
### Get All Accounts for a User
168+
169+
```graphql
170+
query GetAccounts {
171+
metaEnvelopes(
172+
filter: { ontologyId: "6fda64db-fd14-4fa2-bd38-77d2e5e6136d" }
173+
first: 100
174+
) {
175+
edges {
176+
node {
177+
id
178+
parsed
179+
}
180+
}
181+
}
182+
}
183+
```
184+
185+
This returns all account MetaEnvelopes on the user's eVault. Each one represents a currency the user holds.
186+
187+
### Get Transaction History for a User
188+
189+
```graphql
190+
query GetLedgerEntries {
191+
metaEnvelopes(
192+
filter: { ontologyId: "550e8400-e29b-41d4-a716-446655440006" }
193+
first: 50
194+
) {
195+
edges {
196+
node {
197+
id
198+
parsed
199+
}
200+
}
201+
pageInfo {
202+
hasNextPage
203+
endCursor
204+
}
205+
}
206+
}
207+
```
208+
209+
### Filter by Currency
210+
211+
To get ledger entries for a specific currency, fetch all ledger MetaEnvelopes and filter client-side by `parsed.currencyId`.
212+
213+
## Transaction Flow
214+
215+
```mermaid
216+
sequenceDiagram
217+
participant Sender as Sender's Platform
218+
participant API as eCurrency API
219+
participant SenderVault as Sender's eVault
220+
participant ReceiverVault as Receiver's eVault
221+
222+
Sender->>API: POST /transfer
223+
Note over API: Validate balance,<br/>check negative rules
224+
225+
API->>API: Create debit ledger entry<br/>(sender account, -amount)
226+
API->>API: Create credit ledger entry<br/>(receiver account, +amount)
227+
API->>API: Compute hash chain
228+
229+
API-->>SenderVault: Sync debit ledger MetaEnvelope
230+
API-->>ReceiverVault: Sync credit ledger MetaEnvelope
231+
232+
Note over SenderVault: Debit entry stored<br/>with updated balance
233+
Note over ReceiverVault: Credit entry stored<br/>with updated balance
234+
```
235+
236+
## Linking Accounts to Ledger Entries
237+
238+
The `accountId` field is the primary key that ties everything together:
239+
240+
```mermaid
241+
graph LR
242+
Account["Account MetaEnvelope<br/>accountId: abc-123<br/>balance: 741"]
243+
L1["Ledger Entry<br/>accountId: abc-123<br/>amount: +100"]
244+
L2["Ledger Entry<br/>accountId: abc-123<br/>amount: -50"]
245+
L3["Ledger Entry<br/>accountId: abc-123<br/>amount: +691"]
246+
247+
Account --- L1
248+
Account --- L2
249+
Account --- L3
250+
```
251+
252+
To reconstruct the full picture for a given user and currency:
253+
254+
1. Query the eVault for account MetaEnvelopes (ontology `6fda64db-fd14-4fa2-bd38-77d2e5e6136d`)
255+
2. Pick the account matching the desired `currencyEname`
256+
3. Use `accountId` from that account to filter ledger MetaEnvelopes (ontology `550e8400-e29b-41d4-a716-446655440006`) where `parsed.accountId` matches
257+
258+
## Mint and Burn
259+
260+
Minting and burning operate on the **group treasury account** (where `accountType = "group"`).
261+
262+
- **Mint**: A credit entry is added to the group's account, increasing total supply
263+
- **Burn**: A debit entry is added to the group's account, decreasing total supply
264+
265+
These entries have no `senderAccountId`/`receiverAccountId` since they are not transfers between two parties.
266+
267+
## Negative Balances
268+
269+
Currencies can be configured to allow negative balances:
270+
271+
| Setting | Behavior |
272+
|---------|----------|
273+
| `allowNegative = false` | Balance cannot go below 0 |
274+
| `allowNegative = true` | Balance can go negative |
275+
| `maxNegativeBalance = -500` | Balance cannot go below -500 |
276+
| `allowNegativeGroupOnly = true` | Only group members can overdraft; non-members are capped at 0 |
277+
278+
## Hash Chain Integrity
279+
280+
Ledger entries form a hash chain per currency. Each entry's `hash` is computed from:
281+
282+
- All fields of the entry (id, currencyId, accountId, amount, type, etc.)
283+
- The `prevHash` (hash of the previous entry in that currency's chain)
284+
285+
This means tampering with any historical entry breaks the chain for all subsequent entries.

platforms/ecurrency/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"typeorm": "typeorm-ts-node-commonjs",
1111
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/database/data-source.ts",
1212
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts",
13-
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/database/data-source.ts"
13+
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/database/data-source.ts",
14+
"backfill:accounts": "ts-node src/scripts/backfill-accounts.ts"
1415
},
1516
"dependencies": {
1617
"axios": "^1.6.7",

0 commit comments

Comments
 (0)