77
88import {
99 Sati ,
10- SATI_PROGRAM_ADDRESS ,
1110 type AgentIdentity ,
1211 type ParsedAttestation ,
1312 type ParsedValidationAttestation ,
@@ -134,6 +133,11 @@ export function getFeedbackSchemaType(feedback: ParsedFeedback): FeedbackSchemaT
134133 return "unknown" ;
135134}
136135
136+ /** Sort attestations by createdAt descending (newest first) */
137+ function sortByCreatedAtDesc < T extends { createdAt ?: number } > ( items : T [ ] ) : T [ ] {
138+ return items . sort ( ( a , b ) => ( b . createdAt ?? 0 ) - ( a . createdAt ?? 0 ) ) ;
139+ }
140+
137141/**
138142 * Parsed feedback from SDK - re-exported for convenience
139143 */
@@ -159,7 +163,7 @@ export async function listAgentFeedbacks(agentMint: Address): Promise<ParsedFeed
159163 allFeedbacks . push ( ...result . items ) ;
160164 }
161165
162- return allFeedbacks ;
166+ return sortByCreatedAtDesc ( allFeedbacks ) ;
163167 } catch ( e ) {
164168 console . error ( "Failed to list agent feedbacks:" , e ) ;
165169 return [ ] ;
@@ -179,7 +183,7 @@ export async function listAgentValidations(agentMint: Address): Promise<ParsedVa
179183
180184 try {
181185 const result = await sati . listValidations ( { sasSchema : validationSchema , agentMint } ) ;
182- return result . items ;
186+ return sortByCreatedAtDesc ( result . items ) ;
183187 } catch ( e ) {
184188 console . error ( "Failed to list agent validations:" , e ) ;
185189 return [ ] ;
@@ -207,7 +211,7 @@ export async function listAllFeedbacks(): Promise<ParsedFeedback[]> {
207211 allFeedbacks . push ( ...result . items ) ;
208212 }
209213
210- return allFeedbacks ;
214+ return sortByCreatedAtDesc ( allFeedbacks ) ;
211215 } catch ( e ) {
212216 console . error ( "Failed to list all feedbacks:" , e ) ;
213217 return [ ] ;
@@ -218,10 +222,18 @@ export async function listAllFeedbacks(): Promise<ParsedFeedback[]> {
218222 * List feedbacks submitted by a specific counterparty
219223 */
220224export async function listFeedbacksByCounterparty ( counterparty : Address ) : Promise < ParsedFeedback [ ] > {
221- // Counterparty is in the schema data, filter client-side
225+ const sati = getSatiClient ( ) ;
226+ const { feedback, feedbackPublic } = getFeedbackSchemas ( ) ;
227+ const schemas = [ feedback , feedbackPublic ] . filter ( Boolean ) as Address [ ] ;
228+ if ( schemas . length === 0 ) return [ ] ;
229+
222230 try {
223- const allFeedbacks = await listAllFeedbacks ( ) ;
224- return allFeedbacks . filter ( ( f ) => f . data . counterparty === counterparty ) ;
231+ const all : ParsedFeedback [ ] = [ ] ;
232+ for ( const sasSchema of schemas ) {
233+ const result = await sati . listFeedbacks ( { sasSchema, counterparty } ) ;
234+ all . push ( ...result . items ) ;
235+ }
236+ return sortByCreatedAtDesc ( all ) ;
225237 } catch ( e ) {
226238 console . error ( "Failed to list feedbacks by counterparty:" , e ) ;
227239 return [ ] ;
@@ -295,44 +307,21 @@ export function formatSlotTime(slot: bigint, currentSlot: bigint): string {
295307 */
296308export async function getCurrentSlot ( ) : Promise < bigint > {
297309 try {
298- const rpcUrl = getCurrentRpcUrl ( ) ;
299- const response = await fetch ( rpcUrl , {
300- method : "POST" ,
301- headers : { "Content-Type" : "application/json" } ,
302- body : JSON . stringify ( {
303- jsonrpc : "2.0" ,
304- id : "get-slot" ,
305- method : "getSlot" ,
306- params : [ { commitment : "confirmed" } ] ,
307- } ) ,
308- } ) ;
309- const data = await response . json ( ) ;
310- return BigInt ( data . result ?? 0 ) ;
310+ const sati = getSatiClient ( ) ;
311+ const rpc = sati . getRpc ( ) ;
312+ const slot = await rpc . getSlot ( { commitment : "confirmed" } ) . send ( ) ;
313+ return slot ;
311314 } catch {
312315 return 0n ;
313316 }
314317}
315318
316- /**
317- * Get deployed reputation score schema address (always fresh for current network)
318- */
319- export function getReputationScoreSchema ( ) : Address | undefined {
320- return getSatiClient ( ) . deployedConfig ?. schemas ?. reputationScore as Address | undefined ;
321- }
322-
323- /**
324- * Get deployed SATI credential address (always fresh for current network)
325- */
326- export function getSatiCredential ( ) : Address | undefined {
327- return getSatiClient ( ) . deployedConfig ?. credential as Address | undefined ;
328- }
329-
330319/**
331320 * List reputation scores for a specific agent.
332321 */
333322export async function listAgentReputationScores ( agentMint : Address ) : Promise < ReputationScoreData [ ] > {
334323 const sati = getSatiClient ( ) ;
335- const reputationSchema = getReputationScoreSchema ( ) ;
324+ const reputationSchema = sati . reputationScoreSchema ;
336325
337326 if ( ! reputationSchema ) {
338327 return [ ] ;
@@ -349,121 +338,13 @@ export async function listAgentReputationScores(agentMint: Address): Promise<Rep
349338// Re-export getSolscanUrl from network module
350339export { getSolscanUrl } from "./network" ;
351340
352- /**
353- * Helius getTransactionsForAddress response types
354- */
355- interface HeliusTransaction {
356- transaction : {
357- message : {
358- accountKeys : Array < { pubkey : string ; signer : boolean ; writable : boolean } | string > ;
359- } ;
360- } ;
361- }
362-
363- interface HeliusResponse {
364- result ?: {
365- data ?: HeliusTransaction [ ] ;
366- } ;
367- error ?: { message : string } ;
368- }
369-
370341/**
371342 * List all agents registered in the SATI registry.
372- *
373- * Uses Helius getTransactionsForAddress to discover mints, then SDK's registry.load
374- * for proper ownership resolution (owner = current token holder, not registrant).
343+ * Uses SDK's AgentIndex-based enumeration for reliable discovery.
375344 */
376345export async function listAllAgents ( params ?: { offset ?: number ; limit ?: number } ) : Promise < ListAgentsResult > {
377- const { offset = 0 , limit = 20 } = params ?? { } ;
378-
379346 const sati = getSatiClient ( ) ;
380- const rpcUrl = getCurrentRpcUrl ( ) ;
381- const stats = await sati . getRegistryStats ( ) ;
382- const groupMint = stats . groupMint ;
383-
384- try {
385- // Step 1: Discover mints via Helius getTransactionsForAddress
386- const response = await fetch ( rpcUrl , {
387- method : "POST" ,
388- headers : { "Content-Type" : "application/json" } ,
389- body : JSON . stringify ( {
390- jsonrpc : "2.0" ,
391- id : "list-agents" ,
392- method : "getTransactionsForAddress" ,
393- params : [
394- SATI_PROGRAM_ADDRESS ,
395- {
396- transactionDetails : "full" ,
397- encoding : "jsonParsed" ,
398- limit : 100 ,
399- sortOrder : "desc" ,
400- filters : { status : "succeeded" } ,
401- } ,
402- ] ,
403- } ) ,
404- } ) ;
405-
406- const data : HeliusResponse = await response . json ( ) ;
407-
408- if ( data . error ) {
409- console . warn ( "Helius getTransactionsForAddress error:" , data . error . message ) ;
410- return { agents : [ ] , totalAgents : stats . totalAgents } ;
411- }
412-
413- const transactions = data . result ?. data ?? [ ] ;
414-
415- if ( transactions . length === 0 ) {
416- return { agents : [ ] , totalAgents : stats . totalAgents } ;
417- }
418-
419- // Step 2: Extract agent mints from registerAgent transactions
420- const agentMints : string [ ] = [ ] ;
421-
422- for ( const tx of transactions ) {
423- if ( ! tx ?. transaction ?. message ) continue ;
424-
425- const accounts = tx . transaction . message . accountKeys ;
426- const pubkeys = accounts . map ( ( acc ) => ( typeof acc === "object" && "pubkey" in acc ? acc . pubkey : String ( acc ) ) ) ;
427-
428- // Check if this transaction involves the group mint (indicates registerAgent)
429- if ( ! pubkeys . includes ( groupMint ) ) continue ;
430-
431- // Find the agent mint - it's the second signer (first is payer)
432- const signerAccounts = accounts . filter ( ( acc ) => typeof acc === "object" && "signer" in acc && acc . signer ) ;
433-
434- if ( signerAccounts . length >= 2 ) {
435- const agentMint =
436- typeof signerAccounts [ 1 ] === "object" && "pubkey" in signerAccounts [ 1 ] ? signerAccounts [ 1 ] . pubkey : null ;
437-
438- // Skip if this is the groupMint itself (from initialize transaction)
439- if ( agentMint && agentMint !== groupMint && ! agentMints . includes ( agentMint ) ) {
440- agentMints . push ( agentMint ) ;
441- }
442- }
443- }
444-
445- if ( agentMints . length === 0 ) {
446- return { agents : [ ] , totalAgents : stats . totalAgents } ;
447- }
448-
449- // Step 3: Apply pagination
450- const paginatedMints = agentMints . slice ( offset , offset + limit ) ;
451-
452- // Step 4: Load agents via SDK (handles owner lookup correctly)
453- const agentPromises = paginatedMints . map ( ( mint ) => sati . loadAgent ( mint as Address ) ) ;
454- const loadedAgents = await Promise . all ( agentPromises ) ;
455-
456- // Filter out nulls (failed loads)
457- const agents = loadedAgents . filter ( ( agent ) : agent is AgentIdentity => agent !== null ) ;
458-
459- return {
460- agents,
461- totalAgents : stats . totalAgents ,
462- } ;
463- } catch ( error ) {
464- console . warn ( "Failed to fetch agents:" , error ) ;
465- return { agents : [ ] , totalAgents : stats . totalAgents } ;
466- }
347+ return sati . listAllAgents ( params ) ;
467348}
468349
469350/**
0 commit comments