Skip to content

Commit 621ff3d

Browse files
committed
Update subgraph URLs and add indexer support
1 parent 96ffffd commit 621ff3d

File tree

16 files changed

+344
-96
lines changed

16 files changed

+344
-96
lines changed

packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class OperatorCategory(Enum):
3737
"https://api.studio.thegraph.com/query/74256/ethereum/version/latest"
3838
),
3939
"subgraph_url_api_key": (
40-
"https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmXGmcjEtwwEgB83KE2ECjjYY38kLMqzaq4ip8GWY7A6zz"
40+
"https://gateway.thegraph.com/api/deployments/id/QmXGmcjEtwwEgB83KE2ECjjYY38kLMqzaq4ip8GWY7A6zz"
4141
),
4242
"hmt_address": "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867",
4343
"factory_address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a",
@@ -53,7 +53,7 @@ class OperatorCategory(Enum):
5353
"https://api.studio.thegraph.com/query/74256/sepolia/version/latest"
5454
),
5555
"subgraph_url_api_key": (
56-
"https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmT4xNvZh8ymarrk1zdytjLhCW59iuTavsd4JgHS4LbCVB"
56+
"https://gateway.thegraph.com/api/deployments/id/QmT4xNvZh8ymarrk1zdytjLhCW59iuTavsd4JgHS4LbCVB"
5757
),
5858
"hmt_address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33",
5959
"factory_address": "0x5987A5558d961ee674efe4A8c8eB7B1b5495D3bf",
@@ -69,7 +69,7 @@ class OperatorCategory(Enum):
6969
"https://api.studio.thegraph.com/query/74256/bsc/version/latest"
7070
),
7171
"subgraph_url_api_key": (
72-
"hthttps://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmdVdpm9NnFz5B8QyzhW1bW1nNfRWemTiFn2MrhYZGTSQD"
72+
"https://gateway.thegraph.com/api/deployments/id/QmdVdpm9NnFz5B8QyzhW1bW1nNfRWemTiFn2MrhYZGTSQD"
7373
),
7474
"hmt_address": "0x711Fd6ab6d65A98904522d4e3586F492B989c527",
7575
"factory_address": "0x92FD968AcBd521c232f5fB8c33b342923cC72714",
@@ -85,7 +85,7 @@ class OperatorCategory(Enum):
8585
"https://api.studio.thegraph.com/query/74256/bsc-testnet/version/latest"
8686
),
8787
"subgraph_url_api_key": (
88-
"https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmZjYMktZe8RAz7W7qL33VZBV6AC57xsLyE1cEfv6NABdZ"
88+
"https://gateway.thegraph.com/api/deployments/id/QmZjYMktZe8RAz7W7qL33VZBV6AC57xsLyE1cEfv6NABdZ"
8989
),
9090
"hmt_address": "0xE3D74BBFa45B4bCa69FF28891fBE392f4B4d4e4d",
9191
"factory_address": "0x2bfA592DBDaF434DDcbb893B1916120d181DAD18",
@@ -103,7 +103,7 @@ class OperatorCategory(Enum):
103103
"https://api.studio.thegraph.com/query/74256/polygon/version/latest"
104104
),
105105
"subgraph_url_api_key": (
106-
"https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmUt9mmfNjtC5ZnQNiWHRbFG3k5zfngMuoTyky9jhXYqG2"
106+
"https://gateway.thegraph.com/api/deployments/id/QmUt9mmfNjtC5ZnQNiWHRbFG3k5zfngMuoTyky9jhXYqG2"
107107
),
108108
"hmt_address": "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571BF",
109109
"factory_address": "0xBDBfD2cC708199C5640C6ECdf3B0F4A4C67AdfcB",
@@ -121,7 +121,7 @@ class OperatorCategory(Enum):
121121
"https://api.studio.thegraph.com/query/74256/amoy/version/latest"
122122
),
123123
"subgraph_url_api_key": (
124-
"https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmWRUFWpWoRRUh7Ec1HUJEwxc84DkP4iFTfLLsoVngJAPa"
124+
"https://gateway.thegraph.com/api/deployments/id/QmWRUFWpWoRRUh7Ec1HUJEwxc84DkP4iFTfLLsoVngJAPa"
125125
),
126126
"hmt_address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33",
127127
"factory_address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29",

packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class SubgraphOptions:
3838

3939
max_retries: Optional[int] = None
4040
base_delay: Optional[int] = None # milliseconds
41+
indexer_id: Optional[str] = None
4142

