safeheron-api-sdk-python 1.1.0__tar.gz → 1.1.2__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.
Files changed (22) hide show
  1. {safeheron_api_sdk_python-1.1.0/safeheron_api_sdk_python.egg-info → safeheron_api_sdk_python-1.1.2}/PKG-INFO +1 -1
  2. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/README.md +35 -0
  3. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python/api/account_api.py +32 -16
  4. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python/api/coin_api.py +6 -7
  5. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python/api/mpc_sign_api.py +4 -5
  6. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python/api/transaction_api.py +76 -10
  7. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python/api/web3_api.py +11 -12
  8. safeheron_api_sdk_python-1.1.2/safeheron_api_sdk_python/client.py +28 -0
  9. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python/cosigner/co_signer_converter.py +26 -15
  10. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python/tools.py +65 -7
  11. safeheron_api_sdk_python-1.1.2/safeheron_api_sdk_python/webhook/webhook_converter.py +61 -0
  12. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2/safeheron_api_sdk_python.egg-info}/PKG-INFO +1 -1
  13. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/setup.py +1 -1
  14. safeheron_api_sdk_python-1.1.0/safeheron_api_sdk_python/client.py +0 -28
  15. safeheron_api_sdk_python-1.1.0/safeheron_api_sdk_python/webhook/webhook_converter.py +0 -51
  16. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/LICENSE +0 -0
  17. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/README.rst +0 -0
  18. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python.egg-info/SOURCES.txt +0 -0
  19. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python.egg-info/dependency_links.txt +0 -0
  20. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python.egg-info/requires.txt +0 -0
  21. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/safeheron_api_sdk_python.egg-info/top_level.txt +0 -0
  22. {safeheron_api_sdk_python-1.1.0 → safeheron_api_sdk_python-1.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: safeheron_api_sdk_python
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: Python for Safeheron API
5
5
  Home-page: https://github.com/Safeheron/safeheron-api-sdk-python
6
6
  Author: safeheron
@@ -12,6 +12,35 @@
12
12
  $ pip install safeheron-api-sdk-python
13
13
  ```
14
14
 
15
+ # Usage
16
+ ```python
17
+ from safeheron_api_sdk_python.api.account_api import *
18
+ ```
19
+
20
+ > Take [`/v1/account/create`](https://docs.safeheron.com/api/index.html) as an example to explain, the complete code can be found in `demo/api_demo` directory
21
+
22
+ * Construct `config`
23
+ ```python
24
+ # You can get `ApiKey` and `SafeheronRsaPublicKey` from Safeheron Web Console: https://www.safeheron.com/console.
25
+ config={
26
+ 'apiKey': '5921aa306f*******368db9abf',
27
+ 'privateKeyPemFile': './my_private.pem',
28
+ 'safeheronPublicKey': 'MIICIjANBgkqhkiG9w0***********+zSdC+8eBdZyI7nMdPIj6xOhUCAwEAAQ==',
29
+ 'baseUrl': 'https://api.safeheron.vip',
30
+ 'RequestTimeout': 10000
31
+ }
32
+ ```
33
+ * Call `create_account` api with `config`
34
+ ```python
35
+ account_api = AccountApi(config)
36
+ param = CreateAccountRequest()
37
+ param.accountName = "accountNameTest"
38
+ res = account_api.create_account(param)
39
+ # Your code to process response
40
+ ...
41
+ ...
42
+ ```
43
+
15
44
  # Test
16
45
 
17
46
  ## Test Create Wallet Account
@@ -27,6 +56,8 @@ $ pip install safeheron-api-sdk-python
27
56
  safeheronPublicKey: MIICI****QuTOTECAwEAAQ==
28
57
  # Safeheron API url
29
58
  baseUrl: https://api.safeheron.vip
59
+ # RequestTimeout (Millisecond), Default: 10000
60
+ requestTimeout: 10000
30
61
  ```
31
62
  * Copy config to `config.yaml` file.
32
63
  ```bash
@@ -50,6 +81,8 @@ $ pip install safeheron-api-sdk-python
50
81
  safeheronPublicKey: MIICI****QuTOTECAwEAAQ==
51
82
  # Safeheron API url
52
83
  baseUrl: https://api.safeheron.vip
84
+ # RequestTimeout (Millisecond), Default: 10000
85
+ requestTimeout: 10000
53
86
  # Wallet Account key
54
87
  accountKey: account****5ecad40
55
88
  # To address
@@ -78,6 +111,8 @@ $ pip install safeheron-api-sdk-python
78
111
  safeheronPublicKey: MIICI****QuTOTECAwEAAQ==
79
112
  # Safeheron API url
80
113
  baseUrl: https://api.safeheron.vip
114
+ # RequestTimeout (Millisecond), Default: 10000
115
+ requestTimeout: 10000
81
116
  # Wallet Account key
82
117
  accountKey: account****5ecad40
83
118
  # Goerli testnet token address in wallet account
@@ -21,6 +21,13 @@ class ListAccountRequest:
21
21
  self.nameSuffix = None
22
22
 
23
23
 
24
+ class OneAccountRequest:
25
+ def __init__(self):
26
+ # Wallet account key
27
+ self.accountKey = None
28
+
29
+
30
+
24
31
  class CreateAccountRequest:
25
32
  def __init__(self):
26
33
  # Account name, within 30 characters
@@ -40,6 +47,11 @@ class BatchCreateAccountRequest:
40
47
  def __init__(self):
41
48
  # The prefix of wallet account name, 30 characters max
42
49
  self.accountName = None
50
+ # Display status in Safeheron App
51
+ # True: not display
52
+ # False: display
53
+ # Default: true
54
+ self.hiddenOnUI = None
43
55
  # Number of wallets to be created, greater than 0, less than 100
44
56
  self.count = None
45
57
  # Account tag
@@ -141,79 +153,83 @@ class BatchCreateAccountCoinUTXORequest:
141
153
  class AccountApi:
142
154
 
143
155
  def __init__(self, config):
144
- global api_client
145
- api_client = Client(config)
156
+ self.api_client = Client(config)
146
157
 
147
158
  # List Wallet Accounts
148
159
  # Filter wallet account lists in team according to different combinations of conditions.
149
160
  def list_accounts(self, request: ListAccountRequest):
150
- return api_client.send_request(request, '/v1/account/list')
161
+ return self.api_client.send_request(request, '/v1/account/list')
162
+
163
+ # Retrieve a Single Wallet Account
164
+ # Retrieve a single wallet account in the team by providing accountKey.
165
+ def one_accounts(self, request: OneAccountRequest):
166
+ return self.api_client.send_request(request, '/v1/account/one')
151
167
 
152
168
  # Create a new wallet account.
153
169
  def create_account(self, request: CreateAccountRequest):
154
- return api_client.send_request(request, '/v1/account/create')
170
+ return self.api_client.send_request(request, '/v1/account/create')
155
171
 
156
172
  # Batch Create Wallet Accounts V1
157
173
  # Generate a batch of wallet accounts based on a specified quantity. By default, the wallet accounts created in bulk will not be displayed in the Safeheron App. For optimal results, we recommend using the V2 version.
158
174
  def batchCreate_accountV1(self, request: BatchCreateAccountRequest):
159
- return api_client.send_request(request, '/v1/account/batch/create')
175
+ return self.api_client.send_request(request, '/v1/account/batch/create')
160
176
 
161
177
  # Batch Create Wallet Accounts V2
162
178
  # Generate a batch of wallet accounts based on a specified quantity. By default, the wallet accounts created in bulk will not be displayed in the Safeheron App.
163
179
  def batch_create_accountV2(self, request: BatchCreateAccountRequest):
164
- return api_client.send_request(request, '/v2/account/batch/create')
180
+ return self.api_client.send_request(request, '/v2/account/batch/create')
165
181
 
166
182
  # Change Display of Wallet Account in App
167
183
  # Change wallet account status in Safeheron App.
168
184
  def update_account_show_state(self, request: UpdateAccountShowStateRequest):
169
- return api_client.send_request(request, '/v1/account/update/show/state')
185
+ return self.api_client.send_request(request, '/v1/account/update/show/state')
170
186
 
171
187
  # Batch Label Wallet Accounts
172
188
  # Relabel a batch of wallet accounts.
173
189
  # Please note that it only supports to label wallets which are created by API. And, the wallets have been used to sweep the target account cannot be relabelled.
174
190
  def batch_update_account_tag(self, request: BatchUpdateAccountTagRequest):
175
- return api_client.send_request(request, '/v1/account/batch/update/tag')
191
+ return self.api_client.send_request(request, '/v1/account/batch/update/tag')
176
192
 
177
193
  # Add Coins to a Wallet Account
178
194
  # Add a new coin to your wallet account, while generating the default address group for the added coin. Once successfully completed, it will return the address information of the newly created default address group. In case the added currency already exists within the account, it will promptly return the existing default address group information for that coin.
179
195
  # In a wallet account, UTXO-based cryptocurrencies can have multiple address groups, while other types of cryptocurrencies usually have only one. To check whether a particular cryptocurrency supports the addition of multiple address groups, simply check the 'isMultipleAddress' parameter through the Coin List.
180
196
  def create_account_coin(self, request: CreateAccountCoinRequest):
181
- return api_client.send_request(request, '/v1/account/coin/create')
197
+ return self.api_client.send_request(request, '/v1/account/coin/create')
182
198
 
183
199
  # Batch Add Coins to Wallet Accounts
184
200
  # Bulk addition of specified coins to designated wallet accounts. And, it creates a default address group for each coin and returns the address information contained within the newly created default address group. If a wallet account already contains the currency being added, the function will return the default address group data for that existing coin.
185
201
  #
186
202
  def batch_create_account_coin(self, request: BatchCreateAccountCoinRequest):
187
- return api_client.send_request(request, '/v1/account/batch/coin/create')
203
+ return self.api_client.send_request(request, '/v1/account/batch/coin/create')
188
204
 
189
205
  # List Coins Within a Wallet Account
190
206
  # Retrieve a complete list of all coins associated with a wallet account, along with the default address group information for each coin.
191
207
  #
192
208
  def list_account_coin(self, request: ListAccountCoinRequest):
193
- return api_client.send_request(request, '/v1/account/coin/list')
209
+ return self.api_client.send_request(request, '/v1/account/coin/list')
194
210
 
195
211
  # List Coin Address Group of a Wallet Account
196
212
  # Retrieve all address groups for a coin within the wallet account.
197
213
  #
198
214
  def list_account_coin_address(self, request: ListAccountCoinAddressRequest):
199
- return api_client.send_request(request, '/v1/account/coin/address/list')
215
+ return self.api_client.send_request(request, '/v1/account/coin/address/list')
200
216
 
201
217
  # Retrieve The Balance of an Address
202
218
  # Retrieve the balance of a specific coin address.
203
219
  def info_account_coin_address(self, request: InfoAccountCoinAddressRequest):
204
- return api_client.send_request(request, '/v1/account/coin/address/info')
220
+ return self.api_client.send_request(request, '/v1/account/coin/address/info')
205
221
 
206
222
  # Rename Coin Address Group of a Wallet Account
207
223
  # Rename a coin address group of a wallet account.
208
224
  def rename_account_coin_address(self, request: RenameAccountCoinAddressRequest):
209
- return api_client.send_request(request, '/v1/account/coin/address/name')
225
+ return self.api_client.send_request(request, '/v1/account/coin/address/name')
210
226
 
211
227
  # Add Address Group for UTXO-Based Coin
212
228
  # Add a new address group for UTXO-based cryptocurrencies under a wallet account. If the coin does not exist, it will be added first, followed by the new address group. The function will return the details of the added address(es).
213
229
  def create_account_coin_address(self, request: CreateAccountCoinAddressRequest):
214
- return api_client.send_request(request, '/v1/account/coin/address/create')
230
+ return self.api_client.send_request(request, '/v1/account/coin/address/create')
215
231
 
216
232
  # Batch Add Address Groups for UTXO-Based Coin
217
233
  # For UTXO-based coins in a wallet account, it is possible to add multiple address groups to the account in bulk by specifying the wallet account and the desired number of address groups. The function will return the details of the added address groups. If the specified coin does not exist in the account, it will be added first, followed by the addition of the corresponding number of address groups.
218
234
  def batch_create_account_coin_UTXO(self, request: BatchCreateAccountCoinUTXORequest):
219
- return api_client.send_request(request, '/v1/account/coin/utxo/batch/create')
235
+ return self.api_client.send_request(request, '/v1/account/coin/utxo/batch/create')
@@ -40,30 +40,29 @@ class CoinBlockHeightRequest:
40
40
  class CoinApi:
41
41
 
42
42
  def __init__(self, config):
43
- global api_client
44
- api_client = Client(config)
43
+ self.api_client = Client(config)
45
44
 
46
45
  # Coin List
47
46
  # Retrieve the list of coins supported by Safeheron.
48
47
  def list_coin(self):
49
- return api_client.send_request(None, '/v1/coin/list')
48
+ return self.api_client.send_request(None, '/v1/coin/list')
50
49
 
51
50
  # Coin Maintenance List
52
51
  # Retrieve the information of coins under maintenance in Safeheron.
53
52
  def list_coin_maintain(self):
54
- return api_client.send_request(None, '/v1/coin/maintain/list')
53
+ return self.api_client.send_request(None, '/v1/coin/maintain/list')
55
54
 
56
55
  # Verify Coin Address
57
56
  # Verify the correctness of a cryptocurrency address based on the provided validation attributes.
58
57
  def check_coin_address(self, request: CheckCoinAddressRequest):
59
- return api_client.send_request(request, '/v1/coin/address/check')
58
+ return self.api_client.send_request(request, '/v1/coin/address/check')
60
59
 
61
60
  # Snapshot the Coin Balance
62
61
  # Safeheron takes and stores daily snapshots of balances based on the transaction block's creation time in GMT+8. Please note that the snapshot only keeps data within 30 days.
63
62
  def coin_balance_snapshot(self, request: CoinBalanceSnapshotRequest):
64
- return api_client.send_request(request, '/v1/coin/balance/snapshot')
63
+ return self.api_client.send_request(request, '/v1/coin/balance/snapshot')
65
64
 
66
65
  # Retrieve Current Block Height for Currency
67
66
  # Retrieve the current block height for a specific cryptocurrency by providing its key.
68
67
  def coin_block_height(self, request: CoinBlockHeightRequest):
69
- return api_client.send_request(request, '/v1/coin/block/height')
68
+ return self.api_client.send_request(request, '/v1/coin/block/height')
@@ -57,21 +57,20 @@ class ListMPCSignTransactionsRequest:
57
57
  class MPCSignApi:
58
58
 
59
59
  def __init__(self, config):
60
- global api_client
61
- api_client = Client(config)
60
+ self.api_client = Client(config)
62
61
 
63
62
  # Create an MPC Sign Transaction
64
63
  # Merchant can initiate MPC Sign via this interface. The merchant must first serialize the transaction data and generate a hash before using this interface to submit the hash and create a transaction. The resulting signature can be retrieved via the MPC Sign transaction interface or webhook. The merchant can proceed with the necessary follow-up processes to obtain the signature according to their specific needs.
65
64
  def create_mpc_sign_transactions(self, request: CreateMPCSignTransactionRequest):
66
65
  request.asDict()
67
- return api_client.send_request(request, '/v1/transactions/mpcsign/create')
66
+ return self.api_client.send_request(request, '/v1/transactions/mpcsign/create')
68
67
 
69
68
  # Retrieve a Single MPC Sign Transaction
70
69
  # To query a specific MPC Sign transaction, either customerRefId or txKey must be provided. If both parameters are provided, the query will be based on the txKey parameter.
71
70
  def one_mpc_sign_transactions(self, request: OneMPCSignTransactionsRequest):
72
- return api_client.send_request(request, '/v1/transactions/mpcsign/one')
71
+ return self.api_client.send_request(request, '/v1/transactions/mpcsign/one')
73
72
 
74
73
  # MPC Sign Transaction List
75
74
  # Filter MPC Sign transaction history by various conditions.
76
75
  def list_mpc_sign_transactions(self, request: ListMPCSignTransactionsRequest):
77
- return api_client.send_request(request, '/v1/transactions/mpcsign/list')
76
+ return self.api_client.send_request(request, '/v1/transactions/mpcsign/list')
@@ -136,6 +136,8 @@ class CreateTransactionRequest:
136
136
  self.failOnContract = None
137
137
  # Custom nonce
138
138
  self.nonce = None
139
+ # Customizable sequence number on Aptos, similar to the nonce in the EVM.
140
+ self.sequenceNumber = None
139
141
  # Balance verification, BALANCE_CHECK by default
140
142
  self.balanceVerifyType = None
141
143
 
@@ -161,6 +163,56 @@ class FeeRateDto:
161
163
  self.gasFeeCap = None
162
164
  # SUI gas budget, similar to EIP-1559 max fee
163
165
  self.gasBudget = None
166
+ # The gas price the transaction sender is willing to pay, similar to EVM gasPrice
167
+ self.gasUnitPrice = None
168
+ # The maximum number of gas units that the transaction sender is willing to spend to execute the transaction, similar to EVM gasLimit
169
+ self.maxGasAmount = None
170
+
171
+ class CreateTransactionsUTXOMultiDestRequest:
172
+ def __init__(self):
173
+ # Merchant unique business ID (100 characters max)
174
+ self.customerRefId = None
175
+ # Merchant extended field (defined by merchant) shown to merchant (255 characters max)
176
+ self.customerExt1 = None
177
+ # Merchant extended field (defined by merchant) shown to merchant (255 characters max)
178
+ self.customerExt2 = None
179
+ # Transaction note (180 characters max)
180
+ self.note = None
181
+ # Coin key
182
+ self.coinKey = None
183
+ # Transaction Fee Rate Grade
184
+ # Choose between transaction fees. If the transaction fee rate is preset, it will take priority
185
+ self.txFeeLevel = None
186
+ # Transaction fee rate, either txFeeLevel or feeRateDto
187
+ self.feeRateDto = FeeRateDto()
188
+ # Maximum estimated transaction fee rate for a given transaction
189
+ self.maxTxFeeRate = None
190
+ # Source account key
191
+ self.sourceAccountKey = None
192
+ # Account type
193
+ self.sourceAccountType = None
194
+ # Destination address list
195
+ self.destinationAddressList = [DestinationAddress]
196
+ # Destination Tag
197
+ self.destinationTag = None
198
+ # Bitcoin enabled for RBF (Replace-by-fee is a protocol in the Bitcoin mempool that allows for the replacement of an unconfirmed transaction with another one)
199
+ self.isRbf = None
200
+
201
+ def asDict(self):
202
+ dict = self.__dict__
203
+ dict["feeRateDto"] = dict["feeRateDto"].__dict__
204
+ destinationAddressList = []
205
+ for time in dict["destinationAddressList"]:
206
+ destinationAddressList.append(time.__dict__)
207
+ dict["destinationAddressList"] = destinationAddressList
208
+ return dict
209
+
210
+ class DestinationAddress:
211
+ def __init__(self):
212
+ # Destination address
213
+ self.address = None
214
+ # Transaction amount
215
+ self.amount = None
164
216
 
165
217
 
166
218
  class RecreateTransactionRequest:
@@ -203,9 +255,19 @@ class TransactionsFeeRateRequest:
203
255
  self.sourceAddress = None
204
256
  # Destination address is optional for TRON and FIL when estimating transaction fees (although providing it may result in a more accurate fee estimation). For EVM-based transactions, the destination address is required when retrieving the gas limit on the blockchain. Otherwise, a default fixed gas limit value will be returned
205
257
  self.destinationAddress = None
258
+ # Destination address list
259
+ self.destinationAddressList = [DestinationAddress]
206
260
  # Transfer amount is required to calculate gas limit more accurately when using EVM chains. When using UTXO, providing the amount can estimate transaction fees more accurately. If no amount is provided, the calculation is based on the maximum UTXO quantity. When using SUI, providing the amount can estimate gas budget more accurately
207
261
  self.value = None
208
262
 
263
+ def asDict(self):
264
+ dict = self.__dict__
265
+ destinationAddressList = []
266
+ for time in dict["destinationAddressList"]:
267
+ destinationAddressList.append(time.__dict__)
268
+ dict["destinationAddressList"] = destinationAddressList
269
+ return dict
270
+
209
271
 
210
272
  class CancelTransactionRequest:
211
273
  def __init__(self):
@@ -253,46 +315,50 @@ class CollectionTransactionsUTXORequest:
253
315
  class TransactionApi:
254
316
 
255
317
  def __init__(self, config):
256
- global api_client
257
- api_client = Client(config)
318
+ self.api_client = Client(config)
258
319
 
259
320
  # Transaction List V1
260
321
  # Filter transaction history by various conditions. For optimal results, we recommend using the V2 version.
261
322
  def list_transactions_v1(self, request: ListTransactionsV1Request):
262
- return api_client.send_request(request, '/v1/transactions/list')
323
+ return self.api_client.send_request(request, '/v1/transactions/list')
263
324
 
264
325
  # Transaction List V2
265
326
  # Filter transaction history by various conditions.
266
327
  def list_transactions_v2(self, request: ListTransactionsV2Request):
267
- return api_client.send_request(request, '/v2/transactions/list')
328
+ return self.api_client.send_request(request, '/v2/transactions/list')
268
329
 
269
330
  # Create a new transaction.
270
331
  def create_transactions(self, request: CreateTransactionRequest):
271
332
  request.asDict()
272
- return api_client.send_request(request, '/v2/transactions/create')
333
+ return self.api_client.send_request(request, '/v2/transactions/create')
334
+
335
+ # For UTXOs that natively support multiple OUTPUTs, this interface allows a single transaction to transfer funds to multiple destination addresses simultaneously.(To use the Co-Signer, please use version 1.5.9 or higher)
336
+ def create_transactions_UTXO_multiDest(self, request: CreateTransactionsUTXOMultiDestRequest):
337
+ request.asDict()
338
+ return self.api_client.send_request(request, '/v1/transactions/utxo/multidest/create')
273
339
 
274
340
  # Speed up EVM and UTXO-based Transactions
275
341
  # Transactions with low transaction fees and those that have been pending for a long time can be sped up. EVM-based and BTC transactions can be sped up through RBF(If 'isRbf' is set to true during transaction creation, the transaction will be accelerated using RBF acceleration. Otherwise, CPFP acceleration will be used.) For other UTXO-based transactions, CPFP will be used.
276
342
  def recreate_transactions(self, request: RecreateTransactionRequest):
277
343
  request.asDict()
278
- return api_client.send_request(request, '/v2/transactions/recreate')
344
+ return self.api_client.send_request(request, '/v2/transactions/recreate')
279
345
 
280
346
  # Retrieve a Transaction
281
347
  # To query a transaction, either customerRefId or txKey are required. If both values are provided, the retrieval will be based on the txKey.
282
348
  def one_transactions(self, request: OneTransactionsRequest):
283
- return api_client.send_request(request, '/v1/transactions/one')
349
+ return self.api_client.send_request(request, '/v1/transactions/one')
284
350
 
285
351
  # Estimate Transaction Fee
286
352
  # This interface provides users with an estimated range of transaction fee rates of a given cryptocurrency when creating or speeding up transactions.
287
353
  def transaction_fee_rate(self, request: TransactionsFeeRateRequest):
288
- return api_client.send_request(request, '/v2/transactions/getFeeRate')
354
+ return self.api_client.send_request(request, '/v2/transactions/getFeeRate')
289
355
 
290
356
  # Cancel Transaction
291
357
  # Cancel the authorization-pending transaction and the signing-in-progress transaction.
292
358
  def cancel_transactions(self, request: CancelTransactionRequest):
293
- return api_client.send_request(request, '/v1/transactions/cancel')
359
+ return self.api_client.send_request(request, '/v1/transactions/cancel')
294
360
 
295
361
  # UTXO-Based Coin Sweeping
296
362
  # For multi-address UTXO coins under a wallet account, this interface allows users to collect the balances of certain qualifying addresses into a specified destination address.
297
363
  def collectionTransactionsUTXO(self, request: CollectionTransactionsUTXORequest):
298
- return api_client.send_request(request, '/v1/transactions/utxo/collection')
364
+ return self.api_client.send_request(request, '/v1/transactions/utxo/collection')
@@ -200,58 +200,57 @@ class ListWeb3SignRequest:
200
200
  class Web3Api:
201
201
 
202
202
  def __init__(self, config):
203
- global api_client
204
- api_client = Client(config)
203
+ self.api_client = Client(config)
205
204
 
206
205
  # Create a Web3 Wallet Account
207
206
  def createWeb3Account(self, request: CreateWeb3AccountRequest):
208
- return api_client.send_request(request, '/v1/web3/account/create')
207
+ return self.api_client.send_request(request, '/v1/web3/account/create')
209
208
 
210
209
  # Batch Create Web3 Wallet Accounts
211
210
  # Create a batch of wallet accounts based on specified number. Web3 wallet accounts created in batches are not displayed in the Safeheron App by default.
212
211
  def batchCreateWeb3Account(self, request: BatchCreateWeb3AccountRequest):
213
- return api_client.send_request(request, '/v1/web3/batch/account/create')
212
+ return self.api_client.send_request(request, '/v1/web3/batch/account/create')
214
213
 
215
214
  # List Web3 Wallet Accounts
216
215
  # Filter Web3 wallet account lists by various conditions.
217
216
  def listWeb3Accounts(self, request: ListWeb3AccountRequest):
218
- return api_client.send_request(request, '/v1/web3/account/list')
217
+ return self.api_client.send_request(request, '/v1/web3/account/list')
219
218
 
220
219
  # Create ethSign
221
220
  # Merchants can initiate an ethSign signature through this interface. The merchant is required to serialize the transaction data, generating a corresponding hash (supporting both 0x and non-0x formatted data). The hash is then submitted through this interface to create a signature, which can be obtained by Retrieve a Single Web3 Signature interface or webhook. From there, merchants can complete the subsequent steps according to their own needs once they have obtained the signature.
222
221
  def createWeb3EthSign(self, request: CreateWeb3EthSignRequest):
223
222
  request.asDict();
224
- return api_client.send_request(request, '/v1/web3/sign/ethSign')
223
+ return self.api_client.send_request(request, '/v1/web3/sign/ethSign')
225
224
 
226
225
  # Create personalSign
227
226
  # Merchants can initiate a personalSign signature for any text using this interface. The merchant only needs to prepare the data to be signed and submit it through this interface to create the signature. The resulting signature can then be obtained by Retrieve a Single Web3 Signature interface or via webhook. From there, merchants can complete the subsequent steps according to their own needs once they have obtained the signature.
228
227
  def createWeb3PersonalSign(self, request: CreateWeb3PersonalSignRequest):
229
228
  request.asDict()
230
- return api_client.send_request(request, '/v1/web3/sign/personalSign')
229
+ return self.api_client.send_request(request, '/v1/web3/sign/personalSign')
231
230
 
232
231
  # Create ethSignTypedData
233
232
  # Merchants can initiate an ethSignTypedData signature of specific formatted data (supporting data formats of v1, v3, and v4) through this interface. Merchants will need to format their signature data and submit it through the interface. Once the signature is created, the result can be retrieved via Retrieve a Single Web3 Signature interface or webhook. From there, merchants can complete the subsequent steps according to their own needs once they have obtained the signature.
234
233
  def createWeb3EthSignTypedData(self, request: CreateWeb3EthSignTypedDataRequest):
235
234
  request.asDict()
236
- return api_client.send_request(request, '/v1/web3/sign/ethSignTypedData')
235
+ return self.api_client.send_request(request, '/v1/web3/sign/ethSignTypedData')
237
236
 
238
237
  # Create ethSignTransaction
239
238
  # Merchants can initiate ethSignTransaction signature transactions through this interface. The merchant must prepare transaction-related data, such as from, to, nonce, gas limit, gas price, value, data, and more. Once this data is submitted, a signature is created and the result can be obtained by Retrieve a Single Web3 Signature interface or webhook. From there, merchants can complete the subsequent steps according to their own needs once they have obtained the signature.
240
239
  def createWeb3EthSignTransaction(self, request: CreateWeb3EthSignTransactionRequest):
241
240
  request.asDict()
242
- return api_client.send_request(request, '/v1/web3/sign/ethSignTransaction')
241
+ return self.api_client.send_request(request, '/v1/web3/sign/ethSignTransaction')
243
242
 
244
243
  # Cancel Signature
245
244
  # Cancel pending signatures.
246
245
  def cancelWeb3Sign(self, request: CancelWeb3SignRequest):
247
- return api_client.send_request(request, '/v1/web3/sign/cancel')
246
+ return self.api_client.send_request(request, '/v1/web3/sign/cancel')
248
247
 
249
248
  # Retrieve a Single Web3 Signature
250
249
  # To query a transaction, either customerRefId or txKey are required. If both values are provided, the retrieval will be based on the txKey.
251
250
  def oneWeb3Sign(self, request: OneWeb3SignRequest):
252
- return api_client.send_request(request, '/v1/web3/sign/one')
251
+ return self.api_client.send_request(request, '/v1/web3/sign/one')
253
252
 
254
253
  # Web3 Sign Transaction List
255
254
  # Filter Web3 Sign history by various conditions.
256
255
  def listWeb3Sign(self, request: ListWeb3SignRequest):
257
- return api_client.send_request(request, '/v1/web3/sign/list')
256
+ return self.api_client.send_request(request, '/v1/web3/sign/list')
@@ -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 = 10
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)
@@ -15,19 +15,16 @@ class CoSignerResponse:
15
15
  class CoSignerConverter:
16
16
 
17
17
  def __init__(self, config):
18
- global api_pub_key
19
- global biz_privKey
20
- api_pub_key = config['apiPubKey']
21
- if 'bizPrivKey' in config:
22
- biz_privKey = PEM_PRIVATE_HEAD + config['bizPrivKey'] + PEM_PRIVATE_END
23
- if 'bizPrivKeyPemFile' in config:
24
- private_key_pem_file = config['bizPrivKeyPemFile']
25
- if private_key_pem_file is not None and private_key_pem_file != '':
26
- biz_privKey = load_rsa_private_key(private_key_pem_file)
18
+ self.api_pub_key = config['apiPubKey']
19
+ if config.get('bizPrivKey'):
20
+ self.biz_privKey = PEM_PRIVATE_HEAD + config['bizPrivKey'] + PEM_PRIVATE_END
21
+ if config.get('bizPrivKeyPemFile'):
22
+ self.biz_privKey = load_rsa_private_key(config['bizPrivKeyPemFile'])
23
+
27
24
 
28
25
  def request_convert(self, co_signer_call_back):
29
- platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + api_pub_key + PEM_PUBLIC_END)
30
- api_user_rsa_sk = get_rsa_key(biz_privKey)
26
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + self.api_pub_key + PEM_PUBLIC_END)
27
+ api_user_rsa_sk = get_rsa_key(self.biz_privKey)
31
28
  required_keys = {
32
29
  'key',
33
30
  'sig',
@@ -40,6 +37,14 @@ class CoSignerConverter:
40
37
  raise Exception(co_signer_call_back)
41
38
 
42
39
  # 1 rsa verify
40
+ rsaType = ''
41
+ if "rsaType" in co_signer_call_back:
42
+ rsaType = co_signer_call_back.pop('rsaType')
43
+
44
+ aesType = ''
45
+ if "aesType" in co_signer_call_back:
46
+ aesType = co_signer_call_back.pop('aesType')
47
+
43
48
  sig = co_signer_call_back.pop('sig')
44
49
  need_sign_message = sort_request(co_signer_call_back)
45
50
  v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
@@ -48,19 +53,25 @@ class CoSignerConverter:
48
53
 
49
54
  # 2 get aes key and iv
50
55
  key = co_signer_call_back.pop('key')
51
- aes_data = rsa_decrypt(api_user_rsa_sk, key)
56
+ if ECB_OAEP_TYPE == rsaType:
57
+ aes_data = rsa_oaep_decrypt(api_user_rsa_sk, key)
58
+ else:
59
+ aes_data = rsa_decrypt(api_user_rsa_sk, key)
52
60
  aes_key = aes_data[0:32]
53
61
  aes_iv = aes_data[32:48]
54
62
 
55
63
  # 3 aes decrypt data, get response data
56
- r = aes_decrypt(aes_key, aes_iv, b64decode(co_signer_call_back['bizContent']))
64
+ if GCM_TYPE == aesType:
65
+ r = aes_gcm_decrypt(aes_key, aes_iv, b64decode(co_signer_call_back['bizContent']))
66
+ else:
67
+ r = aes_decrypt(aes_key, aes_iv, b64decode(co_signer_call_back['bizContent']))
57
68
  # response_dict['bizContent'] = json.loads(r.decode())
58
69
 
59
70
  return json.loads(r.decode())
60
71
 
61
72
  def response_converter(self, co_signer_response: CoSignerResponse):
62
- platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + api_pub_key + PEM_PUBLIC_END)
63
- api_user_rsa_sk = get_rsa_key(PEM_PRIVATE_HEAD + biz_privKey + PEM_PRIVATE_END)
73
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + self.api_pub_key + PEM_PUBLIC_END)
74
+ api_user_rsa_sk = get_rsa_key(PEM_PRIVATE_HEAD + self.biz_privKey + PEM_PRIVATE_END)
64
75
 
65
76
  ret = dict()
66
77
 
@@ -1,4 +1,3 @@
1
- # Crypto 可直接替换为 Cryptodome
2
1
  from Cryptodome.Cipher import AES
3
2
  from Cryptodome.Util.Padding import pad, unpad
4
3
  from Cryptodome.PublicKey import RSA
@@ -8,6 +7,7 @@ from Cryptodome.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5
8
7
  from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
9
8
  from Cryptodome.Hash import SHA256, SHA1
10
9
  from Cryptodome import Random
10
+ from Crypto.Cipher import PKCS1_OAEP
11
11
  import json
12
12
  import time
13
13
  import requests
@@ -20,6 +20,10 @@ PEM_PUBLIC_END = "\n-----END PUBLIC KEY-----"
20
20
  PEM_PRIVATE_HEAD = "-----BEGIN RSA PRIVATE KEY-----\n"
21
21
  PEM_PRIVATE_END = "\n-----END RSA PRIVATE KEY-----"
22
22
 
23
+
24
+ GCM_TYPE = "GCM_NOPADDING"
25
+ ECB_OAEP_TYPE = "ECB_OAEP"
26
+
23
27
  def load_rsa_private_key(file_path, password=None):
24
28
  try:
25
29
  with open(file_path, "rb") as key_file:
@@ -55,6 +59,16 @@ def aes_encrypt(key: bytes, iv: bytes, message: bytes):
55
59
  return encrypt_text
56
60
 
57
61
 
62
+ def aes_gcm_encrypt(key: bytes, iv: bytes, message: bytes):
63
+ check_key_and_iv(key, iv)
64
+ try:
65
+ cipher = AES.new(key, AES.MODE_GCM, iv)
66
+ cipher_text, tag = cipher.encrypt_and_digest(message)
67
+ except Exception as e:
68
+ raise Exception("aes encrypt error: %s" % e)
69
+ return cipher_text + tag
70
+
71
+
58
72
  def aes_decrypt(key: bytes, iv: bytes, encrypt_text: bytes):
59
73
  check_key_and_iv(key, iv)
60
74
  try:
@@ -65,6 +79,16 @@ def aes_decrypt(key: bytes, iv: bytes, encrypt_text: bytes):
65
79
  return unpad(pad_text, AES.block_size)
66
80
 
67
81
 
82
+ def aes_gcm_decrypt(key: bytes, iv: bytes, encrypt_text: bytes):
83
+ check_key_and_iv(key, iv)
84
+ try:
85
+ cipher = AES.new(key, AES.MODE_GCM, iv)
86
+ pad_text = cipher.decrypt(encrypt_text)
87
+ except Exception as e:
88
+ raise Exception("aes decrypt error: %s" % e)
89
+ return pad_text[0:len(pad_text)-len(iv)]
90
+
91
+
68
92
  def get_rsa_key(key_pem, passphrase=None):
69
93
  if key_pem == '':
70
94
  raise Exception("rsa encrypt error: pubkey can't be none")
@@ -84,6 +108,16 @@ def rsa_encrypt(public_key, message: bytes):
84
108
  return encrypt_text_base64.decode('utf-8')
85
109
 
86
110
 
111
+ def rsa_oaep_encrypt(public_key, message: bytes):
112
+ try:
113
+ cipher = PKCS1_OAEP.new(public_key, hashAlgo=SHA256)
114
+ encrypt_text = cipher.encrypt(message)
115
+ except Exception as e:
116
+ raise Exception("rsa encrypt error: %s" % e)
117
+ encrypt_text_base64 = b64encode(encrypt_text)
118
+ return encrypt_text_base64.decode('utf-8')
119
+
120
+
87
121
  def rsa_decrypt(private_key, message: str):
88
122
  encrypt_text = b64decode(message)
89
123
  try:
@@ -94,6 +128,15 @@ def rsa_decrypt(private_key, message: str):
94
128
  raise Exception("rsa decrypt error: %s" % e)
95
129
 
96
130
 
131
+ def rsa_oaep_decrypt(private_key, message: str):
132
+ encrypt_text = b64decode(message)
133
+ try:
134
+ cipher_rsa = PKCS1_OAEP.new(private_key, hashAlgo=SHA256)
135
+ return cipher_rsa.decrypt(encrypt_text)
136
+ except Exception as e:
137
+ raise Exception("rsa decrypt error: %s" % e)
138
+
139
+
97
140
  def rsa_sign(private_key, message_hash: bytes):
98
141
  try:
99
142
  signer = Signature_pkcs1_v1_5.new(private_key)
@@ -121,7 +164,7 @@ def sort_request(r: dict):
121
164
  data1 = json.loads(sortData)
122
165
  for key, value in data1.items():
123
166
  # print(value, type(value))
124
- if isinstance(value, dict): # 注意:对于dict类型的数据,一定要保留双引号,但python默认都是单引号
167
+ if isinstance(value, dict):
125
168
  val = json.dumps(value).replace(' ', '')
126
169
  else:
127
170
  val = value
@@ -144,12 +187,12 @@ def encrypt_request(api_key, request_dict, platform_rsa_pk, api_user_rsa_sk):
144
187
 
145
188
  # 1 rsa encrypt aes key + iv
146
189
  aes_data = aes_key + aes_iv
147
- ret['key'] = rsa_encrypt(platform_rsa_pk, aes_data)
190
+ ret['key'] = rsa_oaep_encrypt(platform_rsa_pk, aes_data)
148
191
 
149
192
  # 2 aes encrypt request data
150
193
  if request_dict is not None:
151
194
  request_data = json.dumps(request_dict.__dict__).replace('\'', '\"').replace('\n', '').encode('utf-8')
152
- aes_encrypted_bytes = aes_encrypt(aes_key, aes_iv, request_data)
195
+ aes_encrypted_bytes = aes_gcm_encrypt(aes_key, aes_iv, request_data)
153
196
  ret['bizContent'] = b64encode(aes_encrypted_bytes).decode()
154
197
 
155
198
  # 3 set timestamp
@@ -158,7 +201,8 @@ def encrypt_request(api_key, request_dict, platform_rsa_pk, api_user_rsa_sk):
158
201
  # 4 sign request
159
202
  need_sign_message = sort_request(ret)
160
203
  ret['sig'] = rsa_sign(api_user_rsa_sk, need_sign_message)
161
-
204
+ ret['rsaType'] = ECB_OAEP_TYPE
205
+ ret['aesType'] = GCM_TYPE
162
206
  return ret
163
207
 
164
208
 
@@ -178,6 +222,14 @@ def decrypt_response(response_dict, platform_rsa_pk, api_user_rsa_sk):
178
222
  raise Exception(response_dict)
179
223
 
180
224
  # 1 rsa verify
225
+ rsaType = ''
226
+ if "rsaType" in response_dict:
227
+ rsaType = response_dict.pop('rsaType')
228
+
229
+ aesType = ''
230
+ if "aesType" in response_dict:
231
+ aesType = response_dict.pop('aesType')
232
+
181
233
  sig = response_dict.pop('sig')
182
234
  need_sign_message = sort_request(response_dict)
183
235
  v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
@@ -186,12 +238,18 @@ def decrypt_response(response_dict, platform_rsa_pk, api_user_rsa_sk):
186
238
 
187
239
  # 2 get aes key and iv
188
240
  key = response_dict.pop('key')
189
- aes_data = rsa_decrypt(api_user_rsa_sk, key)
241
+ if ECB_OAEP_TYPE == rsaType:
242
+ aes_data = rsa_oaep_decrypt(api_user_rsa_sk, key)
243
+ else:
244
+ aes_data = rsa_decrypt(api_user_rsa_sk, key)
190
245
  aes_key = aes_data[0:32]
191
246
  aes_iv = aes_data[32:48]
192
247
 
193
248
  # 3 aes decrypt data, get response data
194
- r = aes_decrypt(aes_key, aes_iv, b64decode(response_dict['bizContent']))
249
+ if GCM_TYPE == aesType:
250
+ r = aes_gcm_decrypt(aes_key, aes_iv, b64decode(response_dict['bizContent']))
251
+ else:
252
+ r = aes_decrypt(aes_key, aes_iv, b64decode(response_dict['bizContent']))
195
253
  #response_dict['bizContent'] = json.loads(r.decode())
196
254
 
197
255
  return json.loads(r.decode())
@@ -0,0 +1,61 @@
1
+ import sys
2
+
3
+ sys.path.pop(0)
4
+ from safeheron_api_sdk_python.tools import *
5
+
6
+
7
+ class WebhookConverter:
8
+
9
+ def __init__(self, config):
10
+ self.safeheron_webHook_rsa_public_key = config['safeheronWebHookRsaPublicKey']
11
+ if config.get('webHookRsaPrivateKey'):
12
+ self.web_hook_rsa_private_key = PEM_PRIVATE_HEAD + config['webHookRsaPrivateKey'] + PEM_PRIVATE_END
13
+ if config.get('webHookRsaPrivateKeyPemFile'):
14
+ self.web_hook_rsa_private_key = load_rsa_private_key(config['webHookRsaPrivateKeyPemFile'])
15
+
16
+ def converter(self, webhook):
17
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + self.safeheron_webHook_rsa_public_key + PEM_PUBLIC_END)
18
+ api_user_rsa_sk = get_rsa_key(self.web_hook_rsa_private_key)
19
+ required_keys = {
20
+ 'key',
21
+ 'sig',
22
+ 'bizContent',
23
+ 'timestamp',
24
+ }
25
+
26
+ missing_keys = required_keys.difference(webhook.keys())
27
+ if missing_keys:
28
+ raise Exception(webhook)
29
+
30
+ # 1 rsa verify
31
+ rsaType = ''
32
+ if "rsaType" in webhook:
33
+ rsaType = webhook.pop('rsaType')
34
+
35
+ aesType = ''
36
+ if "aesType" in webhook:
37
+ aesType = webhook.pop('aesType')
38
+
39
+ sig = webhook.pop('sig')
40
+ need_sign_message = sort_request(webhook)
41
+ v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
42
+ if not v:
43
+ raise Exception("rsa verify: false")
44
+
45
+ # 2 get aes key and iv
46
+ key = webhook.pop('key')
47
+ if ECB_OAEP_TYPE == rsaType:
48
+ aes_data = rsa_oaep_decrypt(api_user_rsa_sk, key)
49
+ else:
50
+ aes_data = rsa_decrypt(api_user_rsa_sk, key)
51
+ aes_key = aes_data[0:32]
52
+ aes_iv = aes_data[32:48]
53
+
54
+ # 3 aes decrypt data, get response data
55
+ if GCM_TYPE == aesType:
56
+ r = aes_gcm_decrypt(aes_key, aes_iv, b64decode(webhook['bizContent']))
57
+ else:
58
+ r = aes_decrypt(aes_key, aes_iv, b64decode(webhook['bizContent']))
59
+ # response_dict['bizContent'] = json.loads(r.decode())
60
+
61
+ return json.loads(r.decode())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: safeheron_api_sdk_python
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: Python for Safeheron API
5
5
  Home-page: https://github.com/Safeheron/safeheron-api-sdk-python
6
6
  Author: safeheron
@@ -3,7 +3,7 @@ from setuptools import find_packages
3
3
  with open("README.rst", "r") as f:
4
4
  long_description = f.read()
5
5
  setup(name='safeheron_api_sdk_python',
6
- version='1.1.0',
6
+ version='1.1.2',
7
7
  description='Python for Safeheron API',
8
8
  long_description=long_description,
9
9
  author='safeheron',
@@ -1,28 +0,0 @@
1
- from safeheron_api_sdk_python.tools import *
2
-
3
- class Client:
4
-
5
- def __init__(self, config):
6
- global api_key
7
- global platform_pub_key
8
- global use_private_key
9
- global base_url
10
- api_key = config['apiKey']
11
- platform_pub_key = config['safeheronPublicKey']
12
- base_url = config['baseUrl']
13
- if 'privateKey' in config:
14
- use_private_key = PEM_PRIVATE_HEAD + config['privateKey'] + PEM_PRIVATE_END
15
- if 'privateKeyPemFile' in config:
16
- private_key_pem_file = config['privateKeyPemFile']
17
- if private_key_pem_file is not None and private_key_pem_file != '':
18
- use_private_key = load_rsa_private_key(private_key_pem_file)
19
-
20
- def send_request(self, request, uri):
21
- req = encrypt_request(api_key, request, platform_pub_key, use_private_key)
22
- res = self.execution(req, uri)
23
- res.raise_for_status()
24
- res = res.json()
25
- return decrypt_response(res, platform_pub_key, use_private_key)
26
-
27
- def execution(self, request, uri):
28
- return requests.post(base_url + uri, data=json.dumps(request), headers={"Content-Type": "application/json"})
@@ -1,51 +0,0 @@
1
- import sys
2
-
3
- sys.path.pop(0)
4
- from safeheron_api_sdk_python.tools import *
5
-
6
-
7
- class WebhookConverter:
8
-
9
- def __init__(self, config):
10
- global safeheron_webHook_rsa_public_key
11
- global web_hook_rsa_private_key
12
- safeheron_webHook_rsa_public_key = config['safeheronWebHookRsaPublicKey']
13
- if 'webHookRsaPrivateKey' in config:
14
- web_hook_rsa_private_key = PEM_PRIVATE_HEAD + config['webHookRsaPrivateKey'] + PEM_PRIVATE_END
15
- if 'webHookRsaPrivateKeyPemFile' in config:
16
- private_key_pem_file = config['webHookRsaPrivateKeyPemFile']
17
- if private_key_pem_file is not None and private_key_pem_file != '':
18
- web_hook_rsa_private_key = load_rsa_private_key(private_key_pem_file)
19
-
20
- def converter(self, webhook):
21
- platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + safeheron_webHook_rsa_public_key + PEM_PUBLIC_END)
22
- api_user_rsa_sk = get_rsa_key(web_hook_rsa_private_key)
23
- required_keys = {
24
- 'key',
25
- 'sig',
26
- 'bizContent',
27
- 'timestamp',
28
- }
29
-
30
- missing_keys = required_keys.difference(webhook.keys())
31
- if missing_keys:
32
- raise Exception(webhook)
33
-
34
- # 1 rsa verify
35
- sig = webhook.pop('sig')
36
- need_sign_message = sort_request(webhook)
37
- v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
38
- if not v:
39
- raise Exception("rsa verify: false")
40
-
41
- # 2 get aes key and iv
42
- key = webhook.pop('key')
43
- aes_data = rsa_decrypt(api_user_rsa_sk, key)
44
- aes_key = aes_data[0:32]
45
- aes_iv = aes_data[32:48]
46
-
47
- # 3 aes decrypt data, get response data
48
- r = aes_decrypt(aes_key, aes_iv, b64decode(webhook['bizContent']))
49
- # response_dict['bizContent'] = json.loads(r.decode())
50
-
51
- return json.loads(r.decode())