numerapi 2.22.0__tar.gz → 2.23.0.dev0__tar.gz
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.
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/PKG-INFO +1 -1
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi/base_api.py +206 -1
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi.egg-info/PKG-INFO +1 -1
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/setup.py +1 -1
- numerapi-2.23.0.dev0/tests/test_base_api.py +264 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/tests/test_numerapi.py +2 -0
- numerapi-2.22.0/tests/test_base_api.py +0 -118
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/LICENSE +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/README.md +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi/__init__.py +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi/cli.py +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi/cryptoapi.py +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi/numerapi.py +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi/py.typed +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi/signalsapi.py +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi/utils.py +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi.egg-info/SOURCES.txt +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi.egg-info/dependency_links.txt +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi.egg-info/entry_points.txt +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi.egg-info/requires.txt +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/numerapi.egg-info/top_level.txt +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/setup.cfg +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/tests/test_cli.py +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/tests/test_signalsapi.py +0 -0
- {numerapi-2.22.0 → numerapi-2.23.0.dev0}/tests/test_utils.py +0 -0
|
@@ -383,6 +383,211 @@ class Api:
|
|
|
383
383
|
}
|
|
384
384
|
return mapping
|
|
385
385
|
|
|
386
|
+
def v3_stake_auth(
|
|
387
|
+
self,
|
|
388
|
+
submission_id: str,
|
|
389
|
+
staker: str,
|
|
390
|
+
amount: float | str | None = None,
|
|
391
|
+
max_amount: float | str | None = None,
|
|
392
|
+
) -> Dict:
|
|
393
|
+
"""Issue a staking v3 authorization for a selected submission.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
submission_id (str): submission id for the selected submission
|
|
397
|
+
staker (str): staker wallet address
|
|
398
|
+
amount (float or str, optional): max stake amount for the
|
|
399
|
+
authorization. Retained as a backwards-compatible alias for
|
|
400
|
+
`max_amount`.
|
|
401
|
+
max_amount (float or str, optional): max stake amount for the
|
|
402
|
+
authorization.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
dict: authorization payload with the following fields:
|
|
406
|
+
|
|
407
|
+
* authorizationSigner (`str`)
|
|
408
|
+
* authorizationDigest (`str`)
|
|
409
|
+
* chainId (`str`)
|
|
410
|
+
* deadline (`str`)
|
|
411
|
+
* maxAmount (`str`)
|
|
412
|
+
* amount (`str`) alias for `maxAmount`
|
|
413
|
+
* modelId (`str`)
|
|
414
|
+
* nmrAddress (`str`)
|
|
415
|
+
* nonce (`str`)
|
|
416
|
+
* roundId (`str`)
|
|
417
|
+
* signature (`str`)
|
|
418
|
+
* staker (`str`)
|
|
419
|
+
* stakingAddress (`str`)
|
|
420
|
+
* submissionId (`str`)
|
|
421
|
+
* submissionHash (`str`)
|
|
422
|
+
* tournamentId (`str`)
|
|
423
|
+
"""
|
|
424
|
+
if (amount is None) == (max_amount is None):
|
|
425
|
+
raise ValueError("Provide exactly one of amount or max_amount.")
|
|
426
|
+
|
|
427
|
+
max_amount = max_amount if max_amount is not None else amount
|
|
428
|
+
query = """
|
|
429
|
+
mutation($submissionId: ID!, $staker: String!, $maxAmount: String!) {
|
|
430
|
+
v3StakeAuth(
|
|
431
|
+
submissionId: $submissionId
|
|
432
|
+
staker: $staker
|
|
433
|
+
maxAmount: $maxAmount
|
|
434
|
+
) {
|
|
435
|
+
authorizationSigner
|
|
436
|
+
authorizationDigest
|
|
437
|
+
chainId
|
|
438
|
+
deadline
|
|
439
|
+
maxAmount
|
|
440
|
+
modelId
|
|
441
|
+
nmrAddress
|
|
442
|
+
nonce
|
|
443
|
+
roundId
|
|
444
|
+
signature
|
|
445
|
+
staker
|
|
446
|
+
stakingAddress
|
|
447
|
+
submissionId
|
|
448
|
+
submissionHash
|
|
449
|
+
tournamentId
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
"""
|
|
453
|
+
arguments = {
|
|
454
|
+
"submissionId": submission_id,
|
|
455
|
+
"staker": staker,
|
|
456
|
+
"maxAmount": str(max_amount),
|
|
457
|
+
}
|
|
458
|
+
authorization = self.raw_query(query, arguments, authorization=True)["data"][
|
|
459
|
+
"v3StakeAuth"
|
|
460
|
+
]
|
|
461
|
+
authorization["amount"] = authorization["maxAmount"]
|
|
462
|
+
return authorization
|
|
463
|
+
|
|
464
|
+
def v3_stake_config(self) -> Dict:
|
|
465
|
+
"""Fetch staking v3 contract configuration.
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
dict: staking v3 configuration with the following fields:
|
|
469
|
+
|
|
470
|
+
* address (`str`)
|
|
471
|
+
* authorizationSigner (`str`)
|
|
472
|
+
* nmrAddress (`str`)
|
|
473
|
+
* owner (`str`)
|
|
474
|
+
* paused (`bool`)
|
|
475
|
+
* pendingOwner (`str`)
|
|
476
|
+
* serviceWallet (`str`)
|
|
477
|
+
"""
|
|
478
|
+
query = """
|
|
479
|
+
query {
|
|
480
|
+
v3StakeConfig {
|
|
481
|
+
address
|
|
482
|
+
authorizationSigner
|
|
483
|
+
nmrAddress
|
|
484
|
+
owner
|
|
485
|
+
paused
|
|
486
|
+
pendingOwner
|
|
487
|
+
serviceWallet
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
"""
|
|
491
|
+
return self.raw_query(query, authorization=True)["data"]["v3StakeConfig"]
|
|
492
|
+
|
|
493
|
+
def v3_stake_round(self, round_id: int | str) -> Dict:
|
|
494
|
+
"""Fetch staking v3 round status by round id.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
round_id (int or str): round id
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
dict: staking v3 round data with the following fields:
|
|
501
|
+
|
|
502
|
+
* closeTime (`str`)
|
|
503
|
+
* merkleRoot (`str`)
|
|
504
|
+
* openTime (`str`)
|
|
505
|
+
* payoutFactor (`str`)
|
|
506
|
+
* remainingBurn (`str`)
|
|
507
|
+
* remainingPayout (`str`)
|
|
508
|
+
* resolveTime (`str`)
|
|
509
|
+
* resolved (`bool`)
|
|
510
|
+
* roundId (`str`)
|
|
511
|
+
* stakeCap (`str`)
|
|
512
|
+
* stakeThreshold (`str`)
|
|
513
|
+
* state (`str`)
|
|
514
|
+
* totalPayout (`str`)
|
|
515
|
+
* totalStaked (`str`)
|
|
516
|
+
* tournamentId (`str`)
|
|
517
|
+
"""
|
|
518
|
+
query = """
|
|
519
|
+
query($roundId: String!) {
|
|
520
|
+
v3StakeRound(roundId: $roundId) {
|
|
521
|
+
closeTime
|
|
522
|
+
merkleRoot
|
|
523
|
+
openTime
|
|
524
|
+
payoutFactor
|
|
525
|
+
remainingBurn
|
|
526
|
+
remainingPayout
|
|
527
|
+
resolveTime
|
|
528
|
+
resolved
|
|
529
|
+
roundId
|
|
530
|
+
stakeCap
|
|
531
|
+
stakeThreshold
|
|
532
|
+
state
|
|
533
|
+
totalPayout
|
|
534
|
+
totalStaked
|
|
535
|
+
tournamentId
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
"""
|
|
539
|
+
arguments = {"roundId": str(round_id)}
|
|
540
|
+
return self.raw_query(query, arguments, authorization=True)["data"][
|
|
541
|
+
"v3StakeRound"
|
|
542
|
+
]
|
|
543
|
+
|
|
544
|
+
def v3_stake_claim(self, round_id: int | str, model_id: str, staker: str) -> Dict:
|
|
545
|
+
"""Fetch a staking v3 claim proof for a model and staker.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
round_id (int or str): round id
|
|
549
|
+
model_id (str): model id
|
|
550
|
+
staker (str): staker wallet address
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
dict: claim payload with the following fields:
|
|
554
|
+
|
|
555
|
+
* apiModelId (`str`)
|
|
556
|
+
* burnAmountWei (`str`)
|
|
557
|
+
* merkleRoot (`str`)
|
|
558
|
+
* modelId (`str`)
|
|
559
|
+
* payoutAmountWei (`str`)
|
|
560
|
+
* proof (`list` of `str`)
|
|
561
|
+
* roundId (`str`)
|
|
562
|
+
* staker (`str`)
|
|
563
|
+
* submissionId (`str`)
|
|
564
|
+
* tournamentId (`str`)
|
|
565
|
+
"""
|
|
566
|
+
query = """
|
|
567
|
+
query($roundId: String!, $modelId: ID!, $staker: String!) {
|
|
568
|
+
v3StakeClaim(roundId: $roundId, modelId: $modelId, staker: $staker) {
|
|
569
|
+
apiModelId
|
|
570
|
+
burnAmountWei
|
|
571
|
+
merkleRoot
|
|
572
|
+
modelId
|
|
573
|
+
payoutAmountWei
|
|
574
|
+
proof
|
|
575
|
+
roundId
|
|
576
|
+
staker
|
|
577
|
+
submissionId
|
|
578
|
+
tournamentId
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
"""
|
|
582
|
+
arguments = {
|
|
583
|
+
"roundId": str(round_id),
|
|
584
|
+
"modelId": model_id,
|
|
585
|
+
"staker": staker,
|
|
586
|
+
}
|
|
587
|
+
return self.raw_query(query, arguments, authorization=True)["data"][
|
|
588
|
+
"v3StakeClaim"
|
|
589
|
+
]
|
|
590
|
+
|
|
386
591
|
def get_current_round(self, tournament: int | None = None) -> int | None:
|
|
387
592
|
"""Get number of the current active round.
|
|
388
593
|
|
|
@@ -1200,7 +1405,7 @@ class Api:
|
|
|
1200
1405
|
return False
|
|
1201
1406
|
if raw is None:
|
|
1202
1407
|
return False
|
|
1203
|
-
open_time = utils.parse_datetime_string(raw[
|
|
1408
|
+
open_time = utils.parse_datetime_string(raw["openTime"])
|
|
1204
1409
|
now = datetime.datetime.now(tz=pytz.utc)
|
|
1205
1410
|
is_new_round = open_time > now - datetime.timedelta(hours=hours)
|
|
1206
1411
|
return is_new_round
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import pytest
|
|
4
|
+
import responses
|
|
5
|
+
|
|
6
|
+
from numerapi import base_api
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture(scope='function', name="api")
|
|
10
|
+
def api_fixture():
|
|
11
|
+
api = base_api.Api(verbosity='DEBUG')
|
|
12
|
+
return api
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_NumerAPI():
|
|
16
|
+
# invalid log level should raise
|
|
17
|
+
with pytest.raises(AttributeError):
|
|
18
|
+
base_api.Api(verbosity="FOO")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test__login(api):
|
|
22
|
+
# passing only one of public_id and secret_key is not enough
|
|
23
|
+
api._login(public_id="foo", secret_key=None)
|
|
24
|
+
assert api.token is None
|
|
25
|
+
api._login(public_id=None, secret_key="bar")
|
|
26
|
+
assert api.token is None
|
|
27
|
+
# passing both works
|
|
28
|
+
api._login(public_id="foo", secret_key="bar")
|
|
29
|
+
assert api.token == ("foo", "bar")
|
|
30
|
+
|
|
31
|
+
# using env variables
|
|
32
|
+
os.environ["NUMERAI_SECRET_KEY"] = "key"
|
|
33
|
+
os.environ["NUMERAI_PUBLIC_ID"] = "id"
|
|
34
|
+
api._login()
|
|
35
|
+
assert api.token == ("id", "key")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_raw_query(api):
|
|
39
|
+
query = "query {latestNmrPrice {priceUsd}}"
|
|
40
|
+
result = api.raw_query(query)
|
|
41
|
+
assert isinstance(result, dict)
|
|
42
|
+
assert "data" in result
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@responses.activate
|
|
46
|
+
def test_get_account(api):
|
|
47
|
+
api.token = ("", "")
|
|
48
|
+
account = {'apiTokens': [{'name': 'uploads',
|
|
49
|
+
'public_id': 'AAA',
|
|
50
|
+
'scopes': ['upload_submission']},
|
|
51
|
+
{'name': '_internal_default',
|
|
52
|
+
'public_id': 'BBB',
|
|
53
|
+
'scopes': ['upload_submission',
|
|
54
|
+
'read_submission_info',
|
|
55
|
+
'read_user_info']},
|
|
56
|
+
{'name': 'all',
|
|
57
|
+
'public_id': 'CCC',
|
|
58
|
+
'scopes': ['upload_submission',
|
|
59
|
+
'stake',
|
|
60
|
+
'read_submission_info',
|
|
61
|
+
'read_user_info']}],
|
|
62
|
+
'availableNmr': '1.010000000000000000',
|
|
63
|
+
'email': 'no-reply@eu83t4nncmxv3g2.xyz',
|
|
64
|
+
'id': '0c10a70a-a851-478f-a289-7a05fe397008',
|
|
65
|
+
'insertedAt': "2018-01-01 11:11:11",
|
|
66
|
+
'mfaEnabled': False,
|
|
67
|
+
'models': [{'id': '881778ad-2ee9-4fb0-82b4-7b7c0f7ce17d',
|
|
68
|
+
'name': 'model1',
|
|
69
|
+
'submissions':
|
|
70
|
+
[{'filename': 'predictions-pPbLKSHGiR.csv',
|
|
71
|
+
'id': 'f2369b69-8c43-47aa-b4de-de3a9de5f52c'}],
|
|
72
|
+
'v2Stake': {'status': None, 'txHash': None}},
|
|
73
|
+
{'id': '881778ad-2ee9-4fb0-82b4-7b7c0f7ce17d',
|
|
74
|
+
'name': 'model2',
|
|
75
|
+
'submissions':
|
|
76
|
+
[{'filename': 'predictions-pPbPOWQNGiR.csv',
|
|
77
|
+
'id': '46a62500-87c7-4d7c-98ad-b743037e8cfd'}],
|
|
78
|
+
'v2Stake': {'status': None, 'txHash': None}}],
|
|
79
|
+
'status': 'VERIFIED',
|
|
80
|
+
'username': 'username1',
|
|
81
|
+
'walletAddress': '0x0000000000000000000000000000'}
|
|
82
|
+
|
|
83
|
+
data = {'data': {'account': account}}
|
|
84
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
85
|
+
res = api.get_account()
|
|
86
|
+
assert isinstance(res, dict)
|
|
87
|
+
assert len(res.get('models')) == 2
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@responses.activate
|
|
91
|
+
def test_get_models(api):
|
|
92
|
+
api.token = ("", "")
|
|
93
|
+
models_list = [
|
|
94
|
+
{"name": "model_x", "id": "95b0d9e2-c901-4f2b-9c98-24138b0bd706",
|
|
95
|
+
"tournament": 0},
|
|
96
|
+
{"name": "model_y", "id": "2c6d63a4-013f-42d1-bbaf-bf35725d29f7",
|
|
97
|
+
"tournament": 0}]
|
|
98
|
+
data = {'data': {'account': {'models': models_list}}}
|
|
99
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
100
|
+
models = api.get_models()
|
|
101
|
+
assert sorted(models.keys()) == ['model_x', 'model_y']
|
|
102
|
+
assert sorted(models.values()) == ['2c6d63a4-013f-42d1-bbaf-bf35725d29f7',
|
|
103
|
+
'95b0d9e2-c901-4f2b-9c98-24138b0bd706']
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@responses.activate
|
|
107
|
+
def test_set_submission_webhook(api):
|
|
108
|
+
api.token = ("", "")
|
|
109
|
+
data = {
|
|
110
|
+
"data": {
|
|
111
|
+
"setSubmissionWebhook": "true"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
115
|
+
res = api.set_submission_webhook(
|
|
116
|
+
'2c6d63a4-013f-42d1-bbaf-bf35725d29f7',
|
|
117
|
+
'https://triggerurl'
|
|
118
|
+
)
|
|
119
|
+
assert res
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@responses.activate
|
|
123
|
+
def test_v3_stake_auth(api):
|
|
124
|
+
api.token = ("", "")
|
|
125
|
+
data = {"data": {"v3StakeAuth": {
|
|
126
|
+
"authorizationSigner": "0xsigner",
|
|
127
|
+
"authorizationDigest": "0xdigest",
|
|
128
|
+
"chainId": "11155111",
|
|
129
|
+
"deadline": "1770000000",
|
|
130
|
+
"maxAmount": "25",
|
|
131
|
+
"modelId": "0xmodel",
|
|
132
|
+
"nmrAddress": "0xnmr",
|
|
133
|
+
"nonce": "0",
|
|
134
|
+
"roundId": "4321",
|
|
135
|
+
"signature": "0x1234",
|
|
136
|
+
"staker": "0xstaker",
|
|
137
|
+
"stakingAddress": "0xstaking",
|
|
138
|
+
"submissionId": "submission-id",
|
|
139
|
+
"submissionHash": "0xhash",
|
|
140
|
+
"tournamentId": "8",
|
|
141
|
+
}}}
|
|
142
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
143
|
+
|
|
144
|
+
result = api.v3_stake_auth("submission-id", "0xstaker", amount=25)
|
|
145
|
+
|
|
146
|
+
body = json.loads(responses.calls[0].request.body)
|
|
147
|
+
assert "v3StakeAuth" in body["query"]
|
|
148
|
+
assert body["variables"]["submissionId"] == "submission-id"
|
|
149
|
+
assert body["variables"]["maxAmount"] == "25"
|
|
150
|
+
assert "maxAmount" in body["query"]
|
|
151
|
+
assert result["maxAmount"] == "25"
|
|
152
|
+
assert result["amount"] == "25"
|
|
153
|
+
assert result["authorizationDigest"] == "0xdigest"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@responses.activate
|
|
157
|
+
def test_v3_stake_auth_accepts_max_amount(api):
|
|
158
|
+
api.token = ("", "")
|
|
159
|
+
data = {"data": {"v3StakeAuth": {
|
|
160
|
+
"authorizationSigner": "0xsigner",
|
|
161
|
+
"authorizationDigest": "0xdigest",
|
|
162
|
+
"chainId": "11155111",
|
|
163
|
+
"deadline": "1770000000",
|
|
164
|
+
"maxAmount": "30",
|
|
165
|
+
"modelId": "0xmodel",
|
|
166
|
+
"nmrAddress": "0xnmr",
|
|
167
|
+
"nonce": "0",
|
|
168
|
+
"roundId": "4321",
|
|
169
|
+
"signature": "0x1234",
|
|
170
|
+
"staker": "0xstaker",
|
|
171
|
+
"stakingAddress": "0xstaking",
|
|
172
|
+
"submissionId": "submission-id",
|
|
173
|
+
"submissionHash": "0xhash",
|
|
174
|
+
"tournamentId": "8",
|
|
175
|
+
}}}
|
|
176
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
177
|
+
|
|
178
|
+
result = api.v3_stake_auth(
|
|
179
|
+
"submission-id",
|
|
180
|
+
"0xstaker",
|
|
181
|
+
max_amount="30",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
body = json.loads(responses.calls[0].request.body)
|
|
185
|
+
assert body["variables"]["maxAmount"] == "30"
|
|
186
|
+
assert result["maxAmount"] == "30"
|
|
187
|
+
assert result["amount"] == "30"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@responses.activate
|
|
191
|
+
def test_v3_stake_config(api):
|
|
192
|
+
api.token = ("", "")
|
|
193
|
+
data = {"data": {"v3StakeConfig": {
|
|
194
|
+
"address": "0xstaking",
|
|
195
|
+
"authorizationSigner": "0xsigner",
|
|
196
|
+
"nmrAddress": "0xnmr",
|
|
197
|
+
"owner": "0xowner",
|
|
198
|
+
"paused": False,
|
|
199
|
+
"pendingOwner": "0xpending",
|
|
200
|
+
"serviceWallet": "0xservice",
|
|
201
|
+
}}}
|
|
202
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
203
|
+
|
|
204
|
+
result = api.v3_stake_config()
|
|
205
|
+
|
|
206
|
+
body = json.loads(responses.calls[0].request.body)
|
|
207
|
+
assert "v3StakeConfig" in body["query"]
|
|
208
|
+
assert result["authorizationSigner"] == "0xsigner"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@responses.activate
|
|
212
|
+
def test_v3_stake_round(api):
|
|
213
|
+
api.token = ("", "")
|
|
214
|
+
data = {"data": {"v3StakeRound": {
|
|
215
|
+
"closeTime": "1",
|
|
216
|
+
"merkleRoot": "0xroot",
|
|
217
|
+
"openTime": "0",
|
|
218
|
+
"payoutFactor": "0.5",
|
|
219
|
+
"remainingBurn": "0",
|
|
220
|
+
"remainingPayout": "2",
|
|
221
|
+
"resolveTime": "2",
|
|
222
|
+
"resolved": False,
|
|
223
|
+
"roundId": "4321",
|
|
224
|
+
"stakeCap": "100",
|
|
225
|
+
"stakeThreshold": "10",
|
|
226
|
+
"state": "open",
|
|
227
|
+
"totalPayout": "2",
|
|
228
|
+
"totalStaked": "50",
|
|
229
|
+
"tournamentId": "8",
|
|
230
|
+
}}}
|
|
231
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
232
|
+
|
|
233
|
+
result = api.v3_stake_round(4321)
|
|
234
|
+
|
|
235
|
+
body = json.loads(responses.calls[0].request.body)
|
|
236
|
+
assert "v3StakeRound" in body["query"]
|
|
237
|
+
assert body["variables"]["roundId"] == "4321"
|
|
238
|
+
assert result["roundId"] == "4321"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@responses.activate
|
|
242
|
+
def test_v3_stake_claim(api):
|
|
243
|
+
api.token = ("", "")
|
|
244
|
+
data = {"data": {"v3StakeClaim": {
|
|
245
|
+
"apiModelId": "api-model-id",
|
|
246
|
+
"burnAmountWei": "0",
|
|
247
|
+
"merkleRoot": "0xroot",
|
|
248
|
+
"modelId": "0xmodel",
|
|
249
|
+
"payoutAmountWei": "2000000000000000000",
|
|
250
|
+
"proof": ["0xproof"],
|
|
251
|
+
"roundId": "4321",
|
|
252
|
+
"staker": "0xstaker",
|
|
253
|
+
"submissionId": "submission-id",
|
|
254
|
+
"tournamentId": "8",
|
|
255
|
+
}}}
|
|
256
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
257
|
+
|
|
258
|
+
result = api.v3_stake_claim(4321, "api-model-id", "0xstaker")
|
|
259
|
+
|
|
260
|
+
body = json.loads(responses.calls[0].request.body)
|
|
261
|
+
assert "v3StakeClaim" in body["query"]
|
|
262
|
+
assert body["variables"]["roundId"] == "4321"
|
|
263
|
+
assert body["variables"]["modelId"] == "api-model-id"
|
|
264
|
+
assert result["proof"] == ["0xproof"]
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import pytest
|
|
3
|
-
import responses
|
|
4
|
-
|
|
5
|
-
from numerapi import base_api
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@pytest.fixture(scope='function', name="api")
|
|
9
|
-
def api_fixture():
|
|
10
|
-
api = base_api.Api(verbosity='DEBUG')
|
|
11
|
-
return api
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_NumerAPI():
|
|
15
|
-
# invalid log level should raise
|
|
16
|
-
with pytest.raises(AttributeError):
|
|
17
|
-
base_api.Api(verbosity="FOO")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test__login(api):
|
|
21
|
-
# passing only one of public_id and secret_key is not enough
|
|
22
|
-
api._login(public_id="foo", secret_key=None)
|
|
23
|
-
assert api.token is None
|
|
24
|
-
api._login(public_id=None, secret_key="bar")
|
|
25
|
-
assert api.token is None
|
|
26
|
-
# passing both works
|
|
27
|
-
api._login(public_id="foo", secret_key="bar")
|
|
28
|
-
assert api.token == ("foo", "bar")
|
|
29
|
-
|
|
30
|
-
# using env variables
|
|
31
|
-
os.environ["NUMERAI_SECRET_KEY"] = "key"
|
|
32
|
-
os.environ["NUMERAI_PUBLIC_ID"] = "id"
|
|
33
|
-
api._login()
|
|
34
|
-
assert api.token == ("id", "key")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_raw_query(api):
|
|
38
|
-
query = "query {latestNmrPrice {priceUsd}}"
|
|
39
|
-
result = api.raw_query(query)
|
|
40
|
-
assert isinstance(result, dict)
|
|
41
|
-
assert "data" in result
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@responses.activate
|
|
45
|
-
def test_get_account(api):
|
|
46
|
-
api.token = ("", "")
|
|
47
|
-
account = {'apiTokens': [{'name': 'uploads',
|
|
48
|
-
'public_id': 'AAA',
|
|
49
|
-
'scopes': ['upload_submission']},
|
|
50
|
-
{'name': '_internal_default',
|
|
51
|
-
'public_id': 'BBB',
|
|
52
|
-
'scopes': ['upload_submission',
|
|
53
|
-
'read_submission_info',
|
|
54
|
-
'read_user_info']},
|
|
55
|
-
{'name': 'all',
|
|
56
|
-
'public_id': 'CCC',
|
|
57
|
-
'scopes': ['upload_submission',
|
|
58
|
-
'stake',
|
|
59
|
-
'read_submission_info',
|
|
60
|
-
'read_user_info']}],
|
|
61
|
-
'availableNmr': '1.010000000000000000',
|
|
62
|
-
'email': 'no-reply@eu83t4nncmxv3g2.xyz',
|
|
63
|
-
'id': '0c10a70a-a851-478f-a289-7a05fe397008',
|
|
64
|
-
'insertedAt': "2018-01-01 11:11:11",
|
|
65
|
-
'mfaEnabled': False,
|
|
66
|
-
'models': [{'id': '881778ad-2ee9-4fb0-82b4-7b7c0f7ce17d',
|
|
67
|
-
'name': 'model1',
|
|
68
|
-
'submissions':
|
|
69
|
-
[{'filename': 'predictions-pPbLKSHGiR.csv',
|
|
70
|
-
'id': 'f2369b69-8c43-47aa-b4de-de3a9de5f52c'}],
|
|
71
|
-
'v2Stake': {'status': None, 'txHash': None}},
|
|
72
|
-
{'id': '881778ad-2ee9-4fb0-82b4-7b7c0f7ce17d',
|
|
73
|
-
'name': 'model2',
|
|
74
|
-
'submissions':
|
|
75
|
-
[{'filename': 'predictions-pPbPOWQNGiR.csv',
|
|
76
|
-
'id': '46a62500-87c7-4d7c-98ad-b743037e8cfd'}],
|
|
77
|
-
'v2Stake': {'status': None, 'txHash': None}}],
|
|
78
|
-
'status': 'VERIFIED',
|
|
79
|
-
'username': 'username1',
|
|
80
|
-
'walletAddress': '0x0000000000000000000000000000'}
|
|
81
|
-
|
|
82
|
-
data = {'data': {'account': account}}
|
|
83
|
-
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
84
|
-
res = api.get_account()
|
|
85
|
-
assert isinstance(res, dict)
|
|
86
|
-
assert len(res.get('models')) == 2
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@responses.activate
|
|
90
|
-
def test_get_models(api):
|
|
91
|
-
api.token = ("", "")
|
|
92
|
-
models_list = [
|
|
93
|
-
{"name": "model_x", "id": "95b0d9e2-c901-4f2b-9c98-24138b0bd706",
|
|
94
|
-
"tournament": 0},
|
|
95
|
-
{"name": "model_y", "id": "2c6d63a4-013f-42d1-bbaf-bf35725d29f7",
|
|
96
|
-
"tournament": 0}]
|
|
97
|
-
data = {'data': {'account': {'models': models_list}}}
|
|
98
|
-
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
99
|
-
models = api.get_models()
|
|
100
|
-
assert sorted(models.keys()) == ['model_x', 'model_y']
|
|
101
|
-
assert sorted(models.values()) == ['2c6d63a4-013f-42d1-bbaf-bf35725d29f7',
|
|
102
|
-
'95b0d9e2-c901-4f2b-9c98-24138b0bd706']
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@responses.activate
|
|
106
|
-
def test_set_submission_webhook(api):
|
|
107
|
-
api.token = ("", "")
|
|
108
|
-
data = {
|
|
109
|
-
"data": {
|
|
110
|
-
"setSubmissionWebhook": "true"
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
114
|
-
res = api.set_submission_webhook(
|
|
115
|
-
'2c6d63a4-013f-42d1-bbaf-bf35725d29f7',
|
|
116
|
-
'https://triggerurl'
|
|
117
|
-
)
|
|
118
|
-
assert res
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|