@wuwei-labs/srsly 4.3.1 → 4.4.2

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.
Files changed (93) hide show
  1. package/dist/cjs/audit/audit.js +400 -0
  2. package/dist/cjs/audit/audit.js.map +1 -0
  3. package/dist/cjs/audit/events.js +227 -0
  4. package/dist/cjs/audit/events.js.map +1 -0
  5. package/dist/cjs/audit/index.js +8 -0
  6. package/dist/cjs/audit/index.js.map +1 -0
  7. package/dist/cjs/audit/types.js +3 -0
  8. package/dist/cjs/audit/types.js.map +1 -0
  9. package/dist/cjs/generated/codama/types/feesPaid.js +33 -0
  10. package/dist/cjs/generated/codama/types/feesPaid.js.map +1 -0
  11. package/dist/cjs/generated/codama/types/fleet.js +0 -14
  12. package/dist/cjs/generated/codama/types/fleet.js.map +1 -1
  13. package/dist/cjs/generated/codama/types/index.js +3 -18
  14. package/dist/cjs/generated/codama/types/index.js.map +1 -1
  15. package/dist/cjs/generated/codama/types/ownerPaid.js +31 -0
  16. package/dist/cjs/generated/codama/types/ownerPaid.js.map +1 -0
  17. package/dist/cjs/generated/codama/types/rentalAccepted.js +8 -0
  18. package/dist/cjs/generated/codama/types/rentalAccepted.js.map +1 -1
  19. package/dist/cjs/generated/codama/types/rentalCancelled.js +6 -2
  20. package/dist/cjs/generated/codama/types/rentalCancelled.js.map +1 -1
  21. package/dist/cjs/generated/codama/types/rentalClosed.js +4 -2
  22. package/dist/cjs/generated/codama/types/rentalClosed.js.map +1 -1
  23. package/dist/cjs/generated/codama/types/rentalSettled.js +35 -0
  24. package/dist/cjs/generated/codama/types/rentalSettled.js.map +1 -0
  25. package/dist/cjs/generated/codama/types/shipStats.js +0 -2
  26. package/dist/cjs/generated/codama/types/shipStats.js.map +1 -1
  27. package/dist/cjs/idl/srsly.json +6748 -0
  28. package/dist/cjs/kit/index.js +2 -1
  29. package/dist/cjs/kit/index.js.map +1 -1
  30. package/dist/cjs/package.json +1 -1
  31. package/dist/cjs/version.js +1 -1
  32. package/dist/esm/audit/audit.js +397 -0
  33. package/dist/esm/audit/audit.js.map +1 -0
  34. package/dist/esm/audit/events.js +221 -0
  35. package/dist/esm/audit/events.js.map +1 -0
  36. package/dist/esm/audit/index.js +3 -0
  37. package/dist/esm/audit/index.js.map +1 -0
  38. package/dist/esm/audit/types.js +2 -0
  39. package/dist/esm/audit/types.js.map +1 -0
  40. package/dist/esm/generated/codama/types/feesPaid.js +28 -0
  41. package/dist/esm/generated/codama/types/feesPaid.js.map +1 -0
  42. package/dist/esm/generated/codama/types/fleet.js +1 -15
  43. package/dist/esm/generated/codama/types/fleet.js.map +1 -1
  44. package/dist/esm/generated/codama/types/index.js +3 -18
  45. package/dist/esm/generated/codama/types/index.js.map +1 -1
  46. package/dist/esm/generated/codama/types/ownerPaid.js +26 -0
  47. package/dist/esm/generated/codama/types/ownerPaid.js.map +1 -0
  48. package/dist/esm/generated/codama/types/rentalAccepted.js +9 -1
  49. package/dist/esm/generated/codama/types/rentalAccepted.js.map +1 -1
  50. package/dist/esm/generated/codama/types/rentalCancelled.js +6 -2
  51. package/dist/esm/generated/codama/types/rentalCancelled.js.map +1 -1
  52. package/dist/esm/generated/codama/types/rentalClosed.js +4 -2
  53. package/dist/esm/generated/codama/types/rentalClosed.js.map +1 -1
  54. package/dist/esm/generated/codama/types/rentalSettled.js +30 -0
  55. package/dist/esm/generated/codama/types/rentalSettled.js.map +1 -0
  56. package/dist/esm/generated/codama/types/shipStats.js +1 -3
  57. package/dist/esm/generated/codama/types/shipStats.js.map +1 -1
  58. package/dist/esm/idl/srsly.json +6748 -0
  59. package/dist/esm/kit/index.js +1 -0
  60. package/dist/esm/kit/index.js.map +1 -1
  61. package/dist/esm/package.json +1 -1
  62. package/dist/esm/version.js +1 -1
  63. package/dist/idl/srsly.json +122 -687
  64. package/dist/types/audit/audit.d.ts +10 -0
  65. package/dist/types/audit/audit.d.ts.map +1 -0
  66. package/dist/types/audit/events.d.ts +17 -0
  67. package/dist/types/audit/events.d.ts.map +1 -0
  68. package/dist/types/audit/index.d.ts +4 -0
  69. package/dist/types/audit/index.d.ts.map +1 -0
  70. package/dist/types/audit/types.d.ts +132 -0
  71. package/dist/types/audit/types.d.ts.map +1 -0
  72. package/dist/types/generated/codama/types/feesPaid.d.ts +28 -0
  73. package/dist/types/generated/codama/types/feesPaid.d.ts.map +1 -0
  74. package/dist/types/generated/codama/types/fleet.d.ts +0 -14
  75. package/dist/types/generated/codama/types/fleet.d.ts.map +1 -1
  76. package/dist/types/generated/codama/types/index.d.ts +3 -18
  77. package/dist/types/generated/codama/types/index.d.ts.map +1 -1
  78. package/dist/types/generated/codama/types/ownerPaid.d.ts +26 -0
  79. package/dist/types/generated/codama/types/ownerPaid.d.ts.map +1 -0
  80. package/dist/types/generated/codama/types/rentalAccepted.d.ts +12 -0
  81. package/dist/types/generated/codama/types/rentalAccepted.d.ts.map +1 -1
  82. package/dist/types/generated/codama/types/rentalCancelled.d.ts +10 -2
  83. package/dist/types/generated/codama/types/rentalCancelled.d.ts.map +1 -1
  84. package/dist/types/generated/codama/types/rentalClosed.d.ts +8 -2
  85. package/dist/types/generated/codama/types/rentalClosed.d.ts.map +1 -1
  86. package/dist/types/generated/codama/types/rentalSettled.d.ts +30 -0
  87. package/dist/types/generated/codama/types/rentalSettled.d.ts.map +1 -0
  88. package/dist/types/generated/codama/types/shipStats.d.ts +1 -3
  89. package/dist/types/generated/codama/types/shipStats.d.ts.map +1 -1
  90. package/dist/types/kit/index.d.ts +1 -0
  91. package/dist/types/kit/index.d.ts.map +1 -1
  92. package/dist/types/version.d.ts +1 -1
  93. package/package.json +8 -1
@@ -52,7 +52,7 @@ var __importStar = (this && this.__importStar) || (function () {
52
52
  };
53
53
  })();
54
54
  Object.defineProperty(exports, "__esModule", { value: true });
55
- exports.codama = exports.claim = exports.VERSION = void 0;
55
+ exports.audit = exports.codama = exports.claim = exports.VERSION = void 0;
56
56
  var version_1 = require("../version");
57
57
  Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.VERSION; } });
58
58
  __exportStar(require("../utils"), exports);
@@ -65,4 +65,5 @@ __exportStar(require("../cost"), exports);
65
65
  var slyvault_1 = require("@wuwei-labs/slyvault");
66
66
  Object.defineProperty(exports, "claim", { enumerable: true, get: function () { return slyvault_1.claim; } });
67
67
  exports.codama = __importStar(require("../generated/codama"));
68
+ exports.audit = __importStar(require("../audit"));
68
69
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/kit/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,sCAAqC;AAA5B,kGAAA,OAAO,OAAA;AAChB,2CAAyB;AACzB,kDAAgC;AAChC,8CAA4B;AAC5B,4CAA0B;AAC1B,yCAAuB;AACvB,0CAAwB;AAExB,sBAAsB;AACtB,iDAG8B;AAF5B,iGAAA,KAAK,OAAA;AAIP,8DAA8C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/kit/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,sCAAqC;AAA5B,kGAAA,OAAO,OAAA;AAChB,2CAAyB;AACzB,kDAAgC;AAChC,8CAA4B;AAC5B,4CAA0B;AAC1B,yCAAuB;AACvB,0CAAwB;AAExB,sBAAsB;AACtB,iDAG8B;AAF5B,iGAAA,KAAK,OAAA;AAIP,8DAA8C;AAC9C,kDAAkC"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wuwei-labs/srsly",
3
- "version": "4.3.1",
3
+ "version": "4.4.0",
4
4
  "description": "TypeScript SDK for SRSLY",
5
5
  "sideEffects": false,
6
6
  "publishConfig": {
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // Auto-generated from package.json - DO NOT EDIT
5
5
  // Run "pnpm run generate:version" to update
6
- exports.VERSION = '4.3.1';
6
+ exports.VERSION = '4.4.0';
7
7
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Contract audit — reconstructs the full payment lifecycle from on-chain events.
3
+ */
4
+ import { fetchContract } from '../accounts/contract';
5
+ import { getRpcUrl } from '../utils/config';
6
+ import { createRpc } from '../utils/rpc';
7
+ import { parseAnchorEvents } from './events';
8
+ const SRSLY_PROGRAM_ID = 'SRSLYxcFnjd5jG2DpJw4as6UEyjwJQK1U4J1TD1hvZH';
9
+ /**
10
+ * Fetch and reconstruct a contract's audit trail from transaction history.
11
+ */
12
+ export async function fetchContractAudit(rpcUrl, contract, options) {
13
+ const resolvedRpcUrl = rpcUrl || getRpcUrl();
14
+ const rpc = createRpc(resolvedRpcUrl);
15
+ // 1. Fetch on-chain contract state
16
+ const contractAccount = await fetchContract(contract, resolvedRpcUrl);
17
+ const onChain = {
18
+ totalEarned: contractAccount.data.totalEarned,
19
+ ownerClaimable: contractAccount.data.ownerClaimable,
20
+ feeClaimable: contractAccount.data.feeClaimable,
21
+ };
22
+ // 2. Fetch transaction signatures
23
+ const lastN = options?.all ? undefined : (options?.lastN ?? 10);
24
+ const sinceTimestamp = options?.since ? Math.floor(Date.now() / 1000) - options.since : undefined;
25
+ const log = options?.onProgress ?? (() => { });
26
+ const { events: allEvents, exhausted } = await fetchEventHistory(rpc, contract, resolvedRpcUrl, lastN, sinceTimestamp, log);
27
+ // 3. Group events into rental lifecycles
28
+ // Filter out in-progress rentals — incomplete data makes reconciliation meaningless
29
+ const allRentals = buildRentalAudits(allEvents)
30
+ .filter(r => r.status !== 'in-progress');
31
+ // Only consider full history when we scanned every signature (no cutoffs)
32
+ const isFullHistory = exhausted;
33
+ const rentals = lastN && allRentals.length > lastN
34
+ ? allRentals.slice(0, lastN)
35
+ : allRentals;
36
+ // 4. Compute summary
37
+ let ownerEarned = 0n;
38
+ let feeCollected = 0n;
39
+ let discount = 0n;
40
+ let refunds = 0n;
41
+ let ownerTransfers = 0n;
42
+ let allMatch = true;
43
+ for (const r of rentals) {
44
+ ownerEarned += r.totalOwnerEarned;
45
+ feeCollected += r.totalFeeCollected;
46
+ discount += r.totalDiscount;
47
+ refunds += r.totalRefund;
48
+ for (const t of r.ownerTransfers) {
49
+ ownerTransfers += t.amount;
50
+ }
51
+ if (r.reconciliation.status === 'mismatch')
52
+ allMatch = false;
53
+ }
54
+ return {
55
+ contract,
56
+ onChain,
57
+ isFullHistory,
58
+ rentals,
59
+ summary: {
60
+ ownerEarned,
61
+ feeCollected,
62
+ discount,
63
+ refunds,
64
+ ownerTransfers,
65
+ matchesTotalEarned: isFullHistory ? ownerEarned === onChain.totalEarned : null,
66
+ allRentalsMatch: allMatch,
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * Fetch all audit-relevant events from a contract's transaction history.
72
+ */
73
+ async function fetchEventHistory(rpc, contract, rpcUrl, maxRentals, sinceTimestamp, log) {
74
+ const allEvents = [];
75
+ let before;
76
+ let rentalCount = 0;
77
+ let txCount = 0;
78
+ let page = 0;
79
+ while (true) {
80
+ const params = { limit: 100 };
81
+ if (before)
82
+ params.before = before;
83
+ page++;
84
+ log(`Fetching signatures (page ${page})...`);
85
+ const signatures = await rpc.getSignaturesForAddress(contract, params).send();
86
+ if (!signatures || signatures.length === 0) {
87
+ log(`No more signatures found`);
88
+ break;
89
+ }
90
+ log(`Got ${signatures.length} signatures, parsing transactions...`);
91
+ for (const sigInfo of signatures) {
92
+ // Time-based cutoff
93
+ if (sinceTimestamp && sigInfo.blockTime && Number(sigInfo.blockTime) < sinceTimestamp) {
94
+ log(`Reached time cutoff after ${txCount} txs, ${allEvents.length} events, ${rentalCount} rentals`);
95
+ return { events: allEvents, exhausted: false };
96
+ }
97
+ // Skip failed txs
98
+ if (sigInfo.err)
99
+ continue;
100
+ txCount++;
101
+ const tx = await rpc.getTransaction(sigInfo.signature, {
102
+ encoding: 'json',
103
+ maxSupportedTransactionVersion: 0,
104
+ }).send();
105
+ if (!tx?.meta?.logMessages)
106
+ continue;
107
+ const events = parseAnchorEvents(tx.meta.logMessages, SRSLY_PROGRAM_ID, BigInt(tx.slot), sigInfo.signature);
108
+ for (const evt of events) {
109
+ allEvents.push(evt);
110
+ if (evt.type === 'RentalAccepted') {
111
+ rentalCount++;
112
+ log(`Found rental #${rentalCount} (${txCount} txs scanned, ${allEvents.length} events)`);
113
+ }
114
+ }
115
+ // Stop fetching once we have enough accepts to build N+1 lifecycles.
116
+ // The extra +1 ensures we don't cut off the Nth lifecycle's older events.
117
+ // The actual lastN trimming happens after grouping.
118
+ if (maxRentals && rentalCount > maxRentals) {
119
+ log(`Found ${rentalCount} rentals (need ${maxRentals}), ${txCount} txs scanned`);
120
+ return { events: allEvents, exhausted: false };
121
+ }
122
+ }
123
+ // Pagination
124
+ before = signatures[signatures.length - 1].signature;
125
+ }
126
+ log(`Done: ${txCount} txs scanned, ${allEvents.length} events, ${rentalCount} rentals`);
127
+ return { events: allEvents, exhausted: true };
128
+ }
129
+ /**
130
+ * Group events into per-rental audit records.
131
+ *
132
+ * Challenges:
133
+ * - Rental state PDAs are reused (reset on close) → same PDA, multiple lifecycles
134
+ * - Queued rentals get promoted to active PDA → RentalAccepted references queued PDA,
135
+ * but all subsequent events (settle/cancel/close) reference active PDA
136
+ *
137
+ * Strategy: match events by borrower (stable across PDA promotion).
138
+ * RentalSettled has no borrower — match by PDA, then fall back to the only
139
+ * open lifecycle without a close.
140
+ *
141
+ * OwnerPaid and FeesPaid are contract-level — associated with the most
142
+ * recently closed rental via temporal proximity.
143
+ */
144
+ function buildRentalAudits(events) {
145
+ // Process chronologically (events come newest-first from RPC)
146
+ const chronological = [...events].reverse();
147
+ // Track open lifecycles by borrower address (unique per contract at any time)
148
+ const openByBorrower = new Map();
149
+ // Secondary: track which PDA a lifecycle is currently on (for RentalSettled)
150
+ const pdaToBorrower = new Map();
151
+ const finalized = [];
152
+ const contractEvents = [];
153
+ for (const evt of chronological) {
154
+ switch (evt.type) {
155
+ case 'RentalAccepted': {
156
+ const borrower = evt.borrower;
157
+ const pda = evt.rentalState;
158
+ // If this borrower already has an open lifecycle, finalize it
159
+ const existing = openByBorrower.get(borrower);
160
+ if (existing?.accepted) {
161
+ finalized.push(existing);
162
+ }
163
+ // Start fresh lifecycle
164
+ const entry = {
165
+ accepted: evt,
166
+ settlements: [],
167
+ ownerTransfers: [],
168
+ };
169
+ openByBorrower.set(borrower, entry);
170
+ pdaToBorrower.set(pda, borrower);
171
+ break;
172
+ }
173
+ case 'RentalSettled': {
174
+ // RentalSettled has no borrower — resolve via PDA
175
+ const pda = evt.rentalState;
176
+ let borrower = pdaToBorrower.get(pda);
177
+ if (!borrower) {
178
+ // PDA changed (queued→active promotion). Find the only open
179
+ // lifecycle that hasn't been closed yet.
180
+ for (const [b, entry] of openByBorrower) {
181
+ if (entry.accepted && !entry.closed && !entry.cancelled) {
182
+ borrower = b;
183
+ // Update PDA mapping for future settles
184
+ pdaToBorrower.set(pda, b);
185
+ break;
186
+ }
187
+ }
188
+ }
189
+ if (borrower) {
190
+ const entry = openByBorrower.get(borrower);
191
+ if (entry)
192
+ (entry.settlements ??= []).push(evt);
193
+ }
194
+ break;
195
+ }
196
+ case 'RentalCancelled': {
197
+ const borrower = evt.borrower;
198
+ const entry = openByBorrower.get(borrower);
199
+ if (entry) {
200
+ entry.cancelled = evt;
201
+ // Update PDA mapping (may have been promoted)
202
+ pdaToBorrower.set(evt.rentalState, borrower);
203
+ }
204
+ break;
205
+ }
206
+ case 'RentalClosed': {
207
+ const borrower = evt.borrower;
208
+ const entry = openByBorrower.get(borrower);
209
+ if (entry) {
210
+ entry.closed = evt;
211
+ pdaToBorrower.set(evt.rentalState, borrower);
212
+ }
213
+ break;
214
+ }
215
+ case 'FeesPaid':
216
+ case 'OwnerPaid': {
217
+ contractEvents.push({ evt, slot: evt.slot });
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ // Finalize remaining open lifecycles
223
+ for (const entry of openByBorrower.values()) {
224
+ if (entry.accepted)
225
+ finalized.push(entry);
226
+ }
227
+ // Associate contract-level events with the nearest closed rental
228
+ const allWithCloseSlot = finalized
229
+ .filter(e => e.closed || e.cancelled)
230
+ .map(e => ({ entry: e, closeSlot: e.closed?.slot ?? e.cancelled.slot }))
231
+ .sort((a, b) => (a.closeSlot < b.closeSlot ? -1 : a.closeSlot > b.closeSlot ? 1 : 0));
232
+ for (const { evt, slot } of contractEvents) {
233
+ let best;
234
+ for (const { entry, closeSlot } of allWithCloseSlot) {
235
+ if (closeSlot <= slot)
236
+ best = entry;
237
+ else
238
+ break;
239
+ }
240
+ if (best) {
241
+ if (evt.type === 'FeesPaid') {
242
+ best.feesPaid = evt;
243
+ }
244
+ else {
245
+ (best.ownerTransfers ??= []).push(evt);
246
+ }
247
+ }
248
+ }
249
+ // Build final audits
250
+ const rentals = finalized
251
+ .filter(e => e.accepted)
252
+ .map(e => finalizeRental(e, []));
253
+ // Most recent first
254
+ rentals.sort((a, b) => {
255
+ const aTime = a.accepted.startTime;
256
+ const bTime = b.accepted.startTime;
257
+ return aTime > bTime ? -1 : aTime < bTime ? 1 : 0;
258
+ });
259
+ return rentals;
260
+ }
261
+ function finalizeRental(partial, _unassigned) {
262
+ const accepted = partial.accepted;
263
+ const settlements = partial.settlements ?? [];
264
+ const cancelled = partial.cancelled;
265
+ const closed = partial.closed;
266
+ const feesPaid = partial.feesPaid;
267
+ const ownerTransfers = partial.ownerTransfers ?? [];
268
+ // Derive status
269
+ let status = 'in-progress';
270
+ if (cancelled)
271
+ status = 'cancelled';
272
+ else if (closed)
273
+ status = 'completed';
274
+ // Compute dates (Solana timestamps are unix seconds)
275
+ const startTime = new Date(Number(accepted.startTime) * 1000);
276
+ const effectiveEndTime = cancelled ? cancelled.newEndTime : accepted.endTime;
277
+ const endTime = new Date(Number(effectiveEndTime) * 1000);
278
+ const durationSeconds = Number(effectiveEndTime - accepted.startTime);
279
+ // Sum actuals
280
+ let totalOwnerEarned = 0n;
281
+ let totalFeeCollected = 0n;
282
+ let totalRefund = 0n;
283
+ let totalDiscount = 0n;
284
+ for (const s of settlements) {
285
+ totalOwnerEarned += s.ownerEarned;
286
+ }
287
+ if (cancelled) {
288
+ totalOwnerEarned += cancelled.ownerEarned;
289
+ totalFeeCollected += cancelled.feeCollected;
290
+ totalRefund += cancelled.refund;
291
+ }
292
+ if (closed) {
293
+ totalOwnerEarned += closed.ownerEarned;
294
+ totalFeeCollected += closed.feeCollected;
295
+ }
296
+ if (feesPaid) {
297
+ totalDiscount += feesPaid.discount;
298
+ }
299
+ const reconciliation = computeReconciliation(accepted, totalOwnerEarned, totalFeeCollected, totalRefund, feesPaid);
300
+ return {
301
+ status,
302
+ borrower: accepted.borrower,
303
+ startTime,
304
+ endTime,
305
+ durationSeconds,
306
+ rate: accepted.rate,
307
+ feeBps: accepted.feeBps,
308
+ escrow: accepted.escrow,
309
+ serviceFee: accepted.serviceFee,
310
+ accepted,
311
+ settlements,
312
+ cancelled,
313
+ closed,
314
+ feesPaid,
315
+ ownerTransfers,
316
+ totalOwnerEarned,
317
+ totalFeeCollected,
318
+ totalDiscount,
319
+ totalRefund,
320
+ reconciliation,
321
+ };
322
+ }
323
+ /**
324
+ * Verify rental payment math e2e for all parties:
325
+ *
326
+ * Borrower:
327
+ * - Paid correct escrow (rate * duration / 86400)
328
+ * - Service fee correctly computed (escrow * fee_bps / 10000)
329
+ * - Refund + earned + fee = escrow (got back what they should)
330
+ * - Discount applied correctly if referrer present
331
+ *
332
+ * Owner:
333
+ * - Earned = escrow - fee - refund (conservation)
334
+ * - Fee ratio matches contracted fee_bps
335
+ *
336
+ * Protocol (slyvault):
337
+ * - Fee collected matches fee_bps proportion
338
+ * - Discount returned correctly to borrower
339
+ */
340
+ function computeReconciliation(accepted, actualOwnerEarned, actualFeeCollected, actualRefund, feesPaid) {
341
+ const escrow = accepted.escrow;
342
+ const duration = accepted.endTime - accepted.startTime;
343
+ // 1. Conservation: all escrow accounted for
344
+ const escrowAccounted = actualOwnerEarned + actualFeeCollected + actualRefund;
345
+ const escrowDelta = escrowAccounted - escrow;
346
+ // 2. Borrower paid correct amount: escrow should = rate * duration / 86400
347
+ const expectedEscrow = duration > 0n ? (accepted.rate * duration) / 86400n : 0n;
348
+ const escrowCorrect = abs(escrow - expectedEscrow) <= 1n;
349
+ // 3. Service fee correctly calculated: service_fee = escrow * fee_bps / 10000
350
+ const expectedServiceFee = (escrow * BigInt(accepted.feeBps)) / 10000n;
351
+ const serviceFeeCorrect = abs(accepted.serviceFee - expectedServiceFee) <= 1n;
352
+ // 4. Fee ratio on payout: fee / (earned + fee) should ≈ fee_bps / 10000
353
+ const grossPayment = actualOwnerEarned + actualFeeCollected;
354
+ const feeRatioBps = grossPayment > 0n
355
+ ? Number((actualFeeCollected * 10000n) / grossPayment)
356
+ : 0;
357
+ const expectedFeeBps = accepted.feeBps;
358
+ const feeRatioMatch = Math.abs(feeRatioBps - expectedFeeBps) <= 1;
359
+ // 5. Discount verification (if FeesPaid event with discount_bps > 0)
360
+ let discountVerified = null;
361
+ if (feesPaid && feesPaid.discountBps > 0) {
362
+ // discount should ≈ fee_collected * discount_bps / 10000
363
+ // (slyvault returns discount from its share)
364
+ const expectedDiscount = (feesPaid.feeCollected * BigInt(feesPaid.discountBps)) / 10000n;
365
+ discountVerified = abs(feesPaid.discount - expectedDiscount) <= 1n;
366
+ }
367
+ else if (feesPaid && feesPaid.discountBps === 0) {
368
+ // No discount expected — verify none given
369
+ discountVerified = feesPaid.discount === 0n;
370
+ }
371
+ const isMatch = abs(escrowDelta) <= 1n
372
+ && escrowCorrect
373
+ && serviceFeeCorrect
374
+ && feeRatioMatch
375
+ && (discountVerified === null || discountVerified);
376
+ return {
377
+ escrow,
378
+ escrowAccounted,
379
+ escrowDelta,
380
+ expectedEscrow,
381
+ escrowCorrect,
382
+ expectedServiceFee,
383
+ serviceFeeCorrect,
384
+ feeRatioBps,
385
+ expectedFeeBps,
386
+ feeRatioMatch,
387
+ discountVerified,
388
+ actualOwnerEarned,
389
+ actualFeeCollected,
390
+ actualRefund,
391
+ status: isMatch ? 'match' : 'mismatch',
392
+ };
393
+ }
394
+ function abs(n) {
395
+ return n < 0n ? -n : n;
396
+ }
397
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/audit/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Anchor event log parser for SRSLY audit events.
3
+ *
4
+ * Discriminators are read from the IDL (precomputed by Anchor's build).
5
+ * Borsh decoding is manual — no external borsh dependency needed.
6
+ */
7
+ import { getAddressDecoder } from '@solana/kit';
8
+ import idl from '../idl/srsly.json';
9
+ /** Convert byte array to hex string */
10
+ function toHex(bytes) {
11
+ return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
12
+ }
13
+ // Build discriminator → event name map from IDL
14
+ const DISC_MAP = new Map();
15
+ for (const evt of idl.events ?? []) {
16
+ const hex = toHex(evt.discriminator);
17
+ DISC_MAP.set(hex, evt.name);
18
+ }
19
+ const addressDecoder = getAddressDecoder();
20
+ /** Decode base64 string to Uint8Array (browser + Node compatible via atob) */
21
+ function decodeBase64(str) {
22
+ const binary = atob(str);
23
+ const bytes = new Uint8Array(binary.length);
24
+ for (let i = 0; i < binary.length; i++)
25
+ bytes[i] = binary.charCodeAt(i);
26
+ return bytes;
27
+ }
28
+ // ── Borsh deserialization helpers ──
29
+ function readAddress(buf, offset) {
30
+ const bytes = buf.slice(offset, offset + 32);
31
+ const address = addressDecoder.decode(bytes);
32
+ return [address, offset + 32];
33
+ }
34
+ function readU64(buf, offset) {
35
+ const lo = buf.getUint32(offset, true);
36
+ const hi = buf.getUint32(offset + 4, true);
37
+ return [BigInt(lo) + (BigInt(hi) << 32n), offset + 8];
38
+ }
39
+ function readI64(buf, offset) {
40
+ const value = buf.getBigInt64(offset, true);
41
+ return [value, offset + 8];
42
+ }
43
+ function readU16(buf, offset) {
44
+ return [buf.getUint16(offset, true), offset + 2];
45
+ }
46
+ function readBool(buf, offset) {
47
+ return [buf.getUint8(offset) !== 0, offset + 1];
48
+ }
49
+ // ── Event decoders ──
50
+ function decodeRentalAccepted(raw, dv, offset, slot, signature) {
51
+ let rentalState, contract, borrower;
52
+ let escrow, rate, serviceFee;
53
+ let feeBps;
54
+ let startTime, endTime;
55
+ [rentalState, offset] = readAddress(raw, offset);
56
+ [contract, offset] = readAddress(raw, offset);
57
+ [borrower, offset] = readAddress(raw, offset);
58
+ [escrow, offset] = readU64(dv, offset);
59
+ [rate, offset] = readU64(dv, offset);
60
+ [serviceFee, offset] = readU64(dv, offset);
61
+ [feeBps, offset] = readU16(dv, offset);
62
+ [startTime, offset] = readI64(dv, offset);
63
+ [endTime, offset] = readI64(dv, offset);
64
+ return {
65
+ type: 'RentalAccepted', rentalState, contract, borrower,
66
+ escrow, rate, serviceFee, feeBps, startTime, endTime, slot, signature,
67
+ };
68
+ }
69
+ function decodeRentalSettled(raw, dv, offset, slot, signature) {
70
+ let rentalState, contract;
71
+ let ownerEarned, escrowRemaining;
72
+ let isFinal;
73
+ [rentalState, offset] = readAddress(raw, offset);
74
+ [contract, offset] = readAddress(raw, offset);
75
+ [ownerEarned, offset] = readU64(dv, offset);
76
+ [escrowRemaining, offset] = readU64(dv, offset);
77
+ [isFinal, offset] = readBool(dv, offset);
78
+ return {
79
+ type: 'RentalSettled', rentalState, contract,
80
+ ownerEarned, escrowRemaining, isFinal, slot, signature,
81
+ };
82
+ }
83
+ function decodeRentalCancelled(raw, dv, offset, slot, signature) {
84
+ let rentalState, contract, borrower;
85
+ let ownerEarned, feeCollected, refund;
86
+ let newEndTime;
87
+ [rentalState, offset] = readAddress(raw, offset);
88
+ [contract, offset] = readAddress(raw, offset);
89
+ [borrower, offset] = readAddress(raw, offset);
90
+ [ownerEarned, offset] = readU64(dv, offset);
91
+ [feeCollected, offset] = readU64(dv, offset);
92
+ [refund, offset] = readU64(dv, offset);
93
+ [newEndTime, offset] = readI64(dv, offset);
94
+ return {
95
+ type: 'RentalCancelled', rentalState, contract, borrower,
96
+ ownerEarned, feeCollected, refund, newEndTime, slot, signature,
97
+ };
98
+ }
99
+ function decodeRentalClosed(raw, dv, offset, slot, signature) {
100
+ let rentalState, contract, borrower;
101
+ let ownerEarned, feeCollected, pointsAwarded;
102
+ [rentalState, offset] = readAddress(raw, offset);
103
+ [contract, offset] = readAddress(raw, offset);
104
+ [borrower, offset] = readAddress(raw, offset);
105
+ [ownerEarned, offset] = readU64(dv, offset);
106
+ [feeCollected, offset] = readU64(dv, offset);
107
+ [pointsAwarded, offset] = readU64(dv, offset);
108
+ return {
109
+ type: 'RentalClosed', rentalState, contract, borrower,
110
+ ownerEarned, feeCollected, pointsAwarded, slot, signature,
111
+ };
112
+ }
113
+ function decodeOwnerPaid(raw, dv, offset, slot, signature) {
114
+ let contract, owner;
115
+ let amount;
116
+ [contract, offset] = readAddress(raw, offset);
117
+ [owner, offset] = readAddress(raw, offset);
118
+ [amount, offset] = readU64(dv, offset);
119
+ return {
120
+ type: 'OwnerPaid', contract, owner, amount, slot, signature,
121
+ };
122
+ }
123
+ function decodeFeesPaid(raw, dv, offset, slot, signature) {
124
+ let contract;
125
+ let feeCollected, discount;
126
+ let discountBps;
127
+ [contract, offset] = readAddress(raw, offset);
128
+ [feeCollected, offset] = readU64(dv, offset);
129
+ [discount, offset] = readU64(dv, offset);
130
+ [discountBps, offset] = readU16(dv, offset);
131
+ return {
132
+ type: 'FeesPaid', contract, feeCollected, discount, discountBps, slot, signature,
133
+ };
134
+ }
135
+ // Sanity check: timestamps should be between 2024 and 2030
136
+ const MIN_TS = 1704067200n; // 2024-01-01
137
+ const MAX_TS = 1893456000n; // 2030-01-01
138
+ function isPlausibleEvent(event) {
139
+ if (event.type === 'RentalAccepted') {
140
+ return event.startTime >= MIN_TS && event.startTime <= MAX_TS
141
+ && event.endTime >= MIN_TS && event.endTime <= MAX_TS
142
+ && event.endTime > event.startTime;
143
+ }
144
+ if (event.type === 'RentalCancelled') {
145
+ return event.newEndTime >= MIN_TS && event.newEndTime <= MAX_TS;
146
+ }
147
+ // For other events, check amounts aren't astronomically large (> 1 trillion stardust = 10k ATLAS)
148
+ if (event.type === 'RentalSettled') {
149
+ return event.ownerEarned < 1000000000000000n;
150
+ }
151
+ return true;
152
+ }
153
+ const DECODERS = {
154
+ RentalAccepted: decodeRentalAccepted,
155
+ RentalSettled: decodeRentalSettled,
156
+ RentalCancelled: decodeRentalCancelled,
157
+ RentalClosed: decodeRentalClosed,
158
+ OwnerPaid: decodeOwnerPaid,
159
+ FeesPaid: decodeFeesPaid,
160
+ };
161
+ /**
162
+ * Parse Anchor events from transaction log messages.
163
+ *
164
+ * Anchor emits events as `Program data: <base64>` log lines where the data
165
+ * starts with an 8-byte discriminator followed by borsh-serialized fields.
166
+ *
167
+ * Discriminators are matched against the IDL — no runtime hashing needed.
168
+ */
169
+ export function parseAnchorEvents(logs, programId, slot, signature) {
170
+ const events = [];
171
+ // Track execution context as a stack of program IDs.
172
+ // `Program data:` lines belong to the program on top of the stack.
173
+ // This correctly handles CPI chains (thread → SRSLY → token → ...).
174
+ const execStack = [];
175
+ const invokeRe = /^Program (\w+) invoke \[(\d+)\]$/;
176
+ for (const log of logs) {
177
+ const invokeMatch = log.match(invokeRe);
178
+ if (invokeMatch) {
179
+ execStack.push(invokeMatch[1]);
180
+ continue;
181
+ }
182
+ if (log.startsWith('Program ') && (log.endsWith(' success') || log.endsWith(' failed'))) {
183
+ execStack.pop();
184
+ continue;
185
+ }
186
+ // Only parse `Program data:` when our program is on top of the stack
187
+ const currentProgram = execStack[execStack.length - 1];
188
+ if (currentProgram !== programId || !log.startsWith('Program data: '))
189
+ continue;
190
+ const base64Data = log.slice('Program data: '.length);
191
+ let raw;
192
+ try {
193
+ raw = decodeBase64(base64Data);
194
+ }
195
+ catch {
196
+ continue;
197
+ }
198
+ if (raw.length < 8)
199
+ continue;
200
+ const discHex = toHex(raw.slice(0, 8));
201
+ const eventName = DISC_MAP.get(discHex);
202
+ if (!eventName)
203
+ continue;
204
+ const decoder = DECODERS[eventName];
205
+ if (!decoder)
206
+ continue;
207
+ try {
208
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
209
+ const event = decoder(raw, dv, 8, slot, signature);
210
+ // Validate: reject events from old program versions with mismatched layouts
211
+ if (!isPlausibleEvent(event))
212
+ continue;
213
+ events.push(event);
214
+ }
215
+ catch {
216
+ // Skip malformed events
217
+ }
218
+ }
219
+ return events;
220
+ }
221
+ //# sourceMappingURL=events.js.map