@stellar-broker/client 0.4.1 → 0.6.0
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.
- package/README.md +160 -109
- package/jest.config.cjs +4 -0
- package/lib/stellarbroker.js +1 -1
- package/lib/stellarbroker.js.map +1 -1
- package/package.json +10 -9
- package/src/asset.js +51 -47
- package/src/authorization.js +65 -0
- package/src/client.js +344 -369
- package/src/errors.js +55 -53
- package/src/estimate.js +4 -3
- package/src/index.d.ts +146 -40
- package/src/index.js +5 -4
- package/src/mediator.js +309 -0
- package/src/quote-request.js +56 -60
- package/src/quote-result.js +93 -0
- package/src/tx-processor.js +133 -161
package/src/mediator.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import {Asset, Keypair, Memo, Operation, TransactionBuilder, Horizon, Networks, StrKey} from '@stellar/stellar-sdk'
|
|
2
|
+
import {fromStroops, toStroops} from './stroops.js'
|
|
3
|
+
import {convertToStellarAsset} from './asset.js'
|
|
4
|
+
import {AuthorizationWrapper} from './authorization.js'
|
|
5
|
+
|
|
6
|
+
//additional XLM amount to cover tx fees
|
|
7
|
+
const feesReserve = '2'
|
|
8
|
+
|
|
9
|
+
export class Mediator {
|
|
10
|
+
/**
|
|
11
|
+
* Create a trader mediator account instance for a given source account
|
|
12
|
+
* @param {string} source - Creator account address
|
|
13
|
+
* @param {string|Asset} sellingAsset - Identifier of the asset to sell
|
|
14
|
+
* @param {string|Asset} buyingAsset - Identifier of the asset to buy
|
|
15
|
+
* @param {string} sellingAmount - Asset amount to sell
|
|
16
|
+
* @param {ClientAuthorizationParams} authorization - Authorization
|
|
17
|
+
*/
|
|
18
|
+
constructor(source, sellingAsset, buyingAsset, sellingAmount, authorization) {
|
|
19
|
+
if (!StrKey.isValidEd25519PublicKey(source))
|
|
20
|
+
throw new Error('Invalid source account')
|
|
21
|
+
this.source = source
|
|
22
|
+
try {
|
|
23
|
+
this.sellingAsset = convertToStellarAsset(sellingAsset)
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.error(e)
|
|
26
|
+
throw new Error('Invalid selling asset')
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
this.buyingAsset = convertToStellarAsset(buyingAsset)
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error(e)
|
|
32
|
+
throw new Error('Invalid buying asset')
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
this.sellingAmount = toStroops(sellingAmount)
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(e)
|
|
38
|
+
throw new Error('Invalid selling amount')
|
|
39
|
+
}
|
|
40
|
+
this.authorization = new AuthorizationWrapper(authorization)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creator account address
|
|
45
|
+
* @type {string}
|
|
46
|
+
* @readonly
|
|
47
|
+
*/
|
|
48
|
+
source
|
|
49
|
+
/**
|
|
50
|
+
* @type {Asset}
|
|
51
|
+
* @readonly
|
|
52
|
+
*/
|
|
53
|
+
sellingAsset
|
|
54
|
+
/**
|
|
55
|
+
* @type {Asset}
|
|
56
|
+
* @readonly
|
|
57
|
+
*/
|
|
58
|
+
buyingAsset
|
|
59
|
+
/**
|
|
60
|
+
* @type {bigint}
|
|
61
|
+
* @readonly
|
|
62
|
+
*/
|
|
63
|
+
sellingAmount
|
|
64
|
+
/**
|
|
65
|
+
* @type {AuthorizationWrapper}
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
authorization
|
|
69
|
+
/**
|
|
70
|
+
* @type {Keypair}
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
mediator
|
|
74
|
+
/**
|
|
75
|
+
* @type {string}
|
|
76
|
+
* @readonly
|
|
77
|
+
*/
|
|
78
|
+
mediatorAddress
|
|
79
|
+
/**
|
|
80
|
+
* Prefix of the mediator address record in the localStorage
|
|
81
|
+
* @type {string}
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
storagePrefix = 'msb_'
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if there are any non-disposed mediators that belong to lost swap sessions
|
|
88
|
+
* @return {boolean}
|
|
89
|
+
*/
|
|
90
|
+
get hasObsoleteMediators() {
|
|
91
|
+
return Object.entries(localStorage).some(([key, initiator]) =>
|
|
92
|
+
initiator === this.source && key.startsWith(this.storagePrefix))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Retrieve funds from mediator accounts that belong to lost swap sessions
|
|
97
|
+
* @return {Promise}
|
|
98
|
+
*/
|
|
99
|
+
async disposeObsoleteMediators() { //TODO: deal with potential problems caused by concurrent swaps
|
|
100
|
+
for (let [key, initiator] of Object.entries(localStorage)) {
|
|
101
|
+
if (initiator === this.source && key.startsWith(this.storagePrefix)) {
|
|
102
|
+
await this.dispose(key.replace(this.storagePrefix, ''))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create mediator account and deposit tokens to sell
|
|
109
|
+
* @return {Promise<string>} - Newly created mediator secret key
|
|
110
|
+
*/
|
|
111
|
+
async init() {
|
|
112
|
+
const sourceAccount = await this.loadAccount(this.source)
|
|
113
|
+
if (!sourceAccount)
|
|
114
|
+
throw new Error('Mediator account doesn\'t exist on the ledger')
|
|
115
|
+
const ops = []
|
|
116
|
+
//create new random keypair for the trade
|
|
117
|
+
this.mediator = Keypair.random()
|
|
118
|
+
this.mediatorAddress = this.mediator.publicKey()
|
|
119
|
+
const {sellingAsset, buyingAsset} = this
|
|
120
|
+
if (sellingAsset.isNative()) { //for XLM total amount should include fee reserves
|
|
121
|
+
const amount = this.sellingAmount + BigInt(feesReserve * 10000000)
|
|
122
|
+
//check available balance
|
|
123
|
+
if (toStroops(findTrustline(sourceAccount, sellingAsset).balance) < amount)
|
|
124
|
+
throw new Error('Insufficient XLM balance for selling amount + potential trading fees')
|
|
125
|
+
//only create account is required for asset transfer
|
|
126
|
+
ops.push(Operation.createAccount({
|
|
127
|
+
source: this.source,
|
|
128
|
+
destination: this.mediatorAddress,
|
|
129
|
+
startingBalance: fromStroops(amount)
|
|
130
|
+
}))
|
|
131
|
+
} else {
|
|
132
|
+
//check available XLM balance
|
|
133
|
+
const xlmBalance = findTrustline(sourceAccount, Asset.native())
|
|
134
|
+
if (xlmBalance.balance < parseInt(feesReserve))
|
|
135
|
+
throw new Error('Insufficient XLM balance for potential trading fees')
|
|
136
|
+
//create mediator account
|
|
137
|
+
ops.push(Operation.createAccount({
|
|
138
|
+
source: this.source,
|
|
139
|
+
destination: this.mediatorAddress,
|
|
140
|
+
startingBalance: feesReserve // for tx fees
|
|
141
|
+
}))
|
|
142
|
+
//check available XLM balance
|
|
143
|
+
const sellingTrustline = findTrustline(sourceAccount, this.sellingAsset)
|
|
144
|
+
if (!sellingTrustline || toStroops(sellingTrustline.balance) < this.sellingAmount)
|
|
145
|
+
throw new Error('Insufficient selling asset balance')
|
|
146
|
+
if (!findTrustline(sourceAccount, this.buyingAsset))
|
|
147
|
+
throw new Error('Source account doesn\'t have buying asset trustline')
|
|
148
|
+
//TODO: check authorizations
|
|
149
|
+
//create trustline for selling asset
|
|
150
|
+
ops.push(Operation.changeTrust({
|
|
151
|
+
source: this.mediatorAddress,
|
|
152
|
+
asset: sellingAsset
|
|
153
|
+
}))
|
|
154
|
+
//transfer tokens to sell
|
|
155
|
+
ops.push(Operation.payment({
|
|
156
|
+
source: this.source,
|
|
157
|
+
asset: sellingAsset,
|
|
158
|
+
destination: this.mediatorAddress,
|
|
159
|
+
amount: fromStroops(this.sellingAmount)
|
|
160
|
+
}))
|
|
161
|
+
}
|
|
162
|
+
//add source account as a signer
|
|
163
|
+
ops.push(Operation.setOptions({
|
|
164
|
+
source: this.mediatorAddress,
|
|
165
|
+
homeDomain: 'mediator.stellar.broker',
|
|
166
|
+
masterWeight: 1,
|
|
167
|
+
highThreshold: 1,
|
|
168
|
+
medThreshold: 1,
|
|
169
|
+
lowThreshold: 1,
|
|
170
|
+
signer: {
|
|
171
|
+
ed25519PublicKey: this.source,
|
|
172
|
+
weight: 1
|
|
173
|
+
}
|
|
174
|
+
}))
|
|
175
|
+
//create trustline for buying asset
|
|
176
|
+
ops.push(Operation.changeTrust({
|
|
177
|
+
source: this.mediatorAddress,
|
|
178
|
+
asset: buyingAsset
|
|
179
|
+
}))
|
|
180
|
+
//confirm tx
|
|
181
|
+
await this.buildAndSend(sourceAccount, ops)
|
|
182
|
+
//store the record in the localStorage
|
|
183
|
+
localStorage.setItem(this.storagePrefix + this.mediatorAddress, this.source)
|
|
184
|
+
//client will need mediator secret for trading
|
|
185
|
+
return this.mediator.secret()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Dispose mediator account
|
|
190
|
+
* @param {string} [address]
|
|
191
|
+
* @return {Promise}
|
|
192
|
+
*/
|
|
193
|
+
async dispose(address) {
|
|
194
|
+
if (!address) {
|
|
195
|
+
address = this.mediatorAddress
|
|
196
|
+
}
|
|
197
|
+
//load account
|
|
198
|
+
let account = await this.loadAccount(address)
|
|
199
|
+
if (!account)
|
|
200
|
+
throw new Error(`Mediator account ${address} doesn't exist on the ledger`)
|
|
201
|
+
|
|
202
|
+
if (!account.signers.find(s => s.key === this.source))
|
|
203
|
+
throw new Error(`${address} is not a mediator account for ${this.source}`)
|
|
204
|
+
const ops = []
|
|
205
|
+
//remove trustlines for each account balance
|
|
206
|
+
for (const balance of account.balances) {
|
|
207
|
+
if (balance.asset_type === 'native')
|
|
208
|
+
continue //skip XLM trustline - merge will handle the transfer
|
|
209
|
+
const asset = convertToStellarAsset(balance)
|
|
210
|
+
//transfer remaining balance to the source account
|
|
211
|
+
if (balance.balance > 0) {
|
|
212
|
+
ops.push(Operation.payment({
|
|
213
|
+
source: address,
|
|
214
|
+
asset,
|
|
215
|
+
destination: this.source,
|
|
216
|
+
amount: balance.balance
|
|
217
|
+
}))
|
|
218
|
+
}
|
|
219
|
+
//remove trustline
|
|
220
|
+
ops.push(Operation.changeTrust({
|
|
221
|
+
source: address,
|
|
222
|
+
asset,
|
|
223
|
+
limit: '0'
|
|
224
|
+
}))
|
|
225
|
+
}
|
|
226
|
+
//merge
|
|
227
|
+
ops.push(Operation.accountMerge({
|
|
228
|
+
source: address,
|
|
229
|
+
destination: this.source
|
|
230
|
+
}))
|
|
231
|
+
if (this.mediatorAddress !== address) {
|
|
232
|
+
account = await this.loadAccount(this.source)
|
|
233
|
+
}
|
|
234
|
+
await this.buildAndSend(account, ops)
|
|
235
|
+
//remove reference from local storage
|
|
236
|
+
localStorage.removeItem(this.storagePrefix + address)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @param {AccountResponse} account
|
|
241
|
+
* @param {Operation[]} operations
|
|
242
|
+
* @param {string} [memo]
|
|
243
|
+
* @private
|
|
244
|
+
*/
|
|
245
|
+
async buildAndSend(account, operations, memo) {
|
|
246
|
+
//create builder
|
|
247
|
+
const builder = new TransactionBuilder(account, {
|
|
248
|
+
fee: '1000000',
|
|
249
|
+
networkPassphrase: Networks.PUBLIC
|
|
250
|
+
})
|
|
251
|
+
builder.setTimeout(30)
|
|
252
|
+
//add memo if needed
|
|
253
|
+
if (memo) {
|
|
254
|
+
builder.addMemo(Memo.text(memo))
|
|
255
|
+
}
|
|
256
|
+
//add operations
|
|
257
|
+
for (const op of operations) {
|
|
258
|
+
builder.addOperation(op)
|
|
259
|
+
}
|
|
260
|
+
//build tx
|
|
261
|
+
let tx = builder.build()
|
|
262
|
+
//sign it on behalf of the mediator account (if available)
|
|
263
|
+
if (this.mediator) {
|
|
264
|
+
tx.sign(this.mediator)
|
|
265
|
+
}
|
|
266
|
+
//request signature from the client (if required)
|
|
267
|
+
if (account.account_id === this.source) {
|
|
268
|
+
tx = await this.authorization.authorize(tx)
|
|
269
|
+
}
|
|
270
|
+
//execute the tx
|
|
271
|
+
const res = await this.createHorizon().submitTransaction(tx, {skipMemoRequiredCheck: true})
|
|
272
|
+
if (!res.successful)
|
|
273
|
+
throw new Error('Failed to create mediator account')
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {string} address
|
|
278
|
+
* @return {Promise<AccountResponse>}
|
|
279
|
+
* @private
|
|
280
|
+
*/
|
|
281
|
+
async loadAccount(address) {
|
|
282
|
+
try {
|
|
283
|
+
return this.createHorizon().loadAccount(address)
|
|
284
|
+
} catch (e) {
|
|
285
|
+
console.error(e)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @return {HorizonServer}
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
293
|
+
createHorizon() {
|
|
294
|
+
return new Horizon.Server('https://horizon.stellar.org')
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {AccountResponse} account
|
|
301
|
+
* @param {Asset} asset
|
|
302
|
+
* @return {BalanceLine}
|
|
303
|
+
*/
|
|
304
|
+
function findTrustline(account, asset) {
|
|
305
|
+
return account.balances.find(b => asset.isNative() ?
|
|
306
|
+
b.asset_type === 'native' :
|
|
307
|
+
b.asset_code === asset.code && b.asset_issuer === asset.issuer
|
|
308
|
+
)
|
|
309
|
+
}
|
package/src/quote-request.js
CHANGED
|
@@ -1,61 +1,57 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* @
|
|
7
|
-
* @property {string}
|
|
8
|
-
* @property {string}
|
|
9
|
-
* @property {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (res.
|
|
33
|
-
throw errors.invalidQuoteParam('
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
throw errors.invalidQuoteParam(parameter, '
|
|
56
|
-
|
|
57
|
-
throw errors.invalidQuoteParam(parameter, 'Slippage tolerance is too small, expected value >= 0')
|
|
58
|
-
if (tolerance > 0.5)
|
|
59
|
-
throw errors.invalidQuoteParam(parameter, 'Slippage tolerance is too large, expected value < 0.5 (50%)')
|
|
60
|
-
return tolerance
|
|
1
|
+
import errors from './errors.js'
|
|
2
|
+
import {parseAsset} from './asset.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} QuoteParams - Quote request parameters provided by the client
|
|
6
|
+
* @property {string} sellingAsset - Asset to sell
|
|
7
|
+
* @property {string} buyingAsset - Asset to buy
|
|
8
|
+
* @property {string} [sellingAmount] - Amount of selling asset
|
|
9
|
+
* @property {number} [slippageTolerance] - Swap slippage tolerance (0.02 by default - 2%)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {QuoteParams} params
|
|
14
|
+
* @return {QuoteParams}
|
|
15
|
+
*/
|
|
16
|
+
export function validateQuoteRequest(params) {
|
|
17
|
+
const {
|
|
18
|
+
sellingAsset, selling_asset, buyingAsset, buying_asset, sellingAmount, selling_amount,
|
|
19
|
+
buyingAmount, buying_amount, slippageTolerance, slippage_tolerance, ...other
|
|
20
|
+
} = params
|
|
21
|
+
const res = {
|
|
22
|
+
sellingAsset: parseAsset(sellingAsset || selling_asset, 'sellingAsset'),
|
|
23
|
+
buyingAsset: parseAsset(buyingAsset || buying_asset, 'buyingAsset'),
|
|
24
|
+
sellingAmount: parseAmount(sellingAmount || selling_amount, 'sellingAmount'),
|
|
25
|
+
//buyingAmount: parseAmount(buyingAmount || buying_amount, 'buyingAmount'),
|
|
26
|
+
slippageTolerance: parseSlippageTolerance(slippageTolerance || slippage_tolerance || 0.02, 'slippageTolerance')
|
|
27
|
+
}
|
|
28
|
+
if (res.buyingAsset === res.sellingAsset)
|
|
29
|
+
throw errors.invalidQuoteParam('buyingAsset', 'Buying asset can\'t be the same as selling asset')
|
|
30
|
+
if (res.sellingAmount === undefined)
|
|
31
|
+
throw errors.invalidQuoteParam('sellingAmount', 'Parameter "sellingAmount" parameter is required')
|
|
32
|
+
/*if (res.buyingAmount !== undefined && res.sellingAmount !== undefined)
|
|
33
|
+
throw errors.invalidQuoteParam('sellingAmount', 'Parameters "buyingAmount" and "sellingAmount" are mutually exclusive')*/
|
|
34
|
+
Object.assign(res, other) //add remaining optional params
|
|
35
|
+
Object.freeze(res)
|
|
36
|
+
return res
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseAmount(amount, parameter) {
|
|
40
|
+
if (amount === undefined)
|
|
41
|
+
return undefined
|
|
42
|
+
const parsed = parseFloat(amount)
|
|
43
|
+
if (isNaN(parsed) || parsed <= 0)
|
|
44
|
+
throw errors.invalidQuoteParam(parameter, 'Invalid asset amount: ' + amount)
|
|
45
|
+
return amount
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseSlippageTolerance(src, parameter) {
|
|
49
|
+
const tolerance = parseFloat(src)
|
|
50
|
+
if (isNaN(tolerance))
|
|
51
|
+
throw errors.invalidQuoteParam(parameter, 'Invalid slippage tolerance, number expected')
|
|
52
|
+
if (tolerance < 0)
|
|
53
|
+
throw errors.invalidQuoteParam(parameter, 'Slippage tolerance is too small, expected value >= 0')
|
|
54
|
+
if (tolerance > 0.5)
|
|
55
|
+
throw errors.invalidQuoteParam(parameter, 'Slippage tolerance is too large, expected value < 0.5 (50%)')
|
|
56
|
+
return tolerance
|
|
61
57
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {fromStroops, toStroops} from './stroops.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {'unfeasible'|'rejected'|'success'} QuoteResultStatus - Result status code returned by the server
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} QuoteDirectTradeResult - Emulated single `path_payment` trades result corresponding to the quote
|
|
9
|
+
* @property {string} selling - Estimated amount to sell
|
|
10
|
+
* @property {string} buying - Estimated amount to buy
|
|
11
|
+
* @property {string[]} path - Conversion path
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Quote received from the server
|
|
16
|
+
*/
|
|
17
|
+
export class QuoteResult {
|
|
18
|
+
/**
|
|
19
|
+
* @param {{}} result
|
|
20
|
+
* @package
|
|
21
|
+
*/
|
|
22
|
+
constructor(result) {
|
|
23
|
+
Object.assign(this, result)
|
|
24
|
+
if (result.estimatedBuyingAmount && result.directTrade) {
|
|
25
|
+
const profit = toStroops(result.estimatedBuyingAmount) - toStroops(result.directTrade.buying)
|
|
26
|
+
if (profit > 0) {
|
|
27
|
+
this.profit = fromStroops(profit)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
this.ts = new Date()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Quote timestamp
|
|
35
|
+
* @type {Date}
|
|
36
|
+
* @readonly
|
|
37
|
+
*/
|
|
38
|
+
ts
|
|
39
|
+
/**
|
|
40
|
+
* Quote status code
|
|
41
|
+
* @type {QuoteResultStatus}
|
|
42
|
+
* @readonly
|
|
43
|
+
*/
|
|
44
|
+
status
|
|
45
|
+
/**
|
|
46
|
+
* Asset to sell
|
|
47
|
+
* @type {string}
|
|
48
|
+
* @readonly
|
|
49
|
+
*/
|
|
50
|
+
sellingAsset
|
|
51
|
+
/**
|
|
52
|
+
* Asset to buy
|
|
53
|
+
* @type {string}
|
|
54
|
+
* @readonly
|
|
55
|
+
*/
|
|
56
|
+
buyingAsset
|
|
57
|
+
/**
|
|
58
|
+
* Swap slippage tolerance
|
|
59
|
+
* @type {number}
|
|
60
|
+
* @readonly
|
|
61
|
+
*/
|
|
62
|
+
slippageTolerance
|
|
63
|
+
/**
|
|
64
|
+
* Amount of the selling asset
|
|
65
|
+
* @type {string}
|
|
66
|
+
* @readonly
|
|
67
|
+
*/
|
|
68
|
+
sellingAmount
|
|
69
|
+
/**
|
|
70
|
+
* Estimated amount of buyingAsset to receive
|
|
71
|
+
* @type {string}
|
|
72
|
+
* @readonly
|
|
73
|
+
*/
|
|
74
|
+
estimatedBuyingAmount
|
|
75
|
+
/**
|
|
76
|
+
* Equivalent direct path_payment trade estimate
|
|
77
|
+
* @type {QuoteDirectTradeResult}
|
|
78
|
+
* @readonly
|
|
79
|
+
*/
|
|
80
|
+
directTrade
|
|
81
|
+
/**
|
|
82
|
+
* Difference between quoted price and estimated direct trade
|
|
83
|
+
* @type {string}
|
|
84
|
+
* @readonly
|
|
85
|
+
*/
|
|
86
|
+
profit = '0'
|
|
87
|
+
/**
|
|
88
|
+
* Error details from the server (for rejected quote requests)
|
|
89
|
+
* @type {string}
|
|
90
|
+
* @readonly
|
|
91
|
+
*/
|
|
92
|
+
error
|
|
93
|
+
}
|