@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/dist/esm/src/verify.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// packages/sdk/src/verify.ts
|
|
2
2
|
import { getBytes, hexlify, getAddress } from "ethers";
|
|
3
|
-
import { decryptAndExtractHandshakeKeys,
|
|
3
|
+
import { decryptAndExtractHandshakeKeys, computeHybridTagFromInitiator } from "./crypto.js";
|
|
4
|
+
import { kem } from "./pq/kem.js";
|
|
4
5
|
import { parseHandshakePayload, parseHandshakeKeys } from "./payload.js";
|
|
5
6
|
import { makeViemPublicClient, parseBindingMessage, } from "./utils.js";
|
|
6
7
|
// ============= Handshake Verification =============
|
|
7
8
|
/**
|
|
8
9
|
* handshake verification with mandatory identity proof
|
|
9
10
|
*/
|
|
10
|
-
export async function verifyHandshakeIdentity(handshakeEvent, provider) {
|
|
11
|
+
export async function verifyHandshakeIdentity(handshakeEvent, provider, ctx) {
|
|
11
12
|
try {
|
|
12
13
|
let plaintextPayload = handshakeEvent.plaintextPayload;
|
|
13
14
|
if (typeof plaintextPayload === "string" &&
|
|
@@ -27,13 +28,7 @@ export async function verifyHandshakeIdentity(handshakeEvent, provider) {
|
|
|
27
28
|
console.error("Failed to parse unified pubKeys from handshake event");
|
|
28
29
|
return false;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
// const dp: any = content.identityProof;
|
|
32
|
-
// const sigPrimary: string = dp.signature;
|
|
33
|
-
// const sig6492: string | undefined = dp.signature6492 ?? dp.erc6492;
|
|
34
|
-
// const uses6492 = hasERC6492Suffix(sigPrimary) || !!sig6492;
|
|
35
|
-
// const isContract1271 = await isSmartContract1271(handshakeEvent.sender, provider);
|
|
36
|
-
return await verifyIdentityProof(content.identityProof, handshakeEvent.sender, parsedKeys, provider);
|
|
31
|
+
return await verifyIdentityProof(content.identityProof, handshakeEvent.sender, parsedKeys, provider, ctx);
|
|
37
32
|
}
|
|
38
33
|
catch (err) {
|
|
39
34
|
console.error("verifyHandshakeIdentity error:", err);
|
|
@@ -44,7 +39,7 @@ export async function verifyHandshakeIdentity(handshakeEvent, provider) {
|
|
|
44
39
|
/**
|
|
45
40
|
* handshake response verification with mandatory identity proof
|
|
46
41
|
*/
|
|
47
|
-
export async function verifyHandshakeResponseIdentity(responseEvent, responderIdentityPubKey, initiatorEphemeralSecretKey, provider) {
|
|
42
|
+
export async function verifyHandshakeResponseIdentity(responseEvent, responderIdentityPubKey, initiatorEphemeralSecretKey, provider, ctx) {
|
|
48
43
|
try {
|
|
49
44
|
const extractedResponse = decryptAndExtractHandshakeKeys(responseEvent.ciphertext, initiatorEphemeralSecretKey);
|
|
50
45
|
if (!extractedResponse) {
|
|
@@ -55,21 +50,16 @@ export async function verifyHandshakeResponseIdentity(responseEvent, responderId
|
|
|
55
50
|
console.error("Identity public key mismatch in handshake response");
|
|
56
51
|
return false;
|
|
57
52
|
}
|
|
58
|
-
// 6492 awareness
|
|
59
53
|
const dpAny = extractedResponse.identityProof;
|
|
60
54
|
if (!dpAny) {
|
|
61
55
|
console.error("Missing identityProof in handshake response payload");
|
|
62
56
|
return false;
|
|
63
57
|
}
|
|
64
|
-
// const sigPrimary: string = dpAny.signature;
|
|
65
|
-
// const sig6492: string | undefined = dpAny.signature6492 ?? dpAny.erc6492;
|
|
66
|
-
// const uses6492 = hasERC6492Suffix(sigPrimary) || !!sig6492;
|
|
67
|
-
// const isContract1271 = await isSmartContract1271(responseEvent.responder,provider);
|
|
68
58
|
const expectedKeys = {
|
|
69
59
|
identityPubKey: extractedResponse.identityPubKey,
|
|
70
60
|
signingPubKey: extractedResponse.signingPubKey,
|
|
71
61
|
};
|
|
72
|
-
return await verifyIdentityProof(extractedResponse.identityProof, responseEvent.responder, expectedKeys, provider);
|
|
62
|
+
return await verifyIdentityProof(extractedResponse.identityProof, responseEvent.responder, expectedKeys, provider, ctx);
|
|
73
63
|
}
|
|
74
64
|
catch (err) {
|
|
75
65
|
console.error("verifyHandshakeResponseIdentity error:", err);
|
|
@@ -77,31 +67,36 @@ export async function verifyHandshakeResponseIdentity(responseEvent, responderId
|
|
|
77
67
|
}
|
|
78
68
|
}
|
|
79
69
|
/**
|
|
80
|
-
* Verify
|
|
70
|
+
* Verify IdentityProof for EOAs and smart accounts.
|
|
81
71
|
* - Verifies the signature with viem (EOA / ERC-1271 / ERC-6492).
|
|
82
72
|
* - Parses and checks the expected address and public key against the message content.
|
|
83
73
|
*/
|
|
84
|
-
export async function verifyIdentityProof(identityProof,
|
|
74
|
+
export async function verifyIdentityProof(identityProof, address, expectedUnifiedKeys, provider, ctx) {
|
|
85
75
|
try {
|
|
86
76
|
const client = await makeViemPublicClient(provider);
|
|
87
|
-
const
|
|
77
|
+
const inputAddress = address;
|
|
78
|
+
const parsed = parseBindingMessage(identityProof.message);
|
|
79
|
+
if (!parsed.address) {
|
|
80
|
+
console.error("Parsed address is undefined");
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const signerAddress = getAddress(parsed.address);
|
|
88
84
|
const okSig = await client.verifyMessage({
|
|
89
|
-
address,
|
|
85
|
+
address: signerAddress,
|
|
90
86
|
message: identityProof.message,
|
|
91
87
|
signature: identityProof.signature,
|
|
92
88
|
});
|
|
93
89
|
if (!okSig) {
|
|
94
|
-
console.error("Binding signature invalid for address");
|
|
90
|
+
console.error("Binding signature invalid for signer address");
|
|
95
91
|
return false;
|
|
96
92
|
}
|
|
97
|
-
const parsed = parseBindingMessage(identityProof.message);
|
|
98
93
|
if (parsed.header && parsed.header !== "VerbEth Key Binding v1") {
|
|
99
94
|
console.error("Unexpected binding header:", parsed.header);
|
|
100
95
|
return false;
|
|
101
96
|
}
|
|
102
|
-
if (!parsed.
|
|
103
|
-
getAddress(parsed.
|
|
104
|
-
console.error("Binding message address mismatch");
|
|
97
|
+
if (!parsed.executorSafeAddress ||
|
|
98
|
+
getAddress(parsed.executorSafeAddress) !== getAddress(inputAddress)) {
|
|
99
|
+
console.error("Binding message Safe address mismatch");
|
|
105
100
|
return false;
|
|
106
101
|
}
|
|
107
102
|
const expectedPkX = hexlify(expectedUnifiedKeys.identityPubKey);
|
|
@@ -122,7 +117,19 @@ export async function verifyIdentityProof(identityProof, smartAccountAddress, ex
|
|
|
122
117
|
console.error("Unexpected version:", parsed.version);
|
|
123
118
|
return false;
|
|
124
119
|
}
|
|
125
|
-
//
|
|
120
|
+
// anti replay cross chain or cross dapp:
|
|
121
|
+
if (typeof ctx?.chainId === "number") {
|
|
122
|
+
if (typeof parsed.chainId !== "number" || parsed.chainId !== ctx.chainId) {
|
|
123
|
+
console.error("ChainId mismatch");
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (ctx?.rpId) {
|
|
128
|
+
if (!parsed.rpId || parsed.rpId !== ctx.rpId) {
|
|
129
|
+
console.error("RpId mismatch");
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
126
133
|
return true;
|
|
127
134
|
}
|
|
128
135
|
catch (err) {
|
|
@@ -131,8 +138,8 @@ export async function verifyIdentityProof(identityProof, smartAccountAddress, ex
|
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
// ============= Utility Functions =============
|
|
134
|
-
export async function verifyAndExtractHandshakeKeys(handshakeEvent, provider) {
|
|
135
|
-
const isValid = await verifyHandshakeIdentity(handshakeEvent, provider);
|
|
141
|
+
export async function verifyAndExtractHandshakeKeys(handshakeEvent, provider, ctx) {
|
|
142
|
+
const isValid = await verifyHandshakeIdentity(handshakeEvent, provider, ctx);
|
|
136
143
|
if (!isValid) {
|
|
137
144
|
return { isValid: false };
|
|
138
145
|
}
|
|
@@ -145,17 +152,23 @@ export async function verifyAndExtractHandshakeKeys(handshakeEvent, provider) {
|
|
|
145
152
|
keys: parsedKeys,
|
|
146
153
|
};
|
|
147
154
|
}
|
|
148
|
-
export async function verifyAndExtractHandshakeResponseKeys(responseEvent, initiatorEphemeralSecretKey, provider) {
|
|
149
|
-
|
|
150
|
-
const expectedTag = computeTagFromInitiator(initiatorEphemeralSecretKey, Rbytes);
|
|
151
|
-
if (expectedTag !== responseEvent.inResponseTo) {
|
|
152
|
-
return { isValid: false };
|
|
153
|
-
}
|
|
155
|
+
export async function verifyAndExtractHandshakeResponseKeys(responseEvent, initiatorEphemeralSecretKey, initiatorKemSecretKey, provider, ctx) {
|
|
156
|
+
// Decrypt first to get kemCiphertext
|
|
154
157
|
const extractedResponse = decryptAndExtractHandshakeKeys(responseEvent.ciphertext, initiatorEphemeralSecretKey);
|
|
155
158
|
if (!extractedResponse) {
|
|
156
159
|
return { isValid: false };
|
|
157
160
|
}
|
|
158
|
-
|
|
161
|
+
if (!extractedResponse.kemCiphertext) {
|
|
162
|
+
return { isValid: false };
|
|
163
|
+
}
|
|
164
|
+
// Decapsulate and verify hybrid tag
|
|
165
|
+
const Rbytes = getBytes(responseEvent.responderEphemeralR);
|
|
166
|
+
const kemSecret = kem.decapsulate(extractedResponse.kemCiphertext, initiatorKemSecretKey);
|
|
167
|
+
const expectedTag = computeHybridTagFromInitiator(initiatorEphemeralSecretKey, Rbytes, kemSecret);
|
|
168
|
+
if (expectedTag !== responseEvent.inResponseTo) {
|
|
169
|
+
return { isValid: false };
|
|
170
|
+
}
|
|
171
|
+
const isValid = await verifyHandshakeResponseIdentity(responseEvent, extractedResponse.identityPubKey, initiatorEphemeralSecretKey, provider, ctx);
|
|
159
172
|
if (!isValid) {
|
|
160
173
|
return { isValid: false };
|
|
161
174
|
}
|
|
@@ -165,22 +178,8 @@ export async function verifyAndExtractHandshakeResponseKeys(responseEvent, initi
|
|
|
165
178
|
identityPubKey: extractedResponse.identityPubKey,
|
|
166
179
|
signingPubKey: extractedResponse.signingPubKey,
|
|
167
180
|
ephemeralPubKey: extractedResponse.ephemeralPubKey,
|
|
181
|
+
kemCiphertext: extractedResponse.kemCiphertext,
|
|
168
182
|
note: extractedResponse.note,
|
|
169
183
|
},
|
|
170
184
|
};
|
|
171
185
|
}
|
|
172
|
-
/**
|
|
173
|
-
* Verify and derive duplex topics from a long-term DH secret.
|
|
174
|
-
* - Accepts either `tag` (inResponseTo) or a raw salt as KDF input.
|
|
175
|
-
* - Recomputes topicOut/topicIn deterministically from the identity DH.
|
|
176
|
-
* - If topicInfo is provided (from HSR), also verify the checksum.
|
|
177
|
-
* - Used by the initiator after decrypting a HandshakeResponse to confirm responder’s topics.
|
|
178
|
-
*/
|
|
179
|
-
export function verifyDerivedDuplexTopics({ myIdentitySecretKey, theirIdentityPubKey, tag, salt, topicInfo }) {
|
|
180
|
-
const s = salt ?? (tag ? getBytes(tag) : undefined);
|
|
181
|
-
if (!s)
|
|
182
|
-
throw new Error("Provide either salt or inResponseTo");
|
|
183
|
-
const { topicOut, topicIn, checksum } = deriveDuplexTopics(myIdentitySecretKey, theirIdentityPubKey, s);
|
|
184
|
-
const ok = topicInfo ? verifyDuplexTopicsChecksum(topicOut, topicIn, topicInfo.chk) : undefined;
|
|
185
|
-
return { topics: { topicOut, topicIn }, ok };
|
|
186
|
-
}
|
|
@@ -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
|
+
}
|