Skip to content

Commit 9b509e9

Browse files
committed
Add ability to create api key. add approval tx via prepare/iterateTransaction
1 parent de81dd6 commit 9b509e9

File tree

2 files changed

+331
-100
lines changed

2 files changed

+331
-100
lines changed

src/client/client.ts

Lines changed: 139 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import fetch from 'node-fetch'
55
import toHex from 'array-buffer-to-hex'
66
import https from 'https'
77
import http from 'http'
8-
import * as NeonJS from '@cityofzion/neon-js'
98
import Promievent from 'promievent'
10-
// import { Transaction as EthTransaction } from 'ethereumjs-tx'
11-
import Web3 from 'web3'
12-
import { Contract } from 'web3-eth-contract'
9+
1310
import BigNumber from 'bignumber.js'
1411
import { ApolloError } from './ApolloError'
1512
import { LIST_MARKETS_QUERY } from '../queries/market/listMarkets'
@@ -140,6 +137,13 @@ import {
140137
CREATE_APIKEY_MUTATION
141138
} from '../mutations/account/createApiKey'
142139
import { PaillierProof } from '../mutations/account/generatePallierProof'
140+
import {
141+
InputApproveTransaction,
142+
ITERATE_TRANSATION_MUTATION,
143+
PrepareTransactionParams,
144+
PrepareTransactionResponse,
145+
PREPARE_TRANSATION_MUTATION
146+
} from '../mutations/movements/prepareTransaction'
143147

