safeheron-api-sdk-python 1.1.18__py3-none-any.whl

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.
@@ -0,0 +1,49 @@
1
+ from safeheron_api_sdk_python.client import *
2
+
3
+ class ResendWebhookRequest:
4
+ def __init__(self):
5
+ # Supported events:
6
+ # TRANSACTION
7
+ # MPC_SIGN
8
+ # WEB3_SIGN
9
+ self.category = None
10
+ # Transaction key
11
+ self.txKey = None
12
+
13
+ class ResendFailedRequest:
14
+ def __init__(self):
15
+ # Start time in UNIX timestamp (ms). The time interval [startTime, endTime] is up to 1 hour
16
+ self.startTime = None
17
+ # End time in UNIX timestamp (ms). The time interval [startTime, endTime] is up to 1 hour
18
+ self.endTime = None
19
+
20
+
21
+ class WebhookApi:
22
+
23
+ def __init__(self, config):
24
+ self.api_client = Client(config)
25
+ # Re-push Transaction Webhook Events
26
+ # Re-push the transaction webhook events generated in the last month. In certain situations, such as network jitter or your system being unavailable to receive webhook events, you may miss receiving notifications. You can use this interface to notify Safeheron to re-push the webhook events. It is important to note that for a single transaction, only the last webhook event will be re-pushed. For example, during the lifecycle of a transaction, multiple TRANSACTION_STATUS_CHANGED webhook events such as BROADCASTING, CONFIRMING, COMPLETED, etc., may be generated. The re-push will only send the last webhook event recorded by the current system. Re-pushing is supported for the following types of webhook events:
27
+ # Events related to asset transactions, including TRANSACTION_CREATED and TRANSACTION_STATUS_CHANGED.
28
+ # Events related to MPC signing, including MPC_SIGN_CREATED and MPC_SIGN_STATUS_CHANGED.
29
+ # Events related to Web3, including WEB3_SIGN_CREATED and WEB3_SIGN_STATUS_CHANGED.
30
+ # More types of webhook events will be supported in the future.
31
+ def resend_webhook(self, request: ResendWebhookRequest):
32
+ return self.api_client.send_request(request, '/v1/webhook/resend')
33
+
34
+ # Push All Failed Webhook Events
35
+ # Resend all webhook events that failed to push within any duration not exceeding 1 hour in the past 7 days (limited to 1 call every 10 minutes). In certain situations, such as network failures or your system being unavailable to receive webhook events, you may miss receiving notifications. Once the issue is resolved, you can use this interface to notify Safeheron to resend all webhook events that failed to push within a certain period. Support re-pushing the following types of webhook events:
36
+ # TRANSACTION_CREATED
37
+ # TRANSACTION_STATUS_CHANGED
38
+ # MPC_SIGN_CREATED
39
+ # MPC_SIGN_STATUS_CHANGED
40
+ # WEB3_SIGN_CREATED
41
+ # WEB3_SIGN_STATUS_CHANGED
42
+ # ILLEGAL_IP_REQUEST
43
+ # NO_MATCHING_TRANSACTION_POLICY
44
+ # More types of webhook events will be supported in the future.
45
+ # Please note that when re-pushing all failed webhook events, your system needs to avoid rollback issues. For example, during the lifecycle of a transaction, multiple TRANSACTION_STATUS_CHANGED webhook events may be generated, such as BROADCASTING, CONFIRMING, and COMPLETED. If your system did not receive the CONFIRMING event due to network issues but did receive the COMPLETED event, using this interface would cause Safeheron to attempt resending the CONFIRMING event to your system. Your system needs to ensure that the transaction does not incorrectly revert from the COMPLETED status back to the CONFIRMING status.
46
+ def resend_failed(self, request: ResendFailedRequest):
47
+ return self.api_client.send_request(request, '/v1/webhook/resend/failed')
48
+
49
+
@@ -0,0 +1,154 @@
1
+ from safeheron_api_sdk_python.client import *
2
+
3
+
4
+ class ListWhitelistRequest:
5
+ def __init__(self):
6
+ # Query page direction, NEXT by default
7
+ self.direct = None
8
+ # The number of items to retrieve at a time, default max value is 500
9
+ self.limit = None
10
+ # Txkey of the first transaction record. If the first page has no value, provide the txKey of the last transaction record from the previous result
11
+ self.fromId = None
12
+ # The supported public blockchains for whitelist addresses are:
13
+ # EVM: Ethereum-compatible public chain networks or Layer 2 addresses that can receive its native token and other tokens
14
+ # Bitcoin: Bitcoin mainnet addresses that can receive Bitcoin
15
+ # Bitcoin Cash: Bitcoin Cash network, which can receive BCH
16
+ # Dash: anonymous Dash network, which can receive DASH
17
+ # TRON: Tron mainnet addresses, which can receive TRX and TRC20 tokens, such as USDT and USDC
18
+ # NEAR: NEAR mainnet, which can receive native token NEAR
19
+ # Filecoin: Receive Filecoin native token FIL, but does not support receiving FIL or tokens from the FVM network
20
+ # Sui: Sui mainnet, which can receive native token Sui and other tokens
21
+ # Aptos: Aptos mainnet, which only supports receiving native token Aptos and does not support other tokens yet
22
+ # Solana: Solana mainnet, which can receive native token SOL and other tokens
23
+ # Bitcoin Testnet: Bitcoin testnet, which can receive Bitcoin testnet ass
24
+ self.chainType = None
25
+ # Whitelist status
26
+ # AUDIT: pending approval
27
+ # APPROVED: active
28
+ # REJECTED: rejected
29
+ self.whitelistStatus = None
30
+ # Start time for creating a whitelist in UNIX timestamp (ms) (If no value is provided, the default value is createTimeMax minus 24 hours)
31
+ self.createTimeMin = None
32
+ # End time for creating a whitelist in UNIX timestamp (ms) (If no value is provided, the default value is the current UTC time)
33
+ self.createTimeMax = None
34
+
35
+
36
+ class OneWhitelistRequest:
37
+ def __init__(self):
38
+ # Whitelist unique identifier. It is required if address is not provided. If both are provided, the whitelistKey takes precedence
39
+ self.whitelistKey = None
40
+ # Whitelist address. It is required if whitelistKey is not provided and please make sure the provided address is correct
41
+ self.address = None
42
+
43
+
44
+ class CreateWhitelistRequest:
45
+ def __init__(self):
46
+ # Whitelist unique name, 20 characters max
47
+ self.whitelistName = None
48
+ # The supported public blockchains for whitelist addresses are:
49
+ # EVM: Ethereum-compatible public chain networks or Layer 2 addresses that can receive its native token and other tokens
50
+ # Bitcoin: Bitcoin mainnet addresses that can receive Bitcoin
51
+ # Bitcoin Cash: Bitcoin Cash network, which can receive BCH
52
+ # Dash: anonymous Dash network, which can receive DASH
53
+ # TRON: Tron mainnet addresses, which can receive TRX and TRC20 tokens, such as USDT and USDC
54
+ # NEAR: NEAR mainnet, which can receive native token NEAR
55
+ # Filecoin: Receive Filecoin native token FIL, but does not support receiving FIL or tokens from the FVM network
56
+ # Sui: Sui mainnet, which can receive native token Sui and other tokens
57
+ # Aptos: Aptos mainnet, which only supports receiving native token Aptos and does not support other tokens yet
58
+ # Solana: Solana mainnet, which can receive native token SOL and other tokens
59
+ # Bitcoin Testnet: Bitcoin testnet, which can receive Bitcoin testnet assets
60
+ self.chainType = None
61
+ # Public blockchain address and the address format needs to meet the requirements of the chain
62
+ self.address = None
63
+ # The memo (up to 20 characters) for the destination address, also known as a comment or tag. For the following networks, if a destination address memo was set initially, a memo matching the one in the transaction record must be provided
64
+ # TON: TON mainnet
65
+ # TON_TESTNET: TON testnet
66
+ self.memo = None
67
+ # Visibility status in Safeheron App and Web Console
68
+ # False: Visible by default
69
+ # True: Invisible; the invisible whitelist can only be managed and used through the API, such as querying, modifying, and using the whitelist as the destination address when initiating transactions
70
+ self.hiddenOnUI = None
71
+
72
+
73
+ class CreateFromTransactionWhitelistRequest:
74
+ def __init__(self):
75
+ # Whitelist unique name, 20 characters max
76
+ self.whitelistName = None
77
+ # Transaction key
78
+ self.txKey = None
79
+ # The destination address in the transaction record; case-sensitive
80
+ self.destinationAddress = None
81
+ # The memo (up to 20 characters) for the destination address, also known as a comment or tag. For the following networks, if a destination address memo was set initially, a memo matching the one in the transaction record must be provided
82
+ # TON: TON mainnet
83
+ # TON_TESTNET: TON testnet
84
+ self.memo = None
85
+ # Visibility status in Safeheron App and Web Console
86
+ # False: Visible by default
87
+ # True: Invisible; the invisible whitelist can only be managed and used through the API, such as querying, modifying, and using the whitelist as the destination address when initiating transactions
88
+ self.hiddenOnUI = None
89
+
90
+
91
+ class EditWhitelistRequest:
92
+ def __init__(self):
93
+ # Whitelist unique identifier
94
+ self.whitelistKey = None
95
+ # Whitelist unique name, 20 characters max
96
+ self.whitelistName = None
97
+ # Public blockchain address and the address format needs to meet the requirements of the chain
98
+ self.address = None
99
+ # The memo (up to 20 characters) for the destination address, also known as a comment or tag. For the following networks, if a destination address memo was set initially, a memo matching the one in the transaction record must be provided
100
+ # TON: TON mainnet
101
+ # TON_TESTNET: TON testnet
102
+ self.memo = None
103
+ # When the whitelist is involved in a transaction approval policy, modifications will result in the new whitelist being directly applied to the approval policy. False by default, meaning that when involved in a transaction approval policy, it will not be modified.
104
+ self.force = None
105
+
106
+
107
+ class DeleteWhitelistRequest:
108
+ def __init__(self):
109
+ # Whitelist unique identifier
110
+ self.whitelistKey = None
111
+
112
+
113
+ class WhitelistApi:
114
+
115
+ def __init__(self, config):
116
+ self.api_client = Client(config)
117
+
118
+ # List Whitelist Data
119
+ # Paginate the whitelist data based on the query criteria.
120
+ def list_whitelist(self, request: ListWhitelistRequest):
121
+ return self.api_client.send_request(request, '/v1/whitelist/list')
122
+
123
+ # Retrieve a Single Whitelist
124
+ # * Retrieve the data of a whitelist.
125
+ def one_whitelist(self, request: OneWhitelistRequest):
126
+ return self.api_client.send_request(request, '/v1/whitelist/one')
127
+
128
+ # Create a Whitelist
129
+ # Add a new whitelisted address. The newly added address needs to be approved in the Safeheron App before it becomes effective. The approval details are as follows:
130
+ # Admin approval: If a custom whitelist approval process is not set, it will become effective after being approved by the team admins according to the team's decision-making process.
131
+ # Custom whitelist approval: If a whitelist approval process is set, it will become effective after being approved according to the process.
132
+ def create_whitelist(self, request: CreateWhitelistRequest):
133
+ return self.api_client.send_request(request, '/v1/whitelist/create')
134
+
135
+ # Create a Whitelist Based on a Transaction
136
+ # Whitelist the transaction's destination address when the transaction meets the following conditions:
137
+ #
138
+ # A transfer transaction from an asset wallet; Web3 wallet transactions or MPC Sign transactions are not supported.
139
+ # The transaction is in a completed state as COMPLETED.
140
+ # The transaction's destination address is a one-time address.
141
+ def create_from_transaction_whitelist(self, request: CreateFromTransactionWhitelistRequest):
142
+ return self.api_client.send_request(request, '/v1/whitelist/createFromTransaction')
143
+
144
+ # Modify a Whitelist
145
+ # Modify a whitelist based on its unique identifier. The whitelist only supports modifying its name and address; whitelists pending for approval cannot be modified. After modifying the whitelist, it needs to be reviewed and approved in the Safeheron App before it becomes effective. The approval details are as follows:
146
+ # Admin approval: If a custom whitelist approval process is not set, it will become effective after being approved by the team admins according to the team's decision-making process.
147
+ # Custom whitelist approval: If a whitelist approval process is set, it will become effective after being approved according to the process.
148
+ def edit_whitelist(self, request: EditWhitelistRequest):
149
+ return self.api_client.send_request(request, '/v1/whitelist/edit')
150
+
151
+ # Delete a Whitelist
152
+ # To delete a whitelisted address, note that no approval is required for deletion. If a whitelisted address that is under approval is deleted, the approval task will also be automatically cancelled.
153
+ def delete_whitelist(self, request: DeleteWhitelistRequest):
154
+ return self.api_client.send_request(request, '/v1/whitelist/delete')
@@ -0,0 +1,28 @@
1
+ from safeheron_api_sdk_python.tools import *
2
+
3
+
4
+ class Client:
5
+
6
+ def __init__(self, config):
7
+ self.api_key = config['apiKey']
8
+ self.platform_pub_key = config['safeheronPublicKey']
9
+ self.base_url = config['baseUrl']
10
+ if config.get('requestTimeout'):
11
+ self.requestTimeout = config['requestTimeout'] / 1000
12
+ else:
13
+ self.requestTimeout = 20
14
+ if config.get('privateKey'):
15
+ self.use_private_key = PEM_PRIVATE_HEAD + config['privateKey'] + PEM_PRIVATE_END
16
+ if config.get('privateKeyPemFile'):
17
+ self.use_private_key = load_rsa_private_key(config['privateKeyPemFile'])
18
+
19
+ def send_request(self, request, uri):
20
+ req = encrypt_request(self.api_key, request, self.platform_pub_key, self.use_private_key)
21
+ res = self.execution(req, uri)
22
+ res.raise_for_status()
23
+ res = res.json()
24
+ return decrypt_response(res, self.platform_pub_key, self.use_private_key)
25
+
26
+ def execution(self, request, uri):
27
+ return requests.post(self.base_url + uri, data=json.dumps(request), headers={"Content-Type": "application/json"},
28
+ timeout=self.requestTimeout)
@@ -0,0 +1,185 @@
1
+ from safeheron_api_sdk_python.tools import *
2
+
3
+
4
+ class CoSignerResponse:
5
+ def __init__(self):
6
+ # approve
7
+ self.approve = None
8
+ # txKey
9
+ self.txKey = None
10
+
11
+
12
+ class CoSignerResponseV3:
13
+ def __init__(self):
14
+ # action
15
+ self.action = None
16
+ # approvalId
17
+ self.approvalId = None
18
+
19
+
20
+ class CoSignerConverter:
21
+
22
+ def __init__(self, config):
23
+ # Supports both coSignerPubKey and apiPublKey
24
+ self.co_signer_pub_key = config.get('coSignerPubKey') or config.get('apiPubKey')
25
+
26
+ # Supports both approvalCallbackServicePrivateKey and bizPrivKey
27
+ private_key = config.get('approvalCallbackServicePrivateKey') or config.get('bizPrivKey')
28
+ if private_key:
29
+ self.approval_callback_service_private_key = PEM_PRIVATE_HEAD + private_key + PEM_PRIVATE_END
30
+
31
+ # Supports both approvalCallbackServicePrivateKeyPemFile and bizPrivKeyPemFile
32
+ pem_file = config.get('approvalCallbackServicePrivateKeyPemFile') or config.get('bizPrivKeyPemFile')
33
+ if pem_file:
34
+ self.approval_callback_service_private_key = load_rsa_private_key(pem_file)
35
+
36
+ def request_convert(self, co_signer_call_back):
37
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + self.co_signer_pub_key + PEM_PUBLIC_END)
38
+ api_user_rsa_sk = get_rsa_key(self.approval_callback_service_private_key)
39
+ required_keys = {
40
+ 'key',
41
+ 'sig',
42
+ 'bizContent',
43
+ 'timestamp',
44
+ }
45
+
46
+ missing_keys = required_keys.difference(co_signer_call_back.keys())
47
+ if missing_keys:
48
+ raise Exception(co_signer_call_back)
49
+
50
+ # 1 rsa verify
51
+ rsaType = ''
52
+ if "rsaType" in co_signer_call_back:
53
+ rsaType = co_signer_call_back.pop('rsaType')
54
+
55
+ aesType = ''
56
+ if "aesType" in co_signer_call_back:
57
+ aesType = co_signer_call_back.pop('aesType')
58
+
59
+ sig = co_signer_call_back.pop('sig')
60
+ need_sign_message = sort_request(co_signer_call_back)
61
+ v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
62
+ if not v:
63
+ raise Exception("rsa verify: false")
64
+
65
+ # 2 get aes key and iv
66
+ key = co_signer_call_back.pop('key')
67
+ if ECB_OAEP_TYPE == rsaType:
68
+ aes_data = rsa_oaep_decrypt(api_user_rsa_sk, key)
69
+ else:
70
+ aes_data = rsa_decrypt(api_user_rsa_sk, key)
71
+ aes_key = aes_data[0:32]
72
+ aes_iv = aes_data[32:48]
73
+
74
+ # 3 aes decrypt data, get response data
75
+ if GCM_TYPE == aesType:
76
+ r = aes_gcm_decrypt(aes_key, aes_iv, b64decode(co_signer_call_back['bizContent']))
77
+ else:
78
+ r = aes_decrypt(aes_key, aes_iv, b64decode(co_signer_call_back['bizContent']))
79
+ # response_dict['bizContent'] = json.loads(r.decode())
80
+
81
+ return json.loads(r.decode())
82
+
83
+ def request_v3_convert(self, co_signer_call_back):
84
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + self.co_signer_pub_key + PEM_PUBLIC_END)
85
+ required_keys = {
86
+ 'version',
87
+ 'sig',
88
+ 'bizContent',
89
+ 'timestamp',
90
+ }
91
+
92
+ missing_keys = required_keys.difference(co_signer_call_back.keys())
93
+ if missing_keys:
94
+ raise Exception(co_signer_call_back)
95
+ co_signer_call_back['version'] = 'v3'
96
+ sig = co_signer_call_back.pop('sig')
97
+
98
+ need_sign_message = sort_request(co_signer_call_back)
99
+ v = rsa_pass_verify(platform_rsa_pk, need_sign_message, sig)
100
+ if not v:
101
+ raise Exception("rsa verify: false")
102
+ return json.loads(b64decode(co_signer_call_back['bizContent']).decode())
103
+
104
+ def response_v3_converter(self, co_signer_response: CoSignerResponseV3):
105
+ api_user_rsa_sk = get_rsa_key(self.approval_callback_service_private_key)
106
+ ret = dict()
107
+ response_data = json.dumps(co_signer_response.__dict__).replace('\'', '\"').replace('\n', '').encode('utf-8')
108
+
109
+ if response_data is not None:
110
+ ret['bizContent'] = b64encode(response_data).decode()
111
+
112
+ ret['timestamp'] = str(int(time.time() * 1000))
113
+ ret['code'] = str('200')
114
+ ret['version'] = str('v3')
115
+ ret['message'] = str('SUCCESS')
116
+
117
+ # 4 sign request
118
+ need_sign_message = sort_request(ret)
119
+ ret['sig'] = rsa_pss_sign(api_user_rsa_sk, need_sign_message)
120
+ return ret
121
+
122
+
123
+ # It has been Deprecated,Please use convertCoSignerResponseWithNewCryptoType
124
+ def response_converter(self, co_signer_response: CoSignerResponse):
125
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + self.co_signer_pub_key + PEM_PUBLIC_END)
126
+ api_user_rsa_sk = get_rsa_key(self.approval_callback_service_private_key)
127
+
128
+ ret = dict()
129
+
130
+ # prepare aes key and iv
131
+ aes_key = get_random_bytes(32)
132
+ aes_iv = get_random_bytes(16)
133
+ response_data = json.dumps(co_signer_response.__dict__).replace('\'', '\"').replace('\n', '').encode('utf-8')
134
+
135
+ # 1 rsa encrypt aes key + iv
136
+ aes_data = aes_key + aes_iv
137
+ ret['key'] = rsa_encrypt(platform_rsa_pk, aes_data)
138
+
139
+ # 2 aes encrypt request data
140
+ if response_data is not None:
141
+ aes_encrypted_bytes = aes_encrypt(aes_key, aes_iv, response_data)
142
+ ret['bizContent'] = b64encode(aes_encrypted_bytes).decode()
143
+
144
+ # 3 set timestamp
145
+ ret['timestamp'] = str(int(time.time() * 1000))
146
+ ret['code'] = str('200')
147
+ ret['message'] = str('SUCCESS')
148
+
149
+ # 4 sign request
150
+ need_sign_message = sort_request(ret)
151
+ ret['sig'] = rsa_sign(api_user_rsa_sk, need_sign_message)
152
+
153
+ return ret
154
+
155
+ def response_converter_with_new_crypto_type(self, co_signer_response: CoSignerResponse):
156
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + self.co_signer_pub_key + PEM_PUBLIC_END)
157
+ api_user_rsa_sk = get_rsa_key(self.approval_callback_service_private_key)
158
+
159
+ ret = dict()
160
+
161
+ # prepare aes key and iv
162
+ aes_key = get_random_bytes(32)
163
+ aes_iv = get_random_bytes(16)
164
+ response_data = json.dumps(co_signer_response.__dict__).replace('\'', '\"').replace('\n', '').encode('utf-8')
165
+
166
+ # 1 rsa encrypt aes key + iv
167
+ aes_data = aes_key + aes_iv
168
+ ret['key'] = rsa_oaep_encrypt(platform_rsa_pk, aes_data)
169
+
170
+ # 2 aes encrypt request data
171
+ if response_data is not None:
172
+ aes_encrypted_bytes = aes_gcm_encrypt(aes_key, aes_iv, response_data)
173
+ ret['bizContent'] = b64encode(aes_encrypted_bytes).decode()
174
+
175
+ # 3 set timestamp
176
+ ret['timestamp'] = str(int(time.time() * 1000))
177
+ ret['code'] = str('200')
178
+ ret['message'] = str('SUCCESS')
179
+
180
+ # 4 sign request
181
+ need_sign_message = sort_request(ret)
182
+ ret['sig'] = rsa_sign(api_user_rsa_sk, need_sign_message)
183
+ ret['rsaType'] = ECB_OAEP_TYPE
184
+ ret['aesType'] = GCM_TYPE
185
+ return ret