1
+ import {
2
+ elizaLogger ,
3
+ composeContext ,
4
+ generateObject ,
5
+ ModelClass ,
6
+ type IAgentRuntime ,
7
+ type Memory ,
8
+ type State ,
9
+ type HandlerCallback ,
10
+ Content ,
11
+ } from "@elizaos/core" ;
12
+ import {
13
+ Address ,
14
+ Dictionary ,
15
+ } from "@ton/core" ;
16
+ import { z } from "zod" ;
17
+ import { initWalletProvider , type WalletProvider } from "../providers/wallet" ;
18
+
19
+ export interface GetCollectionDataContent extends Content {
20
+ collectionAddress : string ;
21
+ }
22
+
23
+ function isGetCollectionDataContent ( content : Content ) : content is GetCollectionDataContent {
24
+ return typeof content . collectionAddress === "string" ;
25
+ }
26
+
27
+ /**
28
+ * Schema for retrieving NFT collection data.
29
+ * - collectionAddress: the NFT collection smart contract address.
30
+ * - endpoint: optional TON RPC endpoint, defaults to testnet if not specified.
31
+ */
32
+ const getCollectionDataSchema = z . object ( {
33
+ collectionAddress : z . string ( ) . nonempty ( "Collection address is required" ) ,
34
+ } ) ;
35
+
36
+ /**
37
+ * Template guiding the extraction of collection data parameters.
38
+ * The output should be a JSON markdown block similar to:
39
+ *
40
+ * {
41
+ * "collectionAddress": "EQSomeCollectionAddressExample",
42
+ * "endpoint": "https://testnet.toncenter.com/api/v2/jsonRPC"
43
+ * }
44
+ */
45
+ const getCollectionDataTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
46
+
47
+ Example response:
48
+ \`\`\`json
49
+ "collectionAddress": "EQCGScrZe1xbyWqWDvdI6mzP-GAcAWFv6ZXuaJOuSqemxku4",
50
+ }
51
+
52
+ {{recentMessages}}
53
+
54
+ Given the recent messages, extract the following information about the requested NFT collection data:
55
+ - Collection address
56
+
57
+ Respond with a JSON markdown block containing only the extracted values.` ;
58
+
59
+
60
+ /**
61
+ * GetCollectionDataAction encapsulates the core logic to retrieve NFT collection data.
62
+ */
63
+ class GetCollectionDataAction {
64
+ private readonly walletProvider : WalletProvider ;
65
+
66
+ constructor ( walletProvider : WalletProvider ) {
67
+ this . walletProvider = walletProvider ;
68
+ }
69
+
70
+ /**
71
+ * Retrieves and parses collection data from the provided collection address.
72
+ * Returns an object containing the next NFT index, owner address and parsed NFT items.
73
+ */
74
+ async getData (
75
+ collectionAddress : string ,
76
+ ) : Promise < {
77
+ collectionAddress : string ;
78
+ nextItemIndex : number ;
79
+ ownerAddress : string | null ;
80
+ nftItems : Array < { index : number ; deposit : string ; ownerAddress : string ; meta : string } > ;
81
+ message : string ;
82
+ } > {
83
+ const walletClient = this . walletProvider . getWalletClient ( ) ;
84
+
85
+ try {
86
+ const addr = Address . parse ( collectionAddress ) ;
87
+
88
+ // Run the get_collection_data method on the collection contract.
89
+ // Per TEP-62, it returns:
90
+ // (int next_item_index, cell collection_content, slice owner_address).
91
+ const result = await walletClient . runMethod ( addr , "get_collection_data" ) ;
92
+
93
+ // Extract the next NFT index.
94
+ const nextItemIndex = result . stack . readNumber ( ) ;
95
+
96
+ const nftItems = [ ] ;
97
+ for ( let i = 0 ; i < nextItemIndex ; i ++ ) {
98
+ const item = await walletClient . runMethod ( addr , "get_nft_item" ,
99
+ [ { type : "int" , value : BigInt ( i ) } ] ) ;
100
+ nftItems . push ( item ) ;
101
+ }
102
+ let ownerAddressStr : string | null = null ;
103
+ try {
104
+ const ownerAddress = result . stack . readAddress ( ) ;
105
+ ownerAddressStr = ownerAddress . toString ( ) ;
106
+ } catch ( e ) {
107
+ ownerAddressStr = null ;
108
+ }
109
+
110
+ return {
111
+ collectionAddress,
112
+ nextItemIndex,
113
+ ownerAddress : ownerAddressStr ,
114
+ nftItems,
115
+ message : "Collection data fetched successfully" ,
116
+ } ;
117
+ } catch ( error : any ) {
118
+ elizaLogger . error ( "Error fetching collection data:" , error ) ;
119
+ throw error ;
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Helper function that builds collection data details.
126
+ */
127
+ const buildGetCollectionData = async (
128
+ runtime : IAgentRuntime ,
129
+ message : Memory ,
130
+ state : State
131
+ ) : Promise < GetCollectionDataContent > => {
132
+
133
+ // Initialize or update state
134
+ let currentState = state ;
135
+ if ( ! currentState ) {
136
+ currentState = ( await runtime . composeState ( message ) ) as State ;
137
+ } else {
138
+ currentState = await runtime . updateRecentMessageState ( currentState ) ;
139
+ }
140
+
141
+ const getCollectionContext = composeContext ( {
142
+ state : currentState ,
143
+ template : getCollectionDataTemplate ,
144
+ } ) ;
145
+ const content = await generateObject ( {
146
+ runtime,
147
+ context : getCollectionContext ,
148
+ schema : getCollectionDataSchema ,
149
+ modelClass : ModelClass . SMALL ,
150
+ } ) ;
151
+
152
+ let buildGetCollectionDataContent : GetCollectionDataContent = content . object as GetCollectionDataContent ;
153
+
154
+ if ( buildGetCollectionDataContent === undefined ) {
155
+ buildGetCollectionDataContent = content as unknown as GetCollectionDataContent ;
156
+ }
157
+
158
+ return buildGetCollectionDataContent ;
159
+ } ;
160
+
161
+ export default {
162
+ name : "GET_NFT_COLLECTION_DATA" ,
163
+ similes : [ "GET_COLLECTION_DATA" , "FETCH_NFT_COLLECTION" ] ,
164
+ description :
165
+ "Fetches collection data (next NFT index, array of NFT items, and owner address) from the provided NFT collection address." ,
166
+ handler : async (
167
+ runtime : IAgentRuntime ,
168
+ message : Memory ,
169
+ state : State ,
170
+ _options : Record < string , unknown > ,
171
+ callback ?: HandlerCallback
172
+ ) => {
173
+ elizaLogger . log ( "Starting GET_NFT_COLLECTION_DATA handler..." ) ;
174
+ // Build collection data details using the helper method.
175
+ const getCollectionDetails = await buildGetCollectionData ( runtime , message , state ) ;
176
+
177
+ if ( ! isGetCollectionDataContent ( getCollectionDetails ) ) {
178
+ if ( callback ) {
179
+ callback ( {
180
+ text : "Unable to process get collection data request. Invalid content provided." ,
181
+ content : { error : "Invalid get collection data content" } ,
182
+ } ) ;
183
+ }
184
+ return false ;
185
+ }
186
+
187
+ try {
188
+ const walletProvider = await initWalletProvider ( runtime ) ;
189
+ const getCollectionDataAction = new GetCollectionDataAction ( walletProvider ) ;
190
+ const collectionData = await getCollectionDataAction . getData ( getCollectionDetails . collectionAddress ) ;
191
+
192
+ if ( callback ) {
193
+ callback ( {
194
+ text : JSON . stringify ( collectionData , null , 2 ) ,
195
+ content : collectionData ,
196
+ } ) ;
197
+ }
198
+ return true ;
199
+ } catch ( error : any ) {
200
+ elizaLogger . error ( "Error fetching collection data:" , error ) ;
201
+ if ( callback ) {
202
+ callback ( {
203
+ text : `Error fetching collection data: ${ error . message } ` ,
204
+ content : { error : error . message } ,
205
+ } ) ;
206
+ }
207
+ return false ;
208
+ }
209
+ } ,
210
+ validate : async ( _runtime : IAgentRuntime ) => true ,
211
+ examples : [
212
+ [
213
+ {
214
+ user : "{{user1}}" ,
215
+ content : {
216
+ collectionAddress : "EQSomeCollectionAddressExample" ,
217
+ action : "GET_NFT_COLLECTION_DATA" ,
218
+ } ,
219
+ } ,
220
+ {
221
+ user : "{{user1}}" ,
222
+ content : {
223
+ text : "Collection data fetched successfully. Next index: ..., NFT items: [...]" ,
224
+ } ,
225
+ } ,
226
+ ] ,
227
+ ] ,
228
+ } ;
0 commit comments