144148
import {
145149
normalizePriceForMarket,
@@ -170,7 +174,6 @@ import {
170174
CurrencyPrice,
171175
LegacyLoginParams,
172176
NonceSet,
173-
SignMovementResult,
174177
AssetData,
175178
Asset,
176179
MissingNonceError,
@@ -198,7 +201,6 @@ import {
198201
Blockchain,
199202
bufferize,
200203
Config,
201-
createAddMovementParams,
202204
createCancelOrderParams,
203205
createGetAssetsNoncesParams,
204206
createListMovementsParams,
@@ -235,13 +237,7 @@ import {
235237
SignStatesFields
236238
} from 'mutations/stateSyncing/fragments/signStatesFragment'
237239

238-
import {
239-
prefixWith0xIfNeeded
240-
// serializeEthTx
241-
} from './ethUtils'
242-
243-
import { SettlementABI } from './abi/eth/settlementABI'
244-
import { Erc20ABI } from './abi/eth/erc20ABI'
240+
import { prefixWith0xIfNeeded } from './ethUtils'
245241

246242
export * from './environments'
247243
import {
@@ -257,7 +253,8 @@ const WebSocket = require('websocket').w3cwebsocket
257253
const BLOCKCHAIN_TO_BIP44 = {
258254
[Blockchain.ETH]: BIP44.ETH,
259255
[Blockchain.BTC]: BIP44.BTC,
260-
[Blockchain.NEO]: BIP44.NEO
256+
[Blockchain.NEO]: BIP44.NEO,
257+
[Blockchain.AVAXC]: BIP44.AVAXC
261258
}
262259

263260
/** @internal */
@@ -280,7 +277,7 @@ export const BIG_NUMBER_FORMAT = {
280277

281278
export const UNLIMITED_APPROVAL =
282279
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe'
283-
280+
export const ACCEPTABLE_APPROVAL = '0xffffffffffffffffffffffffffffe'
284281
export class Client {
285282
private _socket = null
286283
private mode: ClientMode = ClientMode.NONE
@@ -308,9 +305,7 @@ export class Client {
308305

309306
private wsToken: string
310307
private wsUri: string
311-
private isMainNet: boolean
312308
private gql: GQL
313-
private web3: Web3
314309
private authorization: string
315310
private walletIndices: { [key: string]: number }
316311

@@ -324,8 +319,6 @@ export class Client {
324319
/** @internal */
325320
public apiKey: APIKey
326321
/** @internal */
327-
public ethVaultContract: Contract
328-
/** @internal */
329322
public marketData: { [key: string]: Market }
330323
/** @internal */
331324
public nashProtocolMarketData: ReturnType<typeof mapMarketsForNashProtocol>
@@ -356,9 +349,6 @@ export class Client {
356349
headers: {},
357350
...clientOpts
358351
}
359-
this.isMainNet = this.opts.host === EnvironmentConfiguration.production.host
360-
361-
this.web3 = new Web3(this.opts.ethNetworkSettings.nodes[0])
362352

363353
if (!opts.host || (opts.host.indexOf('.') === -1 && !opts.isLocal)) {
364354
throw new Error(`Invalid API host '${opts.host}'`)
@@ -416,15 +406,6 @@ export class Client {
416406
this.opts.maxEthCostPrTransaction
417407
)
418408
}
419-
const network = new NeonJS.rpc.Network({
420-
...this.opts.neoNetworkSettings,
421-
name: this.opts.neoNetworkSettings.name
422-
})
423-
NeonJS.default.add.network(network, true)
424-
this.ethVaultContract = new this.web3.eth.Contract(
425-
SettlementABI,
426-
this.opts.ethNetworkSettings.contracts.vault.contract
427-
)
428409

429410
const query: GQL['query'] = async params => {
430411
let obj: GQLResp<any>
@@ -2525,28 +2506,6 @@ export class Client {
25252506
)
25262507
}
25272508

2528-
public async queryAllowance(assetData: AssetData): Promise<BigNumber> {
2529-
let approvalPower = assetData.blockchainPrecision
2530-
if (assetData.symbol === CryptoCurrency.USDC) {
2531-
approvalPower = this.isMainNet ? 6 : 18
2532-
}
2533-
const erc20Contract = new this.web3.eth.Contract(
2534-
Erc20ABI,
2535-
`0x${assetData.hash}`
2536-
)
2537-
try {
2538-
const res = await erc20Contract.methods
2539-
.allowance(
2540-
`0x${this.apiKey.child_keys[BIP44.ETH].address}`,
2541-
this.opts.ethNetworkSettings.contracts.vault.contract
2542-
)
2543-
.call()
2544-
return new BigNumber(res).div(Math.pow(10, approvalPower))
2545-
} catch (e) {
2546-
return new BigNumber(0)
2547-
}
2548-
}
2549-
25502509
private getBlockchainFees = async (
25512510
blockchain: Blockchain
25522511
): Promise<BlockchainFees> => {
@@ -2764,14 +2723,20 @@ export class Client {
27642723
}
27652724
const assetData = this.assetData[quantity.currency]
27662725
const blockchain = assetData.blockchain
2767-
let address
2768-
try {
2769-
const childKey = this.apiKey.child_keys[
2770-
BLOCKCHAIN_TO_BIP44[blockchain.toUpperCase() as Blockchain]
2771-
]
2772-
address = childKey.address
2773-
} catch (e) {
2774-
address = this.nashCoreConfig.wallets[blockchain].address
2726+
const childKey = this.apiKey.child_keys[
2727+
BLOCKCHAIN_TO_BIP44[blockchain.toUpperCase() as Blockchain]
2728+
]
2729+
const address = childKey.address
2730+
2731+
if (
2732+
blockchain === 'eth' &&
2733+
movementType === MovementTypeDeposit &&
2734+
quantity.currency !== CryptoCurrency.ETH
2735+
) {
2736+
await this.approveAndAwaitAllowance(
2737+
quantity,
2738+
this.opts.ethNetworkSettings.contracts.vault.contract
2739+
)
27752740
}
27762741

27772742
const blockchainFees = await this.getBlockchainFees(
@@ -2956,51 +2921,125 @@ export class Client {
29562921
return null
29572922
}
29582923

2959-
/**
2960-
* Sign a withdraw request.
2961-
*
2962-
* @param address
2963-
* @param quantity
2964-
* @returns
2965-
*
2966-
* Example
2967-
* ```typescript
2968-
* import { createCurrencyAmount } from '@neon-exchange/api-client-ts'
2969-
*
2970-
* const address = 'd5480a0b20e2d056720709a9538b17119fbe9fd6';
2971-
* const amount = createCurrencyAmount('1.5', CryptoCurrency.ETH);
2972-
* const signedMovement = await nash.signWithdrawRequest(address, amount);
2973-
* console.log(signedMovement)
2974-
* ```
2975-
*/
2976-
public async signWithdrawRequest(
2977-
address: string,
2978-
quantity: CurrencyAmount,
2979-
nonce?: number
2980-
): Promise<SignMovementResult> {
2981-
const signMovementParams = createAddMovementParams(
2982-
address,
2983-
false,
2984-
quantity,
2985-
MovementTypeWithdrawal,
2986-
nonce
2924+
public approveAndAwaitAllowance = async (
2925+
amount: CurrencyAmount,
2926+
targetAddress: string,
2927+
feeLevel: 'low' | 'med' | 'high' = 'med'
2928+
): Promise<boolean> => {
2929+
const assetData = this.assetData[amount.currency]
2930+
const blockchain = assetData.blockchain.toUpperCase() as Blockchain
2931+
const childKey = this.apiKey.child_keys[BLOCKCHAIN_TO_BIP44[blockchain]]
2932+
const address = childKey.address
2933+
2934+
const blockchainFees = await this.getBlockchainFees(
2935+
blockchain.toUpperCase() as Blockchain
29872936
)
2988-
const signedPayload = await this.signPayload(signMovementParams)
2989-
const result = await this.gql.mutate({
2990-
mutation: ADD_MOVEMENT_MUTATION,
2991-
variables: {
2992-
payload: signedPayload.payload,
2993-
signature: signedPayload.signature
2937+
2938+
let gasPrice = blockchainFees.priceMedium
2939+
switch (feeLevel) {
2940+
case 'low':
2941+
gasPrice = blockchainFees.priceLow
2942+
break
2943+
case 'high':
2944+
gasPrice = blockchainFees.priceHigh
2945+
break
2946+
}
2947+
2948+
const approveParams: InputApproveTransaction = {
2949+
minimumQuantity: {
2950+
amount: new BigNumber(ACCEPTABLE_APPROVAL, 16).toString(),
2951+
assetHash: assetData.hash,
2952+
blockchain
2953+
},
2954+
quantity: {
2955+
amount: new BigNumber(UNLIMITED_APPROVAL, 16).toString(),
2956+
assetHash: assetData.hash,
2957+
blockchain
2958+
},
2959+
targetAddress: targetAddress.replace('0x', '')
2960+
}
2961+
2962+
const prepareParams: PrepareTransactionParams = {
2963+
address,
2964+
approve: approveParams,
2965+
blockchain,
2966+
gasPrice,
2967+
timestamp: new Date().getTime()
2968+
}
2969+
2970+
const sendPrepare = async (
2971+
params: PrepareTransactionParams
2972+
): Promise<PrepareTransactionResponse> => {
2973+
params.timestamp = new Date().getTime()
2974+
2975+
const signedPrepareTx = await this.signPayload({
2976+
payload: prepareParams,
2977+
kind: SigningPayloadID.prepareTransactionPayload
2978+
})
2979+
try {
2980+
const prepareTxResult = await this.gql.mutate({
2981+
mutation: PREPARE_TRANSATION_MUTATION,
2982+
variables: {
2983+
payload: signedPrepareTx.payload,
2984+
signature: signedPrepareTx.signature
2985+
}
2986+
})
2987+
return {
2988+
...prepareTxResult.data.prepareTransaction,
2989+
approvalNeeded: true
2990+
}
2991+
} catch (e) {
2992+
if (
2993+
e.message.includes(
2994+
'Specified minimum amount to approve is already covered by current allowance'
2995+
)
2996+
) {
2997+
return {
2998+
reference: '',
2999+
transaction: null,
3000+
transactionElements: [],
3001+
approvalNeeded: false
3002+
}
3003+
}
3004+
throw e
29943005
}
3006+
}
3007+
3008+
const prepareResult = await sendPrepare(prepareParams)
3009+
if (!prepareResult.approvalNeeded) {
3010+
return true
3011+
}
3012+
3013+
const signedIteratePayload = await this.signPayload({
3014+
payload: {
3015+
reference: prepareResult.reference,
3016+
transactionElements: prepareResult.transactionElements,
3017+
timestamp: new Date().getTime()
3018+
},
3019+
kind: SigningPayloadID.iterateTransactionPayload
29953020
})
29963021

2997-
// after deposit or withdrawal we want to update nonces
2998-
await this.updateTradedAssetNonces()
3022+
const iterateTxResult = await this.gql.mutate({
3023+
mutation: ITERATE_TRANSATION_MUTATION,
3024+
variables: {
3025+
payload: signedIteratePayload.payload,
3026+
signature: signedIteratePayload.signature
3027+
}
3028+
})
29993029

3000-
return {
3001-
result: result.data.addMovement,
3002-
blockchain_data: signedPayload.blockchain_data
3030+
if (
3031+
iterateTxResult.data.iterateTransaction &&
3032+
iterateTxResult.data.iterateTransaction.transactionElements.length === 0
3033+
) {
3034+
let result = prepareResult
3035+
while (result.approvalNeeded) {
3036+
await sleep(10000)
3037+
result = await sendPrepare(prepareParams)
3038+
}
3039+
return true
30033040
}
3041+
3042+
return false
30043043
}
30053044

30063045
/**

0 commit comments

Comments
 (0)