@verbeth/sdk 0.1.4 → 0.1.6
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/README.md +20 -168
- package/dist/esm/src/addresses.d.ts +20 -0
- package/dist/esm/src/addresses.d.ts.map +1 -0
- package/dist/esm/src/addresses.js +33 -0
- package/dist/esm/src/client/HsrTagIndex.d.ts +77 -0
- package/dist/esm/src/client/HsrTagIndex.d.ts.map +1 -0
- package/dist/esm/src/client/HsrTagIndex.js +157 -0
- package/dist/esm/src/client/PendingManager.d.ts +65 -0
- package/dist/esm/src/client/PendingManager.d.ts.map +1 -0
- package/dist/esm/src/client/PendingManager.js +84 -0
- package/dist/esm/src/client/SessionManager.d.ts +65 -0
- package/dist/esm/src/client/SessionManager.d.ts.map +1 -0
- package/dist/esm/src/client/SessionManager.js +146 -0
- package/dist/esm/src/client/VerbethClient.d.ts +153 -99
- package/dist/esm/src/client/VerbethClient.d.ts.map +1 -1
- package/dist/esm/src/client/VerbethClient.js +429 -123
- package/dist/esm/src/client/VerbethClientBuilder.d.ts +105 -0
- package/dist/esm/src/client/VerbethClientBuilder.d.ts.map +1 -0
- package/dist/esm/src/client/VerbethClientBuilder.js +146 -0
- package/dist/esm/src/client/hsrMatcher.d.ts +22 -0
- package/dist/esm/src/client/hsrMatcher.d.ts.map +1 -0
- package/dist/esm/src/client/hsrMatcher.js +31 -0
- package/dist/esm/src/client/index.d.ts +6 -1
- package/dist/esm/src/client/index.d.ts.map +1 -1
- package/dist/esm/src/client/index.js +2 -0
- package/dist/esm/src/client/types.d.ts +151 -10
- package/dist/esm/src/client/types.d.ts.map +1 -1
- package/dist/esm/src/crypto(old).d.ts +46 -0
- package/dist/esm/src/crypto(old).d.ts.map +1 -0
- package/dist/esm/src/crypto(old).js +137 -0
- package/dist/esm/src/crypto.d.ts +7 -29
- package/dist/esm/src/crypto.d.ts.map +1 -1
- package/dist/esm/src/crypto.js +36 -72
- package/dist/esm/src/executor.d.ts +17 -18
- package/dist/esm/src/executor.d.ts.map +1 -1
- package/dist/esm/src/executor.js +54 -70
- package/dist/esm/src/handshake.d.ts +51 -0
- package/dist/esm/src/handshake.d.ts.map +1 -0
- package/dist/esm/src/handshake.js +105 -0
- package/dist/esm/src/identity.d.ts +24 -18
- package/dist/esm/src/identity.d.ts.map +1 -1
- package/dist/esm/src/identity.js +126 -31
- package/dist/esm/src/index.d.ts +11 -7
- package/dist/esm/src/index.d.ts.map +1 -1
- package/dist/esm/src/index.js +10 -7
- package/dist/esm/src/payload.d.ts +3 -30
- package/dist/esm/src/payload.d.ts.map +1 -1
- package/dist/esm/src/payload.js +3 -77
- package/dist/esm/src/pq/kem.d.ts +33 -0
- package/dist/esm/src/pq/kem.d.ts.map +1 -0
- package/dist/esm/src/pq/kem.js +40 -0
- package/dist/esm/src/ratchet/auth.d.ts +34 -0
- package/dist/esm/src/ratchet/auth.d.ts.map +1 -0
- package/dist/esm/src/ratchet/auth.js +88 -0
- package/dist/esm/src/ratchet/codec.d.ts +52 -0
- package/dist/esm/src/ratchet/codec.d.ts.map +1 -0
- package/dist/esm/src/ratchet/codec.js +127 -0
- package/dist/esm/src/ratchet/decrypt.d.ts +28 -0
- package/dist/esm/src/ratchet/decrypt.d.ts.map +1 -0
- package/dist/esm/src/ratchet/decrypt.js +255 -0
- package/dist/esm/src/ratchet/encrypt.d.ts +17 -0
- package/dist/esm/src/ratchet/encrypt.d.ts.map +1 -0
- package/dist/esm/src/ratchet/encrypt.js +78 -0
- package/dist/esm/src/ratchet/index.d.ts +8 -0
- package/dist/esm/src/ratchet/index.d.ts.map +1 -0
- package/dist/esm/src/ratchet/index.js +8 -0
- package/dist/esm/src/ratchet/kdf.d.ts +60 -0
- package/dist/esm/src/ratchet/kdf.d.ts.map +1 -0
- package/dist/esm/src/ratchet/kdf.js +91 -0
- package/dist/esm/src/ratchet/session.d.ts +43 -0
- package/dist/esm/src/ratchet/session.d.ts.map +1 -0
- package/dist/esm/src/ratchet/session.js +139 -0
- package/dist/esm/src/ratchet/types.d.ts +168 -0
- package/dist/esm/src/ratchet/types.d.ts.map +1 -0
- package/dist/esm/src/ratchet/types.js +27 -0
- package/dist/esm/src/safeSessionSigner.d.ts +35 -0
- package/dist/esm/src/safeSessionSigner.d.ts.map +1 -0
- package/dist/esm/src/safeSessionSigner.js +59 -0
- package/dist/esm/src/send.d.ts +32 -24
- package/dist/esm/src/send.d.ts.map +1 -1
- package/dist/esm/src/send.js +84 -39
- package/dist/esm/src/types.d.ts +8 -13
- package/dist/esm/src/types.d.ts.map +1 -1
- package/dist/esm/src/utils/safeSessionSigner.d.ts +23 -0
- package/dist/esm/src/utils/safeSessionSigner.d.ts.map +1 -0
- package/dist/esm/src/utils/safeSessionSigner.js +59 -0
- package/dist/esm/src/utils/txQueue.d.ts +12 -0
- package/dist/esm/src/utils/txQueue.d.ts.map +1 -0
- package/dist/esm/src/utils/txQueue.js +25 -0
- package/dist/esm/src/utils.d.ts +2 -3
- package/dist/esm/src/utils.d.ts.map +1 -1
- package/dist/esm/src/utils.js +5 -5
- package/dist/esm/src/verify.d.ts +9 -25
- package/dist/esm/src/verify.d.ts.map +1 -1
- package/dist/esm/src/verify.js +49 -50
- package/dist/src/addresses.d.ts +20 -0
- package/dist/src/addresses.d.ts.map +1 -0
- package/dist/src/addresses.js +33 -0
- package/dist/src/client/HsrTagIndex.d.ts +77 -0
- package/dist/src/client/HsrTagIndex.d.ts.map +1 -0
- package/dist/src/client/HsrTagIndex.js +157 -0
- package/dist/src/client/PendingManager.d.ts +65 -0
- package/dist/src/client/PendingManager.d.ts.map +1 -0
- package/dist/src/client/PendingManager.js +84 -0
- package/dist/src/client/SessionManager.d.ts +65 -0
- package/dist/src/client/SessionManager.d.ts.map +1 -0
- package/dist/src/client/SessionManager.js +146 -0
- package/dist/src/client/VerbethClient.d.ts +153 -99
- package/dist/src/client/VerbethClient.d.ts.map +1 -1
- package/dist/src/client/VerbethClient.js +429 -123
- package/dist/src/client/VerbethClientBuilder.d.ts +105 -0
- package/dist/src/client/VerbethClientBuilder.d.ts.map +1 -0
- package/dist/src/client/VerbethClientBuilder.js +146 -0
- package/dist/src/client/hsrMatcher.d.ts +22 -0
- package/dist/src/client/hsrMatcher.d.ts.map +1 -0
- package/dist/src/client/hsrMatcher.js +31 -0
- package/dist/src/client/index.d.ts +6 -1
- package/dist/src/client/index.d.ts.map +1 -1
- package/dist/src/client/index.js +2 -0
- package/dist/src/client/types.d.ts +151 -10
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/crypto(old).d.ts +46 -0
- package/dist/src/crypto(old).d.ts.map +1 -0
- package/dist/src/crypto(old).js +137 -0
- package/dist/src/crypto.d.ts +7 -29
- package/dist/src/crypto.d.ts.map +1 -1
- package/dist/src/crypto.js +36 -72
- package/dist/src/executor.d.ts +17 -18
- package/dist/src/executor.d.ts.map +1 -1
- package/dist/src/executor.js +54 -70
- package/dist/src/handshake.d.ts +51 -0
- package/dist/src/handshake.d.ts.map +1 -0
- package/dist/src/handshake.js +105 -0
- package/dist/src/identity.d.ts +24 -18
- package/dist/src/identity.d.ts.map +1 -1
- package/dist/src/identity.js +126 -31
- package/dist/src/index.d.ts +11 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -7
- package/dist/src/payload.d.ts +3 -30
- package/dist/src/payload.d.ts.map +1 -1
- package/dist/src/payload.js +3 -77
- package/dist/src/pq/kem.d.ts +33 -0
- package/dist/src/pq/kem.d.ts.map +1 -0
- package/dist/src/pq/kem.js +40 -0
- package/dist/src/ratchet/auth.d.ts +34 -0
- package/dist/src/ratchet/auth.d.ts.map +1 -0
- package/dist/src/ratchet/auth.js +88 -0
- package/dist/src/ratchet/codec.d.ts +52 -0
- package/dist/src/ratchet/codec.d.ts.map +1 -0
- package/dist/src/ratchet/codec.js +127 -0
- package/dist/src/ratchet/decrypt.d.ts +28 -0
- package/dist/src/ratchet/decrypt.d.ts.map +1 -0
- package/dist/src/ratchet/decrypt.js +255 -0
- package/dist/src/ratchet/encrypt.d.ts +17 -0
- package/dist/src/ratchet/encrypt.d.ts.map +1 -0
- package/dist/src/ratchet/encrypt.js +78 -0
- package/dist/src/ratchet/index.d.ts +8 -0
- package/dist/src/ratchet/index.d.ts.map +1 -0
- package/dist/src/ratchet/index.js +8 -0
- package/dist/src/ratchet/kdf.d.ts +60 -0
- package/dist/src/ratchet/kdf.d.ts.map +1 -0
- package/dist/src/ratchet/kdf.js +91 -0
- package/dist/src/ratchet/session.d.ts +43 -0
- package/dist/src/ratchet/session.d.ts.map +1 -0
- package/dist/src/ratchet/session.js +139 -0
- package/dist/src/ratchet/types.d.ts +168 -0
- package/dist/src/ratchet/types.d.ts.map +1 -0
- package/dist/src/ratchet/types.js +27 -0
- package/dist/src/safeSessionSigner.d.ts +35 -0
- package/dist/src/safeSessionSigner.d.ts.map +1 -0
- package/dist/src/safeSessionSigner.js +59 -0
- package/dist/src/send.d.ts +32 -24
- package/dist/src/send.d.ts.map +1 -1
- package/dist/src/send.js +84 -39
- package/dist/src/types.d.ts +8 -13
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/safeSessionSigner.d.ts +23 -0
- package/dist/src/utils/safeSessionSigner.d.ts.map +1 -0
- package/dist/src/utils/safeSessionSigner.js +59 -0
- package/dist/src/utils/txQueue.d.ts +12 -0
- package/dist/src/utils/txQueue.d.ts.map +1 -0
- package/dist/src/utils/txQueue.js +25 -0
- package/dist/src/utils.d.ts +2 -3
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +5 -5
- package/dist/src/verify.d.ts +9 -25
- package/dist/src/verify.d.ts.map +1 -1
- package/dist/src/verify.js +49 -50
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,191 +1,43 @@
|
|
|
1
1
|
# @verbeth/sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **End-to-end encryption** using NaCl Box (X25519 + XSalsa20-Poly1305)
|
|
8
|
-
- **Forward secrecy** with ephemeral keys per message
|
|
9
|
-
- **Handshake protocol** for secure key exchange
|
|
10
|
-
- **Privacy-focused** with minimal metadata via `recipientHash`
|
|
11
|
-
- **EOA & Smart Account support** (ERC-1271/6492 compatible)
|
|
12
|
-
- **Fully on-chain** - no centralized infrastructure
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
3
|
+
End-to-end encrypted messaging over public EVM blockchains.
|
|
15
4
|
|
|
5
|
+
### Install
|
|
16
6
|
```bash
|
|
17
|
-
npm install @verbeth/sdk
|
|
7
|
+
npm install @verbeth/sdk
|
|
18
8
|
```
|
|
19
9
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
### Quickstart
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
createVerbethClient,
|
|
14
|
+
deriveIdentityKeyPairWithProof,
|
|
15
|
+
ExecutorFactory,
|
|
16
|
+
getVerbethAddress
|
|
17
|
+
} from '@verbeth/sdk';
|
|
18
|
+
import { ethers } from 'ethers';
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
const provider = new BrowserProvider(window.ethereum);
|
|
20
|
+
const provider = new ethers.BrowserProvider(window.ethereum);
|
|
31
21
|
const signer = await provider.getSigner();
|
|
32
22
|
const address = await signer.getAddress();
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
const contract = LogChainV1__factory.connect(LOGCHAIN_ADDRESS, signer);
|
|
36
|
-
|
|
37
|
-
// Derive identity keys (done once, then stored)
|
|
38
|
-
const { identityKeyPair, identityProof } = await deriveIdentityKeyPairWithProof(signer);
|
|
39
|
-
|
|
40
|
-
// Create executor (handles transaction submission)
|
|
41
|
-
const executor = ExecutorFactory.createEOA(contract);
|
|
24
|
+
const { identityKeyPair, identityProof } = await deriveIdentityKeyPairWithProof(signer, address);
|
|
42
25
|
|
|
43
|
-
|
|
44
|
-
const client =
|
|
45
|
-
|
|
46
|
-
identityKeyPair,
|
|
47
|
-
identityProof,
|
|
26
|
+
const contract = new ethers.Contract(getVerbethAddress(), VerbethABI, signer);
|
|
27
|
+
const client = createVerbethClient({
|
|
28
|
+
address,
|
|
48
29
|
signer,
|
|
49
|
-
address
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Send a handshake to start chatting
|
|
53
|
-
const { tx, ephemeralKeyPair } = await client.sendHandshake(
|
|
54
|
-
'0xRecipientAddress...',
|
|
55
|
-
'Hello! Want to chat?'
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// Store ephemeralKeyPair. you'll just need it to decrypt the handshake response!
|
|
59
|
-
|
|
60
|
-
// Accept a handshake
|
|
61
|
-
const { tx, duplexTopics } = await client.acceptHandshake(
|
|
62
|
-
handshakeEvent.ephemeralPubKey,
|
|
63
|
-
handshakeEvent.identityPubKey,
|
|
64
|
-
'Sure, lets chat!'
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// Send encrypted messages
|
|
68
|
-
await client.sendMessage(
|
|
69
|
-
duplexTopics.topicOut,
|
|
70
|
-
recipientIdentityPubKey,
|
|
71
|
-
'This message is encrypted!'
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
// Decrypt received messages
|
|
75
|
-
const decrypted = await client.decryptMessage(
|
|
76
|
-
messageEvent.ciphertext,
|
|
77
|
-
senderIdentityPubKey
|
|
78
|
-
);
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### 2. Low-level API
|
|
82
|
-
|
|
83
|
-
For more control, use the low-level functions:
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
import {
|
|
87
|
-
initiateHandshake,
|
|
88
|
-
respondToHandshake,
|
|
89
|
-
sendEncryptedMessage,
|
|
90
|
-
decryptMessage,
|
|
91
|
-
deriveIdentityKeyPairWithProof
|
|
92
|
-
} from '@verbeth/sdk';
|
|
93
|
-
|
|
94
|
-
// Generate identity keys
|
|
95
|
-
const { identityKeyPair, identityProof } = await deriveIdentityKeyPairWithProof(signer);
|
|
96
|
-
|
|
97
|
-
// Initiate handshake
|
|
98
|
-
const ephemeralKeyPair = nacl.box.keyPair();
|
|
99
|
-
const tx = await initiateHandshake({
|
|
100
|
-
executor,
|
|
101
|
-
recipientAddress: '0xBob...',
|
|
102
|
-
ephemeralPubKey: ephemeralKeyPair.publicKey,
|
|
103
30
|
identityKeyPair,
|
|
104
31
|
identityProof,
|
|
105
|
-
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// Send encrypted message
|
|
109
|
-
await sendEncryptedMessage({
|
|
110
|
-
executor,
|
|
111
|
-
topic: derivedTopic,
|
|
112
|
-
message: 'Secret message',
|
|
113
|
-
recipientPubKey: bobsIdentityKey,
|
|
114
|
-
senderAddress: myAddress,
|
|
115
|
-
senderSignKeyPair: identityKeyPair,
|
|
116
|
-
timestamp: Date.now()
|
|
32
|
+
executor: ExecutorFactory.createEOA(contract),
|
|
117
33
|
});
|
|
118
34
|
|
|
119
|
-
|
|
120
|
-
const plaintext = decryptMessage(
|
|
121
|
-
ciphertext,
|
|
122
|
-
senderIdentityPubKey,
|
|
123
|
-
myIdentityKeyPair.secretKey
|
|
124
|
-
);
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Smart Account Support
|
|
128
|
-
|
|
129
|
-
Verbeth works with ERC-4337 smart accounts:
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
import { ExecutorFactory } from '@verbeth/sdk';
|
|
133
|
-
|
|
134
|
-
// For UserOp-based execution
|
|
135
|
-
const executor = ExecutorFactory.createUserOp(
|
|
136
|
-
contract,
|
|
137
|
-
bundler,
|
|
138
|
-
smartAccount,
|
|
139
|
-
signer
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
// For direct EntryPoint execution
|
|
143
|
-
const executor = ExecutorFactory.createDirectEntryPoint(
|
|
144
|
-
contract,
|
|
145
|
-
entryPoint,
|
|
146
|
-
smartAccountAddress,
|
|
147
|
-
signer
|
|
148
|
-
);
|
|
35
|
+
await client.sendMessage(conversationId, 'Hello, encrypted world!');
|
|
149
36
|
```
|
|
150
37
|
|
|
151
|
-
## Contract Addresses
|
|
152
|
-
|
|
153
|
-
**LogChainV1 Singleton:** `0x41a3eaC0d858028E9228d1E2092e6178fc81c4f0`
|
|
154
|
-
|
|
155
|
-
**ERC1967Proxy:** `0x62720f39d5Ec6501508bDe4D152c1E13Fd2F6707`
|
|
156
|
-
|
|
157
|
-
## How It Works
|
|
158
|
-
|
|
159
|
-
1. **Identity Keys**: Each account derives long-term X25519 (encryption) + Ed25519 (signing) keys bound to their address via signature
|
|
160
|
-
2. **Handshake**: Alice sends her ephemeral key + identity proof to Bob via a `Handshake` event
|
|
161
|
-
3. **Response**: Bob verifies Alice's identity and responds with his keys + duplex topics
|
|
162
|
-
4. **Messaging**: Both parties derive shared topics and exchange encrypted messages via `MessageSent` events
|
|
163
|
-
5. **Decryption**: Recipients monitor their inbound topic and decrypt with their identity key
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
## Security Considerations
|
|
167
|
-
|
|
168
|
-
- **Forward Secrecy**: Fresh ephemeral keys per message provide sender-side forward secrecy
|
|
169
|
-
- **Identity Binding**: Addresses are cryptographically bound to long-term keys via signature
|
|
170
|
-
- **Non-Repudiation**: Optional Ed25519 signatures prove message origin
|
|
171
|
-
- **Privacy**: RecipientHash hides recipient identity; duplex topics separate communication channels
|
|
172
|
-
|
|
173
|
-
⚠️ **Note**: Current design provides sender-side forward secrecy. Recipient-side FS requires ephemeral↔ephemeral or session ratcheting (e.g., Double Ratchet).
|
|
174
|
-
|
|
175
|
-
## Built With
|
|
176
|
-
|
|
177
|
-
- [TweetNaCl](https://tweetnacl.js.org/) - Encryption primitives
|
|
178
|
-
- [Ethers v6](https://docs.ethers.org/v6/) - Ethereum interactions
|
|
179
|
-
- [Viem](https://viem.sh/) - EIP-1271/6492 verification
|
|
180
|
-
- [Noble Curves](https://github.com/paulmillr/noble-curves) - Elliptic curve operations
|
|
181
|
-
|
|
182
|
-
## Examples
|
|
183
|
-
|
|
184
|
-
Check out the [demo application](https://github.com/okrame/verbeth-sdk/tree/main/apps/demo) for a complete implementation.
|
|
185
|
-
|
|
186
38
|
## Documentation
|
|
187
39
|
|
|
188
|
-
For detailed protocol documentation,
|
|
40
|
+
For detailed protocol documentation, see [docs.verbeth.xyz](https://docs.verbeth.xyz).
|
|
189
41
|
|
|
190
42
|
## License
|
|
191
43
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ChainConfig {
|
|
2
|
+
verbethProxy: `0x${string}`;
|
|
3
|
+
verbethImpl: `0x${string}`;
|
|
4
|
+
creationBlock: number;
|
|
5
|
+
moduleSetupHelper?: `0x${string}`;
|
|
6
|
+
}
|
|
7
|
+
export declare const VERBETH_CONFIG: ChainConfig;
|
|
8
|
+
export declare const MODULE_SETUP_HELPERS: Record<number, `0x${string}`>;
|
|
9
|
+
export declare function getVerbethAddress(): `0x${string}`;
|
|
10
|
+
export declare function getCreationBlock(): number;
|
|
11
|
+
export declare function getModuleSetupHelper(chainId: number): `0x${string}` | undefined;
|
|
12
|
+
export declare function isModuleSetupSupported(chainId: number): boolean;
|
|
13
|
+
export declare const SCAN_DEFAULTS: {
|
|
14
|
+
readonly INITIAL_SCAN_BLOCKS: 1000;
|
|
15
|
+
readonly MAX_RETRIES: 3;
|
|
16
|
+
readonly MAX_RANGE_PROVIDER: 2000;
|
|
17
|
+
readonly CHUNK_SIZE: 2000;
|
|
18
|
+
readonly REAL_TIME_BUFFER: 3;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=addresses.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addresses.d.ts","sourceRoot":"","sources":["../../../src/addresses.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;CACnC;AAGD,eAAO,MAAM,cAAc,EAAE,WAInB,CAAC;AAGX,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,MAAM,EAAE,CAGrD,CAAC;AAGX,wBAAgB,iBAAiB,IAAI,KAAK,MAAM,EAAE,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,SAAS,CAE/E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE/D;AAGD,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// packages/sdk/src/addresses.ts
|
|
2
|
+
// Deterministic deployment - same addresses on Base Mainnet & Sepolia
|
|
3
|
+
export const VERBETH_CONFIG = {
|
|
4
|
+
verbethProxy: '0x82C9c5475D63e4C9e959280e9066aBb24973a663',
|
|
5
|
+
verbethImpl: '0x51670aB6eDE1d1B11C654CCA53b7D42080802326',
|
|
6
|
+
creationBlock: 36053269,
|
|
7
|
+
};
|
|
8
|
+
// Per-chain module setup helpers (for Safe session module)
|
|
9
|
+
export const MODULE_SETUP_HELPERS = {
|
|
10
|
+
8453: '0xc022F74924BDB4b62D830234d89b066359bF67c0', // Base Mainnet
|
|
11
|
+
84532: '0xbd59Fea46D308eDF3b75C22a6f64AC68feFc731A', // Base Sepolia
|
|
12
|
+
};
|
|
13
|
+
// Helper functions
|
|
14
|
+
export function getVerbethAddress() {
|
|
15
|
+
return VERBETH_CONFIG.verbethProxy;
|
|
16
|
+
}
|
|
17
|
+
export function getCreationBlock() {
|
|
18
|
+
return VERBETH_CONFIG.creationBlock;
|
|
19
|
+
}
|
|
20
|
+
export function getModuleSetupHelper(chainId) {
|
|
21
|
+
return MODULE_SETUP_HELPERS[chainId];
|
|
22
|
+
}
|
|
23
|
+
export function isModuleSetupSupported(chainId) {
|
|
24
|
+
return chainId in MODULE_SETUP_HELPERS;
|
|
25
|
+
}
|
|
26
|
+
// Scanning defaults (chain-agnostic)
|
|
27
|
+
export const SCAN_DEFAULTS = {
|
|
28
|
+
INITIAL_SCAN_BLOCKS: 1000,
|
|
29
|
+
MAX_RETRIES: 3,
|
|
30
|
+
MAX_RANGE_PROVIDER: 2000,
|
|
31
|
+
CHUNK_SIZE: 2000,
|
|
32
|
+
REAL_TIME_BUFFER: 3,
|
|
33
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export interface PendingContactEntry {
|
|
2
|
+
address: string;
|
|
3
|
+
handshakeEphemeralSecret: Uint8Array;
|
|
4
|
+
kemSecretKey: Uint8Array;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Index for efficient HSR tag matching.
|
|
8
|
+
*
|
|
9
|
+
* When an HSR arrives, we need to find which pending contact it belongs to.
|
|
10
|
+
* This requires computing tag = computeTagFromInitiator(secret, R) for each
|
|
11
|
+
* pending contact until we find a match.
|
|
12
|
+
*
|
|
13
|
+
* This class caches the computed tags per (contact, R) pair, making
|
|
14
|
+
* subsequent lookups O(1) after the first computation.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const hsrIndex = new HsrTagIndex();
|
|
19
|
+
*
|
|
20
|
+
* // When pending contacts change
|
|
21
|
+
* hsrIndex.rebuild(pendingContacts.map(c => ({
|
|
22
|
+
* address: c.address,
|
|
23
|
+
* handshakeEphemeralSecret: c.handshakeEphemeralSecret
|
|
24
|
+
* })));
|
|
25
|
+
*
|
|
26
|
+
* // Matching O(1) after first computation for each R
|
|
27
|
+
* const matchedAddress = hsrIndex.matchByTag(inResponseToTag, R);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare class HsrTagIndex {
|
|
31
|
+
private entries;
|
|
32
|
+
private tagToAddress;
|
|
33
|
+
/**
|
|
34
|
+
* Rebuild the index with a new set of pending contacts.
|
|
35
|
+
*
|
|
36
|
+
* Preserves cached tag computations for contacts that remain.
|
|
37
|
+
* Clears entries for contacts no longer in the list.
|
|
38
|
+
*
|
|
39
|
+
* @param contacts - Current pending contacts
|
|
40
|
+
*/
|
|
41
|
+
rebuild(contacts: PendingContactEntry[]): void;
|
|
42
|
+
/**
|
|
43
|
+
* Add a single pending contact without full rebuild.
|
|
44
|
+
*
|
|
45
|
+
* @param contact - Contact to add
|
|
46
|
+
*/
|
|
47
|
+
addContact(contact: PendingContactEntry): void;
|
|
48
|
+
/**
|
|
49
|
+
* Remove a contact from the index.
|
|
50
|
+
*
|
|
51
|
+
* @param address - Address of contact to remove
|
|
52
|
+
*/
|
|
53
|
+
removeContact(address: string): void;
|
|
54
|
+
clear(): void;
|
|
55
|
+
/**
|
|
56
|
+
* Match an HSR by its tag using hybrid (PQ-secure) computation.
|
|
57
|
+
*
|
|
58
|
+
* Decrypts the payload internally to extract kemCiphertext, then
|
|
59
|
+
* decapsulates and computes the hybrid tag for matching.
|
|
60
|
+
*
|
|
61
|
+
* @param inResponseToTag - The tag from the HSR event
|
|
62
|
+
* @param R - Responder's ephemeral public key (from HSR event)
|
|
63
|
+
* @param encryptedPayload - JSON string of the encrypted HSR payload
|
|
64
|
+
* @returns Address of matching contact, or null if no match
|
|
65
|
+
*/
|
|
66
|
+
matchByTag(inResponseToTag: `0x${string}`, R: Uint8Array, encryptedPayload: string): string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Get the number of indexed contacts.
|
|
69
|
+
*/
|
|
70
|
+
get size(): number;
|
|
71
|
+
/**
|
|
72
|
+
* Check if a contact is in the index.
|
|
73
|
+
*/
|
|
74
|
+
hasContact(address: string): boolean;
|
|
75
|
+
private secretsEqual;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=HsrTagIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HsrTagIndex.d.ts","sourceRoot":"","sources":["../../../../src/client/HsrTagIndex.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB,EAAE,UAAU,CAAC;IACrC,YAAY,EAAE,UAAU,CAAC;CAC1B;AAQD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,YAAY,CAAkC;IAEtD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,IAAI;IAuB9C;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAc9C;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAcpC,KAAK,IAAI,IAAI;IAKb;;;;;;;;;;OAUG;IACH,UAAU,CACR,eAAe,EAAE,KAAK,MAAM,EAAE,EAC9B,CAAC,EAAE,UAAU,EACb,gBAAgB,EAAE,MAAM,GACvB,MAAM,GAAG,IAAI;IAyBhB;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIpC,OAAO,CAAC,YAAY;CAOrB"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// packages/sdk/src/client/HsrTagIndex.ts
|
|
2
|
+
/**
|
|
3
|
+
* Index for O(1) HSR (Handshake Response) matching.
|
|
4
|
+
*
|
|
5
|
+
* Caches computed tags for pending contacts to avoid O(n) loops
|
|
6
|
+
* when matching incoming handshake responses.
|
|
7
|
+
*/
|
|
8
|
+
import { computeHybridTagFromInitiator, decryptHandshakeResponse } from '../crypto.js';
|
|
9
|
+
import { kem } from '../pq/kem.js';
|
|
10
|
+
/**
|
|
11
|
+
* Index for efficient HSR tag matching.
|
|
12
|
+
*
|
|
13
|
+
* When an HSR arrives, we need to find which pending contact it belongs to.
|
|
14
|
+
* This requires computing tag = computeTagFromInitiator(secret, R) for each
|
|
15
|
+
* pending contact until we find a match.
|
|
16
|
+
*
|
|
17
|
+
* This class caches the computed tags per (contact, R) pair, making
|
|
18
|
+
* subsequent lookups O(1) after the first computation.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const hsrIndex = new HsrTagIndex();
|
|
23
|
+
*
|
|
24
|
+
* // When pending contacts change
|
|
25
|
+
* hsrIndex.rebuild(pendingContacts.map(c => ({
|
|
26
|
+
* address: c.address,
|
|
27
|
+
* handshakeEphemeralSecret: c.handshakeEphemeralSecret
|
|
28
|
+
* })));
|
|
29
|
+
*
|
|
30
|
+
* // Matching O(1) after first computation for each R
|
|
31
|
+
* const matchedAddress = hsrIndex.matchByTag(inResponseToTag, R);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class HsrTagIndex {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.entries = new Map();
|
|
37
|
+
this.tagToAddress = new Map();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Rebuild the index with a new set of pending contacts.
|
|
41
|
+
*
|
|
42
|
+
* Preserves cached tag computations for contacts that remain.
|
|
43
|
+
* Clears entries for contacts no longer in the list.
|
|
44
|
+
*
|
|
45
|
+
* @param contacts - Current pending contacts
|
|
46
|
+
*/
|
|
47
|
+
rebuild(contacts) {
|
|
48
|
+
const newEntries = new Map();
|
|
49
|
+
for (const contact of contacts) {
|
|
50
|
+
const existing = this.entries.get(contact.address);
|
|
51
|
+
if (existing &&
|
|
52
|
+
this.secretsEqual(existing.secret, contact.handshakeEphemeralSecret) &&
|
|
53
|
+
this.secretsEqual(existing.kemSecretKey, contact.kemSecretKey)) {
|
|
54
|
+
newEntries.set(contact.address, existing);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
newEntries.set(contact.address, {
|
|
58
|
+
address: contact.address,
|
|
59
|
+
secret: contact.handshakeEphemeralSecret,
|
|
60
|
+
kemSecretKey: contact.kemSecretKey,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Keep tagToAddress cache - it's still valid for already computed tags
|
|
65
|
+
this.entries = newEntries;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Add a single pending contact without full rebuild.
|
|
69
|
+
*
|
|
70
|
+
* @param contact - Contact to add
|
|
71
|
+
*/
|
|
72
|
+
addContact(contact) {
|
|
73
|
+
const existing = this.entries.get(contact.address);
|
|
74
|
+
if (!existing ||
|
|
75
|
+
!this.secretsEqual(existing.secret, contact.handshakeEphemeralSecret) ||
|
|
76
|
+
!this.secretsEqual(existing.kemSecretKey, contact.kemSecretKey)) {
|
|
77
|
+
this.entries.set(contact.address, {
|
|
78
|
+
address: contact.address,
|
|
79
|
+
secret: contact.handshakeEphemeralSecret,
|
|
80
|
+
kemSecretKey: contact.kemSecretKey,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Remove a contact from the index.
|
|
86
|
+
*
|
|
87
|
+
* @param address - Address of contact to remove
|
|
88
|
+
*/
|
|
89
|
+
removeContact(address) {
|
|
90
|
+
const entry = this.entries.get(address);
|
|
91
|
+
if (entry) {
|
|
92
|
+
// Remove any cached tags for this address
|
|
93
|
+
for (const [tag, addr] of this.tagToAddress) {
|
|
94
|
+
if (addr === address) {
|
|
95
|
+
this.tagToAddress.delete(tag);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this.entries.delete(address);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
clear() {
|
|
102
|
+
this.entries.clear();
|
|
103
|
+
this.tagToAddress.clear();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Match an HSR by its tag using hybrid (PQ-secure) computation.
|
|
107
|
+
*
|
|
108
|
+
* Decrypts the payload internally to extract kemCiphertext, then
|
|
109
|
+
* decapsulates and computes the hybrid tag for matching.
|
|
110
|
+
*
|
|
111
|
+
* @param inResponseToTag - The tag from the HSR event
|
|
112
|
+
* @param R - Responder's ephemeral public key (from HSR event)
|
|
113
|
+
* @param encryptedPayload - JSON string of the encrypted HSR payload
|
|
114
|
+
* @returns Address of matching contact, or null if no match
|
|
115
|
+
*/
|
|
116
|
+
matchByTag(inResponseToTag, R, encryptedPayload) {
|
|
117
|
+
// Cache check
|
|
118
|
+
const cachedAddress = this.tagToAddress.get(inResponseToTag);
|
|
119
|
+
if (cachedAddress) {
|
|
120
|
+
return cachedAddress;
|
|
121
|
+
}
|
|
122
|
+
// For each contact: decrypt → extract kemCiphertext → decapsulate → compute hybrid tag
|
|
123
|
+
for (const [address, entry] of this.entries) {
|
|
124
|
+
const decrypted = decryptHandshakeResponse(encryptedPayload, entry.secret);
|
|
125
|
+
if (!decrypted || !decrypted.kemCiphertext)
|
|
126
|
+
continue;
|
|
127
|
+
const kemSecret = kem.decapsulate(decrypted.kemCiphertext, entry.kemSecretKey);
|
|
128
|
+
const tag = computeHybridTagFromInitiator(entry.secret, R, kemSecret);
|
|
129
|
+
this.tagToAddress.set(tag, address);
|
|
130
|
+
if (tag === inResponseToTag) {
|
|
131
|
+
return address;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get the number of indexed contacts.
|
|
138
|
+
*/
|
|
139
|
+
get size() {
|
|
140
|
+
return this.entries.size;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if a contact is in the index.
|
|
144
|
+
*/
|
|
145
|
+
hasContact(address) {
|
|
146
|
+
return this.entries.has(address);
|
|
147
|
+
}
|
|
148
|
+
secretsEqual(a, b) {
|
|
149
|
+
if (a.length !== b.length)
|
|
150
|
+
return false;
|
|
151
|
+
for (let i = 0; i < a.length; i++) {
|
|
152
|
+
if (a[i] !== b[i])
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal Pending Message Coordinator.
|
|
3
|
+
*
|
|
4
|
+
* Manages the lifecycle of outbound messages:
|
|
5
|
+
* - Creating pending records before tx submission
|
|
6
|
+
* - Updating status on submission
|
|
7
|
+
* - Matching confirmations by txHash
|
|
8
|
+
* - Cleaning up after confirmation or failure
|
|
9
|
+
*/
|
|
10
|
+
import { PendingStore, PendingMessage } from './types.js';
|
|
11
|
+
export interface CreatePendingParams {
|
|
12
|
+
id: string;
|
|
13
|
+
conversationId: string;
|
|
14
|
+
topic: string;
|
|
15
|
+
payloadHex: string;
|
|
16
|
+
plaintext: string;
|
|
17
|
+
sessionStateBefore: string;
|
|
18
|
+
sessionStateAfter: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class PendingManager {
|
|
22
|
+
private store;
|
|
23
|
+
constructor(store: PendingStore);
|
|
24
|
+
/**
|
|
25
|
+
* Create and save a pending message record.
|
|
26
|
+
* Called right before submitting a transaction.
|
|
27
|
+
*/
|
|
28
|
+
create(params: CreatePendingParams): Promise<PendingMessage>;
|
|
29
|
+
/**
|
|
30
|
+
* Mark as submitted with transaction hash.
|
|
31
|
+
* Called immediately after tx is broadcast.
|
|
32
|
+
*/
|
|
33
|
+
markSubmitted(id: string, txHash: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Mark as failed.
|
|
36
|
+
* Called when tx submission fails.
|
|
37
|
+
* Note: Ratchet slot is already burned (session was committed).
|
|
38
|
+
*/
|
|
39
|
+
markFailed(id: string): Promise<void>;
|
|
40
|
+
get(id: string): Promise<PendingMessage | null>;
|
|
41
|
+
getByTxHash(txHash: string): Promise<PendingMessage | null>;
|
|
42
|
+
getByConversation(conversationId: string): Promise<PendingMessage[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Finalize and delete.
|
|
45
|
+
* Called when we see our MessageSent event on-chain.
|
|
46
|
+
*
|
|
47
|
+
* @returns The finalized pending message, or null if not found
|
|
48
|
+
*/
|
|
49
|
+
finalize(id: string): Promise<PendingMessage | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Delete a pending message without finalizing.
|
|
52
|
+
* Used for cleanup on failure or cancellation.
|
|
53
|
+
*/
|
|
54
|
+
delete(id: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Clean up stale pending messages.
|
|
57
|
+
* Called periodically to remove old failed/stuck records.
|
|
58
|
+
*
|
|
59
|
+
* @param conversationId - Conversation to clean up
|
|
60
|
+
* @param maxAgeMs - Maximum age in milliseconds (default: 24 hours)
|
|
61
|
+
* @returns Number of records cleaned up
|
|
62
|
+
*/
|
|
63
|
+
cleanupStale(conversationId: string, maxAgeMs?: number): Promise<number>;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=PendingManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PendingManager.d.ts","sourceRoot":"","sources":["../../../../src/client/PendingManager.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAiB,MAAM,YAAY,CAAC;AAEzE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,cAAc;IACb,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,YAAY;IAEvC;;;OAGG;IACG,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;IAUlE;;;OAGG;IACG,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D;;;;OAIG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAK/C,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAI3D,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAK1E;;;;;OAKG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAU1D;;;OAGG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC;;;;;;;OAOG;IACG,YAAY,CAChB,cAAc,EAAE,MAAM,EACtB,QAAQ,GAAE,MAA4B,GACrC,OAAO,CAAC,MAAM,CAAC;CAcnB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// packages/sdk/src/client/PendingManager.ts
|
|
2
|
+
export class PendingManager {
|
|
3
|
+
constructor(store) {
|
|
4
|
+
this.store = store;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Create and save a pending message record.
|
|
8
|
+
* Called right before submitting a transaction.
|
|
9
|
+
*/
|
|
10
|
+
async create(params) {
|
|
11
|
+
const pending = {
|
|
12
|
+
...params,
|
|
13
|
+
txHash: null,
|
|
14
|
+
status: 'preparing',
|
|
15
|
+
};
|
|
16
|
+
await this.store.save(pending);
|
|
17
|
+
return pending;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Mark as submitted with transaction hash.
|
|
21
|
+
* Called immediately after tx is broadcast.
|
|
22
|
+
*/
|
|
23
|
+
async markSubmitted(id, txHash) {
|
|
24
|
+
await this.store.updateStatus(id, 'submitted', txHash);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Mark as failed.
|
|
28
|
+
* Called when tx submission fails.
|
|
29
|
+
* Note: Ratchet slot is already burned (session was committed).
|
|
30
|
+
*/
|
|
31
|
+
async markFailed(id) {
|
|
32
|
+
await this.store.updateStatus(id, 'failed');
|
|
33
|
+
}
|
|
34
|
+
async get(id) {
|
|
35
|
+
return this.store.get(id);
|
|
36
|
+
}
|
|
37
|
+
async getByTxHash(txHash) {
|
|
38
|
+
return this.store.getByTxHash(txHash);
|
|
39
|
+
}
|
|
40
|
+
async getByConversation(conversationId) {
|
|
41
|
+
return this.store.getByConversation(conversationId);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Finalize and delete.
|
|
45
|
+
* Called when we see our MessageSent event on-chain.
|
|
46
|
+
*
|
|
47
|
+
* @returns The finalized pending message, or null if not found
|
|
48
|
+
*/
|
|
49
|
+
async finalize(id) {
|
|
50
|
+
const pending = await this.store.get(id);
|
|
51
|
+
if (!pending) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
await this.store.delete(id);
|
|
55
|
+
return pending;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Delete a pending message without finalizing.
|
|
59
|
+
* Used for cleanup on failure or cancellation.
|
|
60
|
+
*/
|
|
61
|
+
async delete(id) {
|
|
62
|
+
await this.store.delete(id);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Clean up stale pending messages.
|
|
66
|
+
* Called periodically to remove old failed/stuck records.
|
|
67
|
+
*
|
|
68
|
+
* @param conversationId - Conversation to clean up
|
|
69
|
+
* @param maxAgeMs - Maximum age in milliseconds (default: 24 hours)
|
|
70
|
+
* @returns Number of records cleaned up
|
|
71
|
+
*/
|
|
72
|
+
async cleanupStale(conversationId, maxAgeMs = 24 * 60 * 60 * 1000) {
|
|
73
|
+
const pending = await this.store.getByConversation(conversationId);
|
|
74
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
75
|
+
let cleaned = 0;
|
|
76
|
+
for (const p of pending) {
|
|
77
|
+
if (p.createdAt < cutoff) {
|
|
78
|
+
await this.store.delete(p.id);
|
|
79
|
+
cleaned++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return cleaned;
|
|
83
|
+
}
|
|
84
|
+
}
|