4243

4344
def is_indexer_error(error: Exception) -> bool:
@@ -90,40 +91,50 @@ def custom_gql_fetch(
9091
9192
:raise Exception: If the subgraph query fails
9293
"""
94+
subgraph_api_key = os.getenv("SUBGRAPH_API_KEY", "")
95+
9396
if not options:
9497
return _fetch_subgraph_data(network, query, params)
9598

96-
if (
97-
options.max_retries is not None
98-
and options.base_delay is None
99-
or options.max_retries is None
100-
and options.base_delay is not None
101-
):
99+
has_max_retries = options.max_retries is not None
100+
has_base_delay = options.base_delay is not None
101+
102+
if has_max_retries != has_base_delay:
102103
raise ValueError(
103104
"Retry configuration must include both max_retries and base_delay"
104105
)
105106

106-
max_retries = int(options.max_retries)
107-
base_delay = options.base_delay / 1000
107+
if options.indexer_id and not subgraph_api_key:
108+
raise ValueError(
109+
"Routing requests to a specific indexer requires SUBGRAPH_API_KEY to be set"
110+
)
111+
112+
max_retries = int(options.max_retries) if has_max_retries else 0
113+
base_delay_seconds = (options.base_delay or 0) / 1000
108114

109115
last_error = None
110116

111117
for attempt in range(max_retries + 1):
112118
try:
113-
return _fetch_subgraph_data(network, query, params)
119+
return _fetch_subgraph_data(network, query, params, options.indexer_id)
114120
except Exception as error:
115121
last_error = error
116122

117123
if not is_indexer_error(error):
118124
break
119125

120-
delay = base_delay * attempt
126+
delay = base_delay_seconds * attempt
121127
time.sleep(delay)
122128

123129
raise last_error
124130

125131

126-
def _fetch_subgraph_data(network: dict, query: str, params: dict = None):
132+
def _fetch_subgraph_data(
133+
network: dict,
134+
query: str,
135+
params: dict = None,
136+
indexer_id: Optional[str] = None,
137+
):
127138
subgraph_api_key = os.getenv("SUBGRAPH_API_KEY", "")
128139
if subgraph_api_key:
129140
subgraph_url = network["subgraph_url_api_key"].replace(
@@ -135,7 +146,15 @@ def _fetch_subgraph_data(network: dict, query: str, params: dict = None):
135146
)
136147
subgraph_url = network["subgraph_url"]
137148

138-
request = requests.post(subgraph_url, json={"query": query, "variables": params})
149+
subgraph_url = _attach_indexer_id(subgraph_url, indexer_id)
150+
151+
headers = (
152+
{"Authorization": f"Bearer {subgraph_api_key}"} if subgraph_api_key else None
153+
)
154+
155+
request = requests.post(
156+
subgraph_url, json={"query": query, "variables": params}, headers=headers
157+
)
139158
if request.status_code == 200:
140159
return request.json()
141160
else:
@@ -146,6 +165,12 @@ def _fetch_subgraph_data(network: dict, query: str, params: dict = None):
146165
)
147166

148167

168+
def _attach_indexer_id(url: str, indexer_id: Optional[str]) -> str:
169+
if not indexer_id:
170+
return url
171+
return f"{url}/indexers/id/{indexer_id}"
172+
173+
149174
def get_hmt_balance(wallet_addr, token_addr, w3):
150175
"""Get HMT balance
151176

packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import os
12
import unittest
23
from unittest.mock import Mock, patch
34
from validators import ValidationError
45

56
from human_protocol_sdk.utils import (
7+
_attach_indexer_id,
8+
_fetch_subgraph_data,
69
SubgraphOptions,
710
custom_gql_fetch,
811
is_indexer_error,
@@ -53,6 +56,12 @@ def setUp(self):
5356
}
5457
self.query = "query Test"
5558
self.variables = {"foo": "bar"}
59+
if "SUBGRAPH_API_KEY" in os.environ:
60+
del os.environ["SUBGRAPH_API_KEY"]
61+
62+
def tearDown(self):
63+
if "SUBGRAPH_API_KEY" in os.environ:
64+
del os.environ["SUBGRAPH_API_KEY"]
5665

5766
def test_returns_response_without_options(self):
5867
expected = {"data": {"ok": True}}
@@ -126,3 +135,65 @@ def test_raises_after_exhausting_retries(self):
126135

127136
self.assertTrue(is_indexer_error(ctx.exception))
128137
self.assertEqual(mock_fetch.call_count, 3)
138+
139+
def test_routes_requests_to_specific_indexer(self):
140+
options = SubgraphOptions(indexer_id="0xabc123")
141+
expected = {"data": {"ok": True}}
142+
os.environ["SUBGRAPH_API_KEY"] = "secure-token"
143+
144+
with patch(
145+
"human_protocol_sdk.utils._fetch_subgraph_data",
146+
return_value=expected,
147+
) as mock_fetch:
148+
result = custom_gql_fetch(
149+
self.network, self.query, self.variables, options=options
150+
)
151+
152+
self.assertEqual(result, expected)
153+
mock_fetch.assert_called_once_with(
154+
self.network, self.query, self.variables, "0xabc123"
155+
)
156+
157+
def test_raises_when_indexer_without_api_key(self):
158+
options = SubgraphOptions(indexer_id="0xabc123")
159+
160+
with self.assertRaises(ValueError) as ctx:
161+
custom_gql_fetch(self.network, self.query, self.variables, options)
162+
163+
self.assertIn(
164+
"Routing requests to a specific indexer requires SUBGRAPH_API_KEY to be set",
165+
str(ctx.exception),
166+
)
167+
168+
def test_fetch_subgraph_adds_authorization_header(self):
169+
network = {
170+
"subgraph_url": "http://subgraph",
171+
"subgraph_url_api_key": "http://subgraph-with-key",
172+
}
173+
174+
with patch.dict(os.environ, {"SUBGRAPH_API_KEY": "token"}, clear=True):
175+
with patch("human_protocol_sdk.utils.requests.post") as mock_post:
176+
mock_post.return_value.status_code = 200
177+
mock_post.return_value.json.return_value = {"data": {}}
178+
179+
_fetch_subgraph_data(network, self.query, self.variables)
180+
181+
mock_post.assert_called_once()
182+
self.assertEqual(
183+
mock_post.call_args.kwargs.get("headers"),
184+
{"Authorization": "Bearer token"},
185+
)
186+
187+
188+
class TestAttachIndexerId(unittest.TestCase):
189+
def test_converts_subgraph_path_to_deployment(self):
190+
base_url = "https://gateway.thegraph.com/api/key/deployments/id/Qm123"
191+
result = _attach_indexer_id(base_url, "0xabc")
192+
self.assertEqual(
193+
result,
194+
"https://gateway.thegraph.com/api/key/deployments/id/Qm123/indexers/id/0xabc",
195+
)
196+
197+
def test_returns_original_url_when_indexer_missing(self):
198+
url = "https://gateway.thegraph.com/api/deployments/id/Qm123"
199+
self.assertEqual(_attach_indexer_id(url, None), url)

packages/sdk/typescript/human-protocol-sdk/example/escrow.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ export const getEscrows = async () => {
99
return;
1010
}
1111

12-
const escrows = await EscrowUtils.getEscrows({
13-
status: [EscrowStatus.Pending, EscrowStatus.Complete],
14-
chainId: ChainId.POLYGON_AMOY,
15-
first: 1000,
16-
});
12+
const escrows = await EscrowUtils.getEscrows(
13+
{
14+
status: [EscrowStatus.Pending, EscrowStatus.Complete],
15+
chainId: ChainId.POLYGON_AMOY,
16+
first: 10,
17+
},
18+
{ indexerId: '0xbdfb5ee5a2abf4fc7bb1bd1221067aef7f9de491' }
19+
);
1720

1821
console.log('Pending escrows:', escrows.length);
1922
};

packages/sdk/typescript/human-protocol-sdk/src/constants.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const NETWORKS: {
3636
subgraphUrl:
3737
'https://api.studio.thegraph.com/query/74256/ethereum/version/latest',
3838
subgraphUrlApiKey:
39-
'https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmXGmcjEtwwEgB83KE2ECjjYY38kLMqzaq4ip8GWY7A6zz',
39+
'https://gateway.thegraph.com/api/deployments/id/QmXGmcjEtwwEgB83KE2ECjjYY38kLMqzaq4ip8GWY7A6zz',
4040
oldSubgraphUrl: '',
4141
oldFactoryAddress: '',
4242
},
@@ -51,7 +51,7 @@ export const NETWORKS: {
5151
subgraphUrl:
5252
'https://api.studio.thegraph.com/query/74256/sepolia/version/latest',
5353
subgraphUrlApiKey:
54-
'https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmT4xNvZh8ymarrk1zdytjLhCW59iuTavsd4JgHS4LbCVB',
54+
'https://gateway.thegraph.com/api/deployments/id/QmT4xNvZh8ymarrk1zdytjLhCW59iuTavsd4JgHS4LbCVB',
5555
oldSubgraphUrl: '',
5656
oldFactoryAddress: '',
5757
},
@@ -66,7 +66,7 @@ export const NETWORKS: {
6666
subgraphUrl:
6767
'https://api.studio.thegraph.com/query/74256/bsc/version/latest',
6868
subgraphUrlApiKey:
69-
'https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmdVdpm9NnFz5B8QyzhW1bW1nNfRWemTiFn2MrhYZGTSQD',
69+
'https://gateway.thegraph.com/api/deployments/id/QmdVdpm9NnFz5B8QyzhW1bW1nNfRWemTiFn2MrhYZGTSQD',
7070
oldSubgraphUrl: 'https://api.thegraph.com/subgraphs/name/humanprotocol/bsc',
7171
oldFactoryAddress: '0xc88bC422cAAb2ac8812de03176402dbcA09533f4',
7272
},
@@ -81,7 +81,7 @@ export const NETWORKS: {
8181
subgraphUrl:
8282
'https://api.studio.thegraph.com/query/74256/bsc-testnet/version/latest',
8383
subgraphUrlApiKey:
84-
'https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmZjYMktZe8RAz7W7qL33VZBV6AC57xsLyE1cEfv6NABdZ',
84+
'https://gateway.thegraph.com/api/deployments/id/QmZjYMktZe8RAz7W7qL33VZBV6AC57xsLyE1cEfv6NABdZ',
8585
oldSubgraphUrl:
8686
'https://api.thegraph.com/subgraphs/name/humanprotocol/bsctest',
8787
oldFactoryAddress: '0xaae6a2646c1f88763e62e0cd08ad050ea66ac46f',
@@ -97,7 +97,7 @@ export const NETWORKS: {
9797
subgraphUrl:
9898
'https://api.studio.thegraph.com/query/74256/polygon/version/latest',
9999
subgraphUrlApiKey:
100-
'https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmUt9mmfNjtC5ZnQNiWHRbFG3k5zfngMuoTyky9jhXYqG2',
100+
'https://gateway.thegraph.com/api/deployments/id/QmUt9mmfNjtC5ZnQNiWHRbFG3k5zfngMuoTyky9jhXYqG2',
101101
oldSubgraphUrl:
102102
'https://api.thegraph.com/subgraphs/name/humanprotocol/polygon',
103103
oldFactoryAddress: '0x45eBc3eAE6DA485097054ae10BA1A0f8e8c7f794',
@@ -113,7 +113,7 @@ export const NETWORKS: {
113113
subgraphUrl:
114114
'https://api.studio.thegraph.com/query/74256/amoy/version/latest',
115115
subgraphUrlApiKey:
116-
'https://gateway-arbitrum.network.thegraph.com/api/[SUBGRAPH_API_KEY]/deployments/id/QmWRUFWpWoRRUh7Ec1HUJEwxc84DkP4iFTfLLsoVngJAPa',
116+
'https://gateway.thegraph.com/api/deployments/id/QmWRUFWpWoRRUh7Ec1HUJEwxc84DkP4iFTfLLsoVngJAPa',
117117
oldSubgraphUrl: '',
118118
oldFactoryAddress: '',
119119
},

packages/sdk/typescript/human-protocol-sdk/src/error.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,13 @@ export const ErrorRetryParametersMissing = new Error(
313313
'Retry configuration must include both maxRetries and baseDelay'
314314
);
315315

316+
/**
317+
* @constant {Error} - Retry configuration is missing required parameters.
318+
*/
319+
export const ErrorRoutingRequestsToIndexerRequiresApiKey = new Error(
320+
'Routing requests to a specific indexer requires SUBGRAPH_API_KEY to be set'
321+
);
322+
316323
/**
317324
* @constant {Warning} - Possible version mismatch.
318325
*/

packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,9 @@ export interface SubgraphOptions {
321321
maxRetries?: number;
322322
/** Base delay between retries in milliseconds */
323323
baseDelay?: number;
324+
/**
325+
* Optional indexer identifier. When provided, requests target
326+
* `{gateway}/deployments/id/<DEPLOYMENT_ID>/indexers/id/<INDEXER_ID>`.
327+
*/
328+
indexerId?: string;
324329
}

0 commit comments

Comments
 (0)