safeheron-api-sdk-python 1.0.8__tar.gz → 1.1.1__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.
- {safeheron_api_sdk_python-1.0.8/safeheron_api_sdk_python.egg-info → safeheron_api_sdk_python-1.1.1}/PKG-INFO +1 -1
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/README.md +35 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python/api/account_api.py +32 -16
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python/api/coin_api.py +6 -7
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python/api/mpc_sign_api.py +4 -5
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python/api/transaction_api.py +76 -10
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python/api/web3_api.py +11 -12
- safeheron_api_sdk_python-1.1.1/safeheron_api_sdk_python/client.py +28 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python/cosigner/co_signer_converter.py +25 -15
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python/tools.py +64 -7
- safeheron_api_sdk_python-1.1.1/safeheron_api_sdk_python/webhook/webhook_converter.py +59 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1/safeheron_api_sdk_python.egg-info}/PKG-INFO +1 -1
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/setup.py +1 -1
- safeheron_api_sdk_python-1.0.8/safeheron_api_sdk_python/client.py +0 -28
- safeheron_api_sdk_python-1.0.8/safeheron_api_sdk_python/webhook/webhook_converter.py +0 -51
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/LICENSE +0 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/README.rst +0 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python.egg-info/SOURCES.txt +0 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python.egg-info/dependency_links.txt +0 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python.egg-info/requires.txt +0 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python.egg-info/top_level.txt +0 -0
- {safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/setup.cfg +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if '
|
|
22
|
-
biz_privKey =
|
|
23
|
-
|
|
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,13 @@ class CoSignerConverter:
|
|
|
40
37
|
raise Exception(co_signer_call_back)
|
|
41
38
|
|
|
42
39
|
# 1 rsa verify
|
|
40
|
+
if "rsaType" in co_signer_call_back:
|
|
41
|
+
rsaType = co_signer_call_back.pop('rsaType')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if "aesType" in co_signer_call_back:
|
|
45
|
+
aesType = co_signer_call_back.pop('aesType')
|
|
46
|
+
|
|
43
47
|
sig = co_signer_call_back.pop('sig')
|
|
44
48
|
need_sign_message = sort_request(co_signer_call_back)
|
|
45
49
|
v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
|
|
@@ -48,19 +52,25 @@ class CoSignerConverter:
|
|
|
48
52
|
|
|
49
53
|
# 2 get aes key and iv
|
|
50
54
|
key = co_signer_call_back.pop('key')
|
|
51
|
-
|
|
55
|
+
if ECB_OAEP_TYPE == rsaType:
|
|
56
|
+
aes_data = rsa_oaep_decrypt(api_user_rsa_sk, key)
|
|
57
|
+
else:
|
|
58
|
+
aes_data = rsa_decrypt(api_user_rsa_sk, key)
|
|
52
59
|
aes_key = aes_data[0:32]
|
|
53
60
|
aes_iv = aes_data[32:48]
|
|
54
61
|
|
|
55
62
|
# 3 aes decrypt data, get response data
|
|
56
|
-
|
|
63
|
+
if GCM_TYPE == aesType:
|
|
64
|
+
r = aes_gcm_decrypt(aes_key, aes_iv, b64decode(co_signer_call_back['bizContent']))
|
|
65
|
+
else:
|
|
66
|
+
r = aes_decrypt(aes_key, aes_iv, b64decode(co_signer_call_back['bizContent']))
|
|
57
67
|
# response_dict['bizContent'] = json.loads(r.decode())
|
|
58
68
|
|
|
59
69
|
return json.loads(r.decode())
|
|
60
70
|
|
|
61
71
|
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)
|
|
72
|
+
platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + self.api_pub_key + PEM_PUBLIC_END)
|
|
73
|
+
api_user_rsa_sk = get_rsa_key(PEM_PRIVATE_HEAD + self.biz_privKey + PEM_PRIVATE_END)
|
|
64
74
|
|
|
65
75
|
ret = dict()
|
|
66
76
|
|
{safeheron_api_sdk_python-1.0.8 → safeheron_api_sdk_python-1.1.1}/safeheron_api_sdk_python/tools.py
RENAMED
|
@@ -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):
|
|
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'] =
|
|
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 =
|
|
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,13 @@ 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
|
+
if "rsaType" in response_dict:
|
|
226
|
+
rsaType = response_dict.pop('rsaType')
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
if "aesType" in response_dict:
|
|
230
|
+
aesType = response_dict.pop('aesType')
|
|
231
|
+
|
|
181
232
|
sig = response_dict.pop('sig')
|
|
182
233
|
need_sign_message = sort_request(response_dict)
|
|
183
234
|
v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
|
|
@@ -186,12 +237,18 @@ def decrypt_response(response_dict, platform_rsa_pk, api_user_rsa_sk):
|
|
|
186
237
|
|
|
187
238
|
# 2 get aes key and iv
|
|
188
239
|
key = response_dict.pop('key')
|
|
189
|
-
|
|
240
|
+
if ECB_OAEP_TYPE == rsaType:
|
|
241
|
+
aes_data = rsa_oaep_decrypt(api_user_rsa_sk, key)
|
|
242
|
+
else:
|
|
243
|
+
aes_data = rsa_decrypt(api_user_rsa_sk, key)
|
|
190
244
|
aes_key = aes_data[0:32]
|
|
191
245
|
aes_iv = aes_data[32:48]
|
|
192
246
|
|
|
193
247
|
# 3 aes decrypt data, get response data
|
|
194
|
-
|
|
248
|
+
if GCM_TYPE == aesType:
|
|
249
|
+
r = aes_gcm_decrypt(aes_key, aes_iv, b64decode(response_dict['bizContent']))
|
|
250
|
+
else:
|
|
251
|
+
r = aes_decrypt(aes_key, aes_iv, b64decode(response_dict['bizContent']))
|
|
195
252
|
#response_dict['bizContent'] = json.loads(r.decode())
|
|
196
253
|
|
|
197
254
|
return json.loads(r.decode())
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
if "rsaType" in webhook:
|
|
32
|
+
rsaType = webhook.pop('rsaType')
|
|
33
|
+
|
|
34
|
+
if "aesType" in webhook:
|
|
35
|
+
aesType = webhook.pop('aesType')
|
|
36
|
+
|
|
37
|
+
sig = webhook.pop('sig')
|
|
38
|
+
need_sign_message = sort_request(webhook)
|
|
39
|
+
v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
|
|
40
|
+
if not v:
|
|
41
|
+
raise Exception("rsa verify: false")
|
|
42
|
+
|
|
43
|
+
# 2 get aes key and iv
|
|
44
|
+
key = webhook.pop('key')
|
|
45
|
+
if ECB_OAEP_TYPE == rsaType:
|
|
46
|
+
aes_data = rsa_oaep_decrypt(api_user_rsa_sk, key)
|
|
47
|
+
else:
|
|
48
|
+
aes_data = rsa_decrypt(api_user_rsa_sk, key)
|
|
49
|
+
aes_key = aes_data[0:32]
|
|
50
|
+
aes_iv = aes_data[32:48]
|
|
51
|
+
|
|
52
|
+
# 3 aes decrypt data, get response data
|
|
53
|
+
if GCM_TYPE == aesType:
|
|
54
|
+
r = aes_gcm_decrypt(aes_key, aes_iv, b64decode(webhook['bizContent']))
|
|
55
|
+
else:
|
|
56
|
+
r = aes_decrypt(aes_key, aes_iv, b64decode(webhook['bizContent']))
|
|
57
|
+
# response_dict['bizContent'] = json.loads(r.decode())
|
|
58
|
+
|
|
59
|
+
return json.loads(r.decode())
|
|
@@ -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.
|
|
6
|
+
version='1.1.1',
|
|
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())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|