organic-protocol 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PROTOCOL.md CHANGED
@@ -1,51 +1,55 @@
1
- # Organic Money — Protocole inter-instances (v1)
1
+ # Organic Money — Inter-instance Protocol (v1)
2
2
 
3
- Ce document est le **standard** que toute implémentation compatible (application ou serveur) doit respecter. Le paquet npm [`organic-protocol`](https://www.npmjs.com/package/organic-protocol) en est la traduction TypeScript exacte ; les implémentations JS/TS devraient l'importer plutôt que réimplémenter. La cryptographie (signatures, chaînes, chiffrement) est définie par [`organic-money`](https://www.npmjs.com/package/organic-money) et n'est pas répétée ici.
3
+ This document is the **standard** every compatible implementation (app or server) must follow. The [`organic-protocol`](https://www.npmjs.com/package/organic-protocol) npm package is its exact TypeScript translation; JS/TS implementations should import it rather than reimplement. Cryptography (signatures, chains, encryption) is defined by [`organic-money`](https://www.npmjs.com/package/organic-money) and is not repeated here.
4
4
 
5
- Version du protocole : **1**. Version du format QR : **1** (indépendante, voir §4).
5
+ Protocol version: **1**. QR format version: **1** (independent, see §4).
6
6
 
7
7
  ---
8
8
 
9
- ## 1. Identité fédérée
9
+ ## 1. Federated identity
10
10
 
11
- Une clé publique seule ne suffit pas à joindre quelqu'un dans un réseau décentralisé. Une **identité** est toujours la paire :
11
+ A public key alone is not enough to reach someone in a decentralized network. An **identity** is always the pair:
12
12
 
13
13
  ```json
14
- { "pk": "<clé publique SECP256K1 compressée, hex>", "url": "<URL racine du serveur référent>" }
14
+ { "pk": "<compressed SECP256K1 public key, hex>", "url": "<root URL of the referent server>" }
15
15
  ```
16
16
 
17
- L'`url` est l'URL **racine** du serveur (sans `/api`, sans slash final), normalisée : schéma `https` par défaut (`http` toléré s'il est explicite réseaux locaux, développement), hôte en minuscules. Voir `normalizeServerUrl`.
17
+ The `url` is the server's **root** URL (no `/api`, no trailing slash), normalized: `https` scheme by default (`http` tolerated when explicitlocal networks, development), lowercase host. See `normalizeServerUrl`.
18
18
 
19
- ## 2. Formats de fil (wire)
19
+ ## 2. Wire formats
20
20
 
21
21
  ### 2.1 Transaction (`TxWire`)
22
22
 
23
23
  ```json
24
- { "v": 1, "d": 20260719, "t": 3, "p": "<pk cible>", "s": "<pk signataire>",
25
- "m": [20260719001], "i": [], "h": "<signature DER hex>" }
24
+ { "v": 1, "d": 20260719, "t": 3, "p": "<target pk>", "s": "<signer pk>",
25
+ "m": "BLeiCZk=", "i": "", "h": "<DER signature, hex>" }
26
26
  ```
27
27
 
28
- | Champ | Nom long | Contenu |
28
+ | Field | Long name | Content |
29
29
  |---|---|---|
30
- | `v` | version | version du protocole (1) |
31
- | `d` | date | entier `YYYYMMDD` |
32
- | `t` | type | type de transaction (§3) |
33
- | `p` | target | clé publique du destinataire |
34
- | `s` | signer | clé publique de l'émetteur |
35
- | `m` | money | ids d'unités de monnaie (`YYYYMMDDXXX`) |
36
- | `i` | invests | ids d'unités d'invest (`YYYYMMDD9XXX`) |
37
- | `h` | signature | signature SECP256K1 DER, hex |
30
+ | `v` | version | protocol version (1) |
31
+ | `d` | date | `YYYYMMDD` integer |
32
+ | `t` | type | transaction type (§3) |
33
+ | `p` | target | recipient's public key |
34
+ | `s` | signer | sender's public key |
35
+ | `m` | money | packed money unit ids (§2.3) |
36
+ | `i` | invests | packed invest unit ids (§2.3) |
37
+ | `h` | signature | SECP256K1 DER signature, hex |
38
38
 
39
- ### 2.2 Bloc (`BlockWire`)
39
+ ### 2.2 Block (`BlockWire`)
40
40
 
41
41
  ```json
42
- { "v": 1, "d": 20260719, "p": "<signature du bloc précédent>", "s": "<pk>",
43
- "m": [], "i": [], "t": 42, "r": "<racine de Merkle>", "h": "<signature>", "x": [ …TxWire ] }
42
+ { "v": 1, "d": 20260719, "p": "<previous block signature>", "s": "<pk>",
43
+ "m": "", "i": "", "t": 42, "r": "<merkle root>", "h": "<signature>", "x": [ …TxWire ] }
44
44
  ```
45
45
 
46
- `t` est ici le **total** (expérience économique cumulée) ; `p` est la signature du bloc précédent (chaînage) ; `x` la liste des transactions.
46
+ Here `t` is the **total** (cumulative economic experience); `p` is the previous block's signature (chain link); `x` is the transaction list. `m`/`i` are the available (unspent) money/invest ids at seal time, packed the same way as in a transaction (§2.3).
47
47
 
48
- ## 3. Types de transaction
48
+ ### 2.3 Packed unit ids (`m`, `i`)
49
+
50
+ Money and invest unit ids (`YYYYMMDDXXX` / `YYYYMMDD9XXX`, §2.1 table) are **not** a JSON array of numbers on the wire — a plain decimal array costs 12 bytes per id (11-12 digits plus a separator) for ~5 bytes of actual information, which matters once a paper bill (§4, `PP`) bundles many ids into one QR code. Instead: each id is packed into 5 big-endian bytes (comfortably covers every possible id), all ids are concatenated, and the result is base64-encoded into a single string. An empty array is the empty string `""`. Order and duplicates are preserved. Reference implementation: `packUnitIds`/`unpackUnitIds` in [`organic-money`](https://www.npmjs.com/package/organic-money) (`src/crypto.js`); `organic-protocol` validates the shape (`isTxWire`/`isBlockWire`) but does not implement the packing itself.
51
+
52
+ ## 3. Transaction types
49
53
 
50
54
  | # | Type | # | Type |
51
55
  |---|---|---|---|
@@ -59,73 +63,73 @@ L'`url` est l'URL **racine** du serveur (sans `/api`, sans slash final), normali
59
63
 
60
64
  ## 4. QR codes
61
65
 
62
- Format : **`OM<version>:<TYPE>:<payload JSON>`** — le préfixe est hors JSON pour permettre le dispatch (et le rejet d'une version inconnue) sans parser. Version courante : `1`.
66
+ Format: **`OM<version>:<TYPE>:<JSON payload>`** — the prefix lives outside the JSON so any reader can dispatch (or reject an unknown version) without parsing. Current version: `1`.
63
67
 
64
- **Règle de compatibilité** : un lecteur qui rencontre une version qu'il ne connaît pas DOIT refuser proprement (« mettez à jour l'application ») et NE DOIT JAMAIS tenter d'interpréter au mieux.
68
+ **Compatibility rule**: a reader encountering a version it does not speak MUST refuse cleanly ("please update the app") and MUST NEVER attempt a best-effort interpretation.
65
69
 
66
- | Type | Forme | Usage |
70
+ | Type | Shape | Usage |
67
71
  |---|---|---|
68
- | `CT` | `OM1:CT:{"pk","url","n","e"?}` | Carte de contact. `n` = nom affiché ; `e` = true si écosystème. |
69
- | `TX` | `OM1:TX:{"tx":<TxWire PAY>,"url"}` | Paiement hors-ligne, caméra contre écran. `url` = serveur du PAYEUR, pour la vérification différée (§5, `tx/verify`). |
70
- | `BR` | `OM1:BR:{"pk","url","n"}` | Demande de validation d'un nouveau citoyen. Les blocs sont récupérés via `GET {url}/api/v1/validations/{pk}` (Phase 2) — le QR est un pointeur, pas un conteneur. |
71
- | `PP` | `OM1:PP:{"tx":<TxWire PAPER>}` | Billet papier imprimé. |
72
+ | `CT` | `OM1:CT:{"pk","url","n","e"?}` | Contact card. `n` = display name; `e` = true for an ecosystem. |
73
+ | `TX` | `OM1:TX:{"tx":<PAY TxWire>,"url"}` | Offline payment, camera-to-screen. `url` = the PAYER's server, for deferred verification (§5, `tx/verify`). |
74
+ | `BR` | `OM1:BR:{"pk","url","n"}` | Validation request of a new citizen. Blocks are fetched via `GET {url}/api/v1/validations/{pk}` (Phase 2) — the QR is a pointer, not a container. |
75
+ | `PP` | `OM1:PP:{"tx":<PAPER TxWire>}` | Printed paper bill. |
72
76
 
73
- Contraintes : le `tx` d'un `TX` est de type PAY ; celui d'un `PP` est de type PAPER.
77
+ Constraints: the `tx` of a `TX` is of type PAY; the one of a `PP` is of type PAPER.
74
78
 
75
- ## 5. API REST `/api/v1`
79
+ ## 5. REST API `/api/v1`
76
80
 
77
- Toutes les routes vivent sous `{url}/api/v1`. Corps en JSON. Les erreurs portent `{ "error": "<message>", "code": "<ApiErrorCode>"? }`.
81
+ All routes live under `{url}/api/v1`. JSON bodies. Errors carry `{ "error": "<message>", "code": "<ApiErrorCode>"? }`.
78
82
 
79
- ### 5.1 Authentifications
83
+ ### 5.1 Authentication schemes
80
84
 
81
- **Block-auth** (`PUT /users/save`, `PUT /users/sign`) — en-tête `x-signature` = signature du hash du bloc soumis, par la clé du compte :
85
+ **Block-auth** (`PUT /users/save`, `PUT /users/sign`) — `x-signature` header = signature of the submitted block's hash, by the account key:
82
86
  `x-signature = signHash(block.hash(), sk)`.
83
87
 
84
- **Timestamp-auth** (`GET /tx/list`, `POST /users/password`, …) — en-tête `x-signature` = `signHash(sha256(publickey + ":" + timestamp), sk)` avec `publickey` et `timestamp` (secondes Unix) transmis en paramètres/corps. Tolérance : ±5 minutes, sinon 401.
88
+ **Timestamp-auth** (`GET /tx/list`, `POST /users/password`, …) — `x-signature` header = `signHash(sha256(publickey + ":" + timestamp), sk)` with `publickey` and `timestamp` (Unix seconds) passed as params/body. Tolerance: ±5 minutes, otherwise 401.
85
89
 
86
90
  ### 5.2 Endpoints (Phase 1)
87
91
 
88
- | Méthode & route | Auth | CorpsRéponse | Notes |
92
+ | Method & route | Auth | BodyResponse | Notes |
89
93
  |---|---|---|---|
90
- | `GET /info` | — | → `InfoResponse` | carte d'identité publique du serveur |
91
- | `GET /servers` | — | → `ServerListEntry[]` | annuaire des serveurs connus |
92
- | `POST /users/register` | — | `RegisterBody` → `RegisterResponse` | la `secretkey` arrive CHIFFRÉE (opaque pour le serveur) ; le mot de passe est bcrypté à l'arrivée |
93
- | `POST /users/login` | — | `LoginBody` → `LoginResponse` | **rotation du devicetoken** : l'appareil précédent est révoqué |
94
- | `PUT /users/save` | block | `SaveBlockBody` → 200 | `409 DEVICE_REVOKED` si le devicetoken n'est plus l'actif |
95
- | `PUT /users/sign` | block | `SignBlockBody` → 200 | le serveur signe le dernier bloc (billets, genèse) |
96
- | `POST /users/password` | timestamp | `PasswordChangeBody` → 200 | met à jour bcrypt + sk re-chiffrée, sans rien lire |
97
- | `POST /tx/send` | — | `TxSendBody` → 200 | **vérification croisée OBLIGATOIRE** (§5.3) |
98
- | `GET /tx/list` | timestamp | → `TxWire[]` | paiements en attente pour la clé |
99
- | `POST /tx/verify` | — | `TxVerifyBody` → `TxVerifyResponse` | lecture seule ; statuts : `confirmed` / `pending` / `invalid` / `unknown-sender` |
100
- | `POST /papers/cash` | — | `PapersCashBody` → 200 | exige le PAPER complet, vérifie sa crypto ; `409 ALREADY_CASHED` |
101
- | `GET /papers/isCashed?hash=` | — | → `IsCashedResponse` \| 404 | 404 = jamais encaissé |
94
+ | `GET /info` | — | → `InfoResponse` | public identity card of the server |
95
+ | `GET /servers` | — | → `ServerListEntry[]` | directory of known servers |
96
+ | `POST /users/register` | — | `RegisterBody` → `RegisterResponse` | the `secretkey` arrives ENCRYPTED (opaque to the server); the password is bcrypted on arrival |
97
+ | `POST /users/login` | — | `LoginBody` → `LoginResponse` | **devicetoken rotation**: the previous device is revoked |
98
+ | `PUT /users/save` | block | `SaveBlockBody` → 200 | `409 DEVICE_REVOKED` when the devicetoken is no longer the active one |
99
+ | `PUT /users/sign` | block | `SignBlockBody` → 200 | the server signs the last block (bills, genesis) |
100
+ | `POST /users/password` | timestamp | `PasswordChangeBody` → 200 | updates bcrypt + re-encrypted sk, without reading anything |
101
+ | `POST /tx/send` | — | `TxSendBody` → 200 | **MANDATORY cross-verification** (§5.3) |
102
+ | `GET /tx/list` | timestamp | → `TxWire[]` | pending payments for the key |
103
+ | `POST /tx/verify` | — | `TxVerifyBody` → `TxVerifyResponse` | read-only; statuses: `confirmed` / `pending` / `invalid` / `unknown-sender` |
104
+ | `POST /papers/cash` | — | `PapersCashBody` → 200 | requires the full PAPER, verifies its crypto; `409 ALREADY_CASHED` |
105
+ | `GET /papers/isCashed?hash=` | — | → `IsCashedResponse` \| 404 | 404 = never cashed |
102
106
 
103
- ### 5.3 Vérification croisée (obligation du serveur)
107
+ ### 5.3 Cross-verification (server duty)
104
108
 
105
- Avant d'accepter sur `tx/send` (et, en Phase 2, toute entrée d'écosystème), le serveur DOIT :
109
+ Before accepting on `tx/send` (and, in Phase 2, any ecosystem input), the server MUST:
106
110
 
107
- 1. vérifier la validité cryptographique de la transaction ;
108
- 2. charger la chaîne **sauvegardée** de l'émetteur (`tx.s`) et la valider (`assertIsValid`) ;
109
- 3. vérifier que la transaction figure dans l'historique de cette chaîne (par sa signature `h`).
111
+ 1. verify the cryptographic validity of the transaction;
112
+ 2. load the SAVED chain of the sender (`tx.s`) and validate it (`assertIsValid`);
113
+ 3. verify the transaction exists in that chain's history (by its signature `h`).
110
114
 
111
- Sans l'étape 3, un attaquant peut signer une transaction portant des unités qu'il ne possède pas. Conséquence côté client : l'ordre **`pay → save → send` est strict**.
115
+ Without step 3, an attacker can sign a transaction carrying units they do not own. Client-side consequence: the **`pay → save → send` order is strict**.
112
116
 
113
- ### 5.4 Un compte = un appareil actif
117
+ ### 5.4 One account = one active device
114
118
 
115
- Le serveur émet un `devicetoken` opaque (UUID) au `register` et à chaque `login` ; seul le dernier émis est valide. Un `save` portant un jeton périmé reçoit `409 DEVICE_REVOKED` — l'application doit alors passer en lecture seule et proposer la restauration. Ce mécanisme n'est pas cryptographique (l'identité reste prouvée par `x-signature`) ; il empêche le fork accidentel d'une chaîne entre deux appareils.
119
+ The server issues an opaque `devicetoken` (UUID) at `register` and at every `login`; only the last one issued is valid. A `save` carrying a stale token receives `409 DEVICE_REVOKED` — the app must then switch to read-only and offer the restore flow. This mechanism is not cryptographic (identity is still proven by `x-signature`); it prevents the accidental fork of a chain across two devices.
116
120
 
117
- ### 5.5 Codes d'erreur
121
+ ### 5.5 Error codes
118
122
 
119
123
  `INVALID_TX` · `INVALID_CHAIN` · `TX_NOT_IN_CHAIN` · `UNKNOWN_SENDER` · `UNKNOWN_USER` · `INVALID_SIGNATURE` · `DEVICE_REVOKED` · `ALREADY_CASHED`
120
124
 
121
- ## 6. Versionnement
125
+ ## 6. Versioning
122
126
 
123
- - **Protocole** (`PROTOCOL_VERSION`, champ `v` des tx/blocs) : n'augmente que sur rupture du format de fil.
124
- - **QR** (`QR_VERSION`, préfixe `OM<v>`) : n'augmente que sur rupture des payloads QR.
125
- - **API** (`/api/v1`) : une rupture de contrat REST crée `/api/v2` ; `GET /info` annonce les versions parlées.
127
+ - **Protocol** (`PROTOCOL_VERSION`, `v` field of txs/blocks): bumped only on a wire-format break.
128
+ - **QR** (`QR_VERSION`, `OM<v>` prefix): bumped only on a QR payload break.
129
+ - **API** (`/api/v1`): a REST contract break creates `/api/v2`; `GET /info` announces the spoken versions.
126
130
 
127
- Les trois évoluent indépendamment. Toute implémentation DOIT annoncer ses versions via `GET /info` et refuser proprement ce qu'elle ne parle pas.
131
+ The three evolve independently. Every implementation MUST announce its versions via `GET /info` and refuse cleanly what it does not speak.
128
132
 
129
133
  ---
130
134
 
131
- *Licence MIT — © suipotryot. Les phases 2 (écosystèmes, validations) et 3 (fédération) étendront ce document sans casser la v1.*
135
+ *MIT license — © suipotryot. Phases 2 (ecosystems, validations) and 3 (federation) will extend this document without breaking v1.*
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  This package carries the **shapes** that travel between Organic Economy instances: transaction and block wire formats, the versioned QR standard (`OM1:CT/TX/BR/PP`) with its encode/decode functions, and the DTOs of the `/api/v1` REST contract. It contains **no cryptography** — that lives in [`organic-money`](https://www.npmjs.com/package/organic-money).
6
6
 
7
- The [organic-webapp](https://github.com/GuziEconomy/organic-webapp) client and the [organic-webserver](https://github.com/GuziEconomy/organic-webserver) server both import this package, so any drift between what one sends and the other expects becomes a compile error instead of a silent bug. Third-party implementations should either import it or implement [PROTOCOL.md](PROTOCOL.md), which is the same standard in prose.
7
+ The [organic-webapp](https://github.com/OrganicEconomy/organic-webapp) client and the [organic-webserver](https://github.com/OrganicEconomy/organic-webserver) server both import this package, so any drift between what one sends and the other expects becomes a compile error instead of a silent bug. Third-party implementations should either import it or implement [PROTOCOL.md](PROTOCOL.md), which is the same standard in prose.
8
8
 
9
9
  ```bash
10
10
  npm install organic-protocol
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { PROTOCOL_VERSION, TxType, isTxWire, isBlockWire, type IntDate, type UnitId, type PublicKeyHex, type SignatureHex, type TxWire, type BlockWire, } from './wire.js';
1
+ export { PROTOCOL_VERSION, TxType, isTxWire, isBlockWire, type IntDate, type UnitId, type PackedUnitIds, type PublicKeyHex, type SignatureHex, type TxWire, type BlockWire, } from './wire.js';
2
2
  export { InvalidServerUrlError, normalizeServerUrl, type Identity, type Contact, type ContactType, } from './identity.js';
3
3
  export { QR_VERSION, QrError, UnsupportedQrVersionError, InvalidQrError, encodeContactQr, encodeOfflineTxQr, encodeValidationQr, encodePaperQr, decodeQr, type QrType, type DecodedQr, type ContactQrPayload, type OfflineTxQrPayload, type ValidationQrPayload, type PaperQrPayload, } from './qr.js';
4
4
  export type { ApiError, ApiErrorCode, InfoResponse, ServerListEntry, ServersResponse, RegisterBody, RegisterResponse, LoginBody, LoginResponse, SaveBlockBody, SignBlockBody, PasswordChangeBody, TxSendBody, TxListResponse, TxVerifyBody, TxVerifyStatus, TxVerifyResponse, PapersCashBody, IsCashedResponse, } from './api.js';
package/dist/wire.d.ts CHANGED
@@ -29,6 +29,16 @@ export type IntDate = number;
29
29
  * The creation date is embedded in the id.
30
30
  */
31
31
  export type UnitId = number;
32
+ /**
33
+ * Wire representation of a `UnitId[]`: each id packed into 5 big-endian
34
+ * bytes (comfortably covers every possible money/invest id), concatenated,
35
+ * then base64-encoded. An empty array is the empty string. A plain JSON
36
+ * number array costs 12 bytes per id (11-12 decimal digits + a separator)
37
+ * for ~5 bytes of actual information — this roughly halves the size of a
38
+ * transaction or block on the wire, which matters most for QR-encoded
39
+ * paper bills. Mirrors `packUnitIds`/`unpackUnitIds` in organic-money.
40
+ */
41
+ export type PackedUnitIds = string;
32
42
  /** Compressed SECP256K1 public key, hex-encoded (33 bytes → 66 chars). */
33
43
  export type PublicKeyHex = string;
34
44
  /** DER-encoded SECP256K1 signature, hex-encoded. */
@@ -44,8 +54,8 @@ export interface TxWire {
44
54
  t: TxType;
45
55
  p: PublicKeyHex;
46
56
  s: PublicKeyHex;
47
- m: UnitId[];
48
- i: UnitId[];
57
+ m: PackedUnitIds;
58
+ i: PackedUnitIds;
49
59
  h: SignatureHex;
50
60
  }
51
61
  /**
@@ -60,8 +70,8 @@ export interface BlockWire {
60
70
  d: IntDate;
61
71
  p: string;
62
72
  s: PublicKeyHex;
63
- m: UnitId[];
64
- i: UnitId[];
73
+ m: PackedUnitIds;
74
+ i: PackedUnitIds;
65
75
  t: number;
66
76
  r: string;
67
77
  h: SignatureHex;
package/dist/wire.js CHANGED
@@ -23,7 +23,7 @@ export var TxType;
23
23
  TxType[TxType["EARN"] = 13] = "EARN";
24
24
  })(TxType || (TxType = {}));
25
25
  const isHex = (s) => typeof s === 'string' && /^[0-9a-fA-F]*$/.test(s);
26
- const isUnitIdArray = (a) => Array.isArray(a) && a.every((n) => typeof n === 'number' && Number.isInteger(n));
26
+ const isPackedUnitIds = (s) => typeof s === 'string' && /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(s);
27
27
  /** Structural check that an unknown value is a well-formed TxWire. */
28
28
  export function isTxWire(o) {
29
29
  if (typeof o !== 'object' || o === null)
@@ -37,8 +37,8 @@ export function isTxWire(o) {
37
37
  t.t <= TxType.EARN &&
38
38
  isHex(t.p) &&
39
39
  isHex(t.s) &&
40
- isUnitIdArray(t.m) &&
41
- isUnitIdArray(t.i) &&
40
+ isPackedUnitIds(t.m) &&
41
+ isPackedUnitIds(t.i) &&
42
42
  isHex(t.h));
43
43
  }
44
44
  /** Structural check that an unknown value is a well-formed BlockWire. */
@@ -50,8 +50,8 @@ export function isBlockWire(o) {
50
50
  typeof b.d === 'number' &&
51
51
  isHex(b.p) &&
52
52
  isHex(b.s) &&
53
- isUnitIdArray(b.m) &&
54
- isUnitIdArray(b.i) &&
53
+ isPackedUnitIds(b.m) &&
54
+ isPackedUnitIds(b.i) &&
55
55
  typeof b.t === 'number' &&
56
56
  isHex(b.r) &&
57
57
  isHex(b.h) &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "organic-protocol",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The shared protocol of the Organic Economy: wire formats, QR codes and API contracts, verified by the TypeScript compiler. Companion of organic-money.",
5
5
  "keywords": [
6
6
  "organic-economy",
@@ -10,6 +10,14 @@
10
10
  ],
11
11
  "license": "MIT",
12
12
  "author": "suipotryot",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/OrganicEconomy/organic-protocol.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/OrganicEconomy/organic-protocol/issues"
19
+ },
20
+ "homepage": "https://github.com/OrganicEconomy/organic-protocol#readme",
13
21
  "type": "module",
14
22
  "main": "./dist/index.js",
15
23
  "types": "./dist/index.d.ts",