@@ -3,7 +3,7 @@ import { TransactionResult } from '@algorandfoundation/algokit-utils/types/index
3
3
import algosdk from 'algosdk'
4
4
import fs from 'fs'
5
5
import path from 'path'
6
- import { AlgorandSubscriber } from '../../src/subscriber '
6
+ import { DynamicAlgorandSubscriber } from '../../src'
7
7
import TransactionType = algosdk . TransactionType
8
8
9
9
if ( ! fs . existsSync ( path . join ( __dirname , '..' , '..' , '.env' ) ) && ! process . env . ALGOD_SERVER ) {
@@ -20,27 +20,54 @@ interface DHMAsset {
20
20
metadata : Record < string , unknown >
21
21
created : string
22
22
lastModified : string
23
+ owner : string
24
+ ownerModified : string
25
+ }
26
+
27
+ interface DHMFilterState {
28
+ assetIds : number [ ]
23
29
}
24
30
25
31
async function getDHMSubscriber ( ) {
26
32
const algod = await algokit . getAlgoClient ( )
27
33
const indexer = await algokit . getAlgoIndexerClient ( )
28
- const subscriber = new AlgorandSubscriber (
34
+ const subscriber = new DynamicAlgorandSubscriber < DHMFilterState > (
29
35
{
30
- filters : [
31
- {
32
- name : 'dhm-asset' ,
33
- filter : {
34
- type : TransactionType . acfg ,
35
- // Data History Museum creator accounts
36
- sender : ( await algokit . isTestNet ( algod ) )
37
- ? 'ER7AMZRPD5KDVFWTUUVOADSOWM4RQKEEV2EDYRVSA757UHXOIEKGMBQIVU'
38
- : 'EHYQCYHUC6CIWZLBX5TDTLVJ4SSVE4RRTMKFDCG4Z4Q7QSQ2XWIQPMKBPU' ,
39
- } ,
40
- } ,
36
+ maxIndexerRoundsToSync : 5_000_000 ,
37
+ dynamicFilters : async ( filterState , pollLevel ) => [
38
+ ...( pollLevel === 0
39
+ ? [
40
+ {
41
+ name : 'dhm-asset' ,
42
+ filter : {
43
+ type : TransactionType . acfg ,
44
+ // Data History Museum creator accounts
45
+ sender : ( await algokit . isTestNet ( algod ) )
46
+ ? 'ER7AMZRPD5KDVFWTUUVOADSOWM4RQKEEV2EDYRVSA757UHXOIEKGMBQIVU'
47
+ : 'EHYQCYHUC6CIWZLBX5TDTLVJ4SSVE4RRTMKFDCG4Z4Q7QSQ2XWIQPMKBPU' ,
48
+ } ,
49
+ } ,
50
+ ]
51
+ : [ ] ) ,
52
+ ...( filterState . assetIds . length > 0
53
+ ? [
54
+ {
55
+ name : 'dhm-ownership-change' ,
56
+ filter : {
57
+ type : TransactionType . axfer ,
58
+ assetId : filterState . assetIds ,
59
+ minAmount : 1 ,
60
+ } ,
61
+ } ,
62
+ ]
63
+ : [ ] ) ,
41
64
] ,
42
- frequencyInSeconds : 5 ,
43
- maxRoundsToSync : 100 ,
65
+ filterStatePersistence : {
66
+ get : getFilterState ,
67
+ set : saveFilterState ,
68
+ } ,
69
+ frequencyInSeconds : 1 ,
70
+ maxRoundsToSync : 500 ,
44
71
syncBehaviour : 'catchup-with-indexer' ,
45
72
watermarkPersistence : {
46
73
get : getLastWatermark ,
@@ -52,9 +79,18 @@ async function getDHMSubscriber() {
52
79
)
53
80
subscriber . onBatch ( 'dhm-asset' , async ( events ) => {
54
81
// eslint-disable-next-line no-console
55
- console . log ( `Received ${ events . length } asset changes` )
82
+ console . log ( `Received ${ events . length } asset changes (${ events . filter ( ( t ) => t [ 'created-asset-index' ] ) . length } new assets)` )
83
+
84
+ // Append any new asset ids to the filter state so ownership is picked up of them
85
+ subscriber . appendFilterState ( { assetIds : events . filter ( ( e ) => e [ 'created-asset-index' ] ) . map ( ( e ) => e [ 'created-asset-index' ] ! ) } )
86
+ } )
87
+ subscriber . onBatch ( 'dhm-ownership-change' , async ( events ) => {
88
+ // eslint-disable-next-line no-console
89
+ console . log ( `Received ${ events . length } ownership changes` )
90
+ } )
91
+ subscriber . onPoll ( async ( pollMetadata ) => {
56
92
// Save all of the Data History Museum Verifiably Authentic Digital Historical Artifacts
57
- await saveDHMTransactions ( events )
93
+ await saveDHMTransactions ( pollMetadata . subscribedTransactions )
58
94
} )
59
95
return subscriber
60
96
}
@@ -81,8 +117,10 @@ async function saveDHMTransactions(transactions: TransactionResult[]) {
81
117
metadata : getArc69Metadata ( t ) ,
82
118
created : new Date ( t [ 'round-time' ] ! * 1000 ) . toISOString ( ) ,
83
119
lastModified : new Date ( t [ 'round-time' ] ! * 1000 ) . toISOString ( ) ,
120
+ owner : t . sender ,
121
+ ownerModified : new Date ( t [ 'round-time' ] ! * 1000 ) . toISOString ( ) ,
84
122
} )
85
- } else {
123
+ } else if ( t [ 'asset-config-transaction' ] ) {
86
124
const asset = assets . find ( ( a ) => a . id === t [ 'asset-config-transaction' ] ! [ 'asset-id' ] )
87
125
if ( ! asset ) {
88
126
// eslint-disable-next-line no-console
@@ -96,6 +134,17 @@ async function saveDHMTransactions(transactions: TransactionResult[]) {
96
134
asset ! . metadata = getArc69Metadata ( t )
97
135
asset ! . lastModified = new Date ( t [ 'round-time' ] ! * 1000 ) . toISOString ( )
98
136
}
137
+ } else if ( t [ 'asset-transfer-transaction' ] ) {
138
+ const asset = assets . find ( ( a ) => a . id === t [ 'asset-transfer-transaction' ] ! [ 'asset-id' ] )
139
+ if ( ! asset ) {
140
+ // eslint-disable-next-line no-console
141
+ console . error ( t )
142
+ throw new Error ( `Unable to find existing asset data for ${ t [ 'asset-transfer-transaction' ] ! [ 'asset-id' ] } ` )
143
+ }
144
+ if ( t [ 'asset-transfer-transaction' ] . amount > 0 ) {
145
+ asset . owner = t [ 'asset-transfer-transaction' ] ! . receiver
146
+ asset . ownerModified = new Date ( t [ 'round-time' ] ! * 1000 ) . toISOString ( )
147
+ }
99
148
}
100
149
}
101
150
@@ -104,12 +153,25 @@ async function saveDHMTransactions(transactions: TransactionResult[]) {
104
153
105
154
// Basic methods that persist using filesystem - for illustrative purposes only
106
155
156
+ async function saveFilterState ( state : DHMFilterState ) {
157
+ fs . writeFileSync ( path . join ( __dirname , 'filters.json' ) , JSON . stringify ( state ) , { encoding : 'utf-8' } )
158
+ }
159
+
160
+ async function getFilterState ( ) : Promise < DHMFilterState > {
161
+ if ( ! fs . existsSync ( path . join ( __dirname , 'filters.json' ) ) ) return { assetIds : [ ] }
162
+ const existing = fs . readFileSync ( path . join ( __dirname , 'filters.json' ) , 'utf-8' )
163
+ const existingData = JSON . parse ( existing ) as DHMFilterState
164
+ // eslint-disable-next-line no-console
165
+ console . log ( `Found existing filter state in filters.json; syncing with ${ existingData . assetIds . length } assets` )
166
+ return existingData
167
+ }
168
+
107
169
async function saveWatermark ( watermark : number ) {
108
170
fs . writeFileSync ( path . join ( __dirname , 'watermark.txt' ) , watermark . toString ( ) , { encoding : 'utf-8' } )
109
171
}
110
172
111
173
async function getLastWatermark ( ) : Promise < number > {
112
- if ( ! fs . existsSync ( path . join ( __dirname , 'watermark.txt' ) ) ) return 0
174
+ if ( ! fs . existsSync ( path . join ( __dirname , 'watermark.txt' ) ) ) return 15_000_000
113
175
const existing = fs . readFileSync ( path . join ( __dirname , 'watermark.txt' ) , 'utf-8' )
114
176
// eslint-disable-next-line no-console
115
177
console . log ( `Found existing sync watermark in watermark.txt; syncing from ${ existing } ` )
0 commit comments