@stellar-broker/client 0.4.0 → 0.5.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.
@@ -1,61 +1,57 @@
1
- import {StrKey} from '@stellar/stellar-sdk'
2
- import errors from './errors.js'
3
- import {parseAsset} from './asset.js'
4
-
5
- /**
6
- * @typedef {object} SwapQuoteParams
7
- * @property {string} sellingAsset - Asset to sell
8
- * @property {string} buyingAsset - Asset to buy
9
- * @property {string} [sellingAmount] - Amount of selling asset
10
- * @property {string} [buyingAmount] - Amount of buying asset
11
- * @property {number} [slippageTolerance] - Swap slippage tolerance (0.02 by default - 2%)
12
- * @property {string} [destination] - Trader account address
13
- */
14
-
15
- /**
16
- * @param {SwapQuoteParams} params
17
- * @return {SwapQuoteParams}
18
- */
19
- export function validateQuoteRequest(params) {
20
- const {
21
- destination, sellingAsset, selling_asset, buyingAsset, buying_asset, sellingAmount, selling_amount,
22
- buyingAmount, buying_amount, slippageTolerance, slippage_tolerance, ...other
23
- } = params
24
- const res = {
25
- sellingAsset: parseAsset(sellingAsset || selling_asset, 'sellingAsset'),
26
- buyingAsset: parseAsset(buyingAsset || buying_asset, 'buyingAsset'),
27
- sellingAmount: parseAmount(sellingAmount || selling_amount, 'sellingAmount'),
28
- buyingAmount: parseAmount(buyingAmount || buying_amount, 'buyingAmount'),
29
- slippageTolerance: parseSlippageTolerance(slippageTolerance || slippage_tolerance || 0.02, 'slippageTolerance'),
30
- flow: 'direct'
31
- }
32
- if (res.buyingAsset === res.sellingAsset)
33
- throw errors.invalidQuoteParam('buyingAsset', 'Buying asset can\'t be the same as selling asset')
34
- if (res.buyingAmount === undefined && res.sellingAmount === undefined)
35
- throw errors.invalidQuoteParam('sellingAmount', 'Either "buyingAmount" or "sellingAmount" parameter is required')
36
- if (res.buyingAmount !== undefined && res.sellingAmount !== undefined)
37
- throw errors.invalidQuoteParam('sellingAmount', 'Parameters "buyingAmount" and "sellingAmount" are mutually exclusive')
38
- Object.assign(res, other) //add remaining optional params
39
- Object.freeze(res)
40
- return res
41
- }
42
-
43
- function parseAmount(amount, parameter) {
44
- if (amount === undefined)
45
- return undefined
46
- const parsed = parseFloat(amount)
47
- if (isNaN(parsed) || parsed <= 0)
48
- throw errors.invalidQuoteParam(parameter, 'Invalid asset amount: ' + amount)
49
- return amount
50
- }
51
-
52
- function parseSlippageTolerance(src, parameter) {
53
- const tolerance = parseFloat(src)
54
- if (isNaN(tolerance))
55
- throw errors.invalidQuoteParam(parameter, 'Invalid slippage tolerance, number expected')
56
- if (tolerance < 0)
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} SwapQuoteParams
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 {SwapQuoteParams} params
14
+ * @return {SwapQuoteParams}
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
  }
@@ -1,162 +1,134 @@
1
- import {hash, Keypair, nativeToScVal, StrKey, TransactionBuilder, xdr} from '@stellar/stellar-sdk'
2
- import errors from './errors.js'
3
-
4
- /**
5
- * @param {StellarBrokerClient} client
6
- * @param {{xdr: string, hash: string, networkFee: string, [confirmed]: boolean}} txRequest
7
- * @return {Promise<string>}
8
- */
9
- export async function processTxRequest(client, txRequest) {
10
- //parse incoming transaction
11
- let tx
12
- try {
13
- tx = TransactionBuilder.fromXDR(txRequest.xdr, client.network)
14
- } catch (e) {
15
- throw errors.invalidSwapTx()
16
- }
17
- //check that transaction is correct
18
- if (!validateTransaction(client, tx))
19
- throw errors.invalidSwapTx()
20
- //if transaction has not been authorized yet by the channel account
21
- const isSorobanTx = tx.operations[0].auth?.length > 0
22
- if (isSorobanTx) {
23
- //send tx back to the server
24
- if (!tx.signatures.length) {
25
- //sign auth
26
- await authorizeInvocation(client, tx)
27
- tx = await signTx(client, tx)
28
- return tx.toXDR()//wait for the signed tx from the server to wrap it with fee bump tx
29
- }
30
- } else {
31
- //sign transaction
32
- tx = await signTx(client, tx)
33
- }
34
- //wrap with fee bump
35
- let wrapped = TransactionBuilder.buildFeeBumpTransaction(client.trader, txRequest.networkFee, tx, client.network)
36
- //sign fee bump wrapper tx
37
- wrapped = await signTx(client, wrapped)
38
- //respond with signed transaction
39
- return wrapped.toXDR()
40
- }
41
-
42
- /**
43
- * @param {StellarBrokerClient} client
44
- * @param {TransactionI} tx
45
- * @return {Promise<TransactionI>}
46
- * @private
47
- */
48
- async function authorizeInvocation(client, tx) {
49
- //from https://github.com/stellar/js-stellar-base/blob/e3d6fc3351e7d242b374c7c6057668366364a279/src/auth.js#L97
50
- const auth = tx.operations[0].auth[0]
51
- /** @type {xdr.SorobanAddressCredentials} */
52
- const addrAuth = auth.credentials().address()
53
- addrAuth.signatureExpirationLedger(tx.ledgerBounds.maxLedger + 1)
54
-
55
- const networkId = hash(Buffer.from(tx.networkPassphrase))
56
- const preimage = xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(
57
- new xdr.HashIdPreimageSorobanAuthorization({
58
- networkId,
59
- nonce: addrAuth.nonce(),
60
- invocation: auth.rootInvocation(),
61
- signatureExpirationLedger: addrAuth.signatureExpirationLedger()
62
- })
63
- )
64
- const payload = hash(preimage.toXDR())
65
- let signature
66
- if (client.authorization instanceof Keypair) {
67
- signature = client.authorization.sign(payload)
68
- } else {
69
- signature = await promisify(client.authorization(payload))
70
- }
71
- const publicKey = StrKey.encodeEd25519PublicKey(addrAuth.address().accountId().value())
72
- const sigScVal = nativeToScVal(
73
- {
74
- public_key: StrKey.decodeEd25519PublicKey(publicKey),
75
- signature
76
- },
77
- {
78
- type: {
79
- public_key: ['symbol', null],
80
- signature: ['symbol', null]
81
- }
82
- }
83
- )
84
-
85
- addrAuth.signature(xdr.ScVal.scvVec([sigScVal]))
86
- }
87
-
88
- /**
89
- * @param {StellarBrokerClient} client
90
- * @param {TransactionI} tx
91
- * @return {Promise<TransactionI>}
92
- * @private
93
- */
94
- async function signTx(client, tx) {
95
- let promise
96
- if (client.authorization instanceof Keypair) {
97
- promise = new Promise((resolve, reject) => {
98
- try {
99
- tx.sign(client.authorization)
100
- resolve(tx)
101
- } catch (e) {
102
- reject(e)
103
- }
104
- })
105
- } else {
106
- //normalize external auth result
107
- promise = promisify(client.authorization(tx))
108
- }
109
- return promise
110
- .then(tx => {
111
- if (typeof tx === 'string') { //normalize response
112
- tx = TransactionBuilder.fromXDR(tx, client.network)
113
- }
114
- if (!tx || tx.signatures?.length < 1)
115
- throw new Error(`Transaction was not signed`)
116
- return tx
117
- })
118
- .catch(e => {
119
- console.error(e)
120
- throw errors.failedToSignTx()
121
- })
122
- }
123
-
124
- /**
125
- * @param {StellarBrokerClient} client
126
- * @param {Transaction} tx
127
- * @throws {StellarBrokerError} Invalid swap transaction received
128
- */
129
- function validateTransaction(client, tx) {
130
- /*if (!(tx instanceof FeeBumpTransaction))
131
- return false
132
- const {innerTransaction} = tx
133
- if (tx.feeSource !== this.source)
134
- return false*/
135
- for (let swap of tx.operations) {
136
- if (swap.type === 'invokeHostFunction')
137
- continue //TODO: add additional validation
138
- if (swap.type !== 'pathPaymentStrictSend' && swap.type !== 'pathPaymentStrictReceive')
139
- return false
140
- const isFee = swap.destination !== client.trader
141
- if (isFee) {
142
- if (swap.type !== 'pathPaymentStrictSend')
143
- return false
144
- if ((swap.source && swap.source !== client.trader))
145
- return false
146
- } else {
147
- if ((swap.source && swap.source !== swap.destination) || swap.destination !== client.trader)
148
- return false
149
- }
150
- }
151
- //TODO: check assets and amounts
152
- return true
153
- }
154
-
155
- function promisify(callResult) {
156
- if (!(callResult instanceof Promise)) {
157
- if (!callResult)
158
- return Promise.reject()
159
- callResult = Promise.resolve(callResult)
160
- }
161
- return callResult
1
+ import {hash, Keypair, nativeToScVal, StrKey, TransactionBuilder, xdr} from '@stellar/stellar-sdk'
2
+ import errors from './errors.js'
3
+
4
+ /**
5
+ * @param {StellarBrokerClient} client
6
+ * @param {{xdr: string, hash: string, networkFee: string, [confirmed]: boolean}} txRequest
7
+ * @return {Promise<string>}
8
+ */
9
+ export async function processTxRequest(client, txRequest) {
10
+ //parse incoming transaction
11
+ let tx
12
+ try {
13
+ tx = TransactionBuilder.fromXDR(txRequest.xdr, client.network)
14
+ } catch (e) {
15
+ throw errors.invalidSwapTx()
16
+ }
17
+ //check that transaction is correct
18
+ if (!validateTransaction(client, tx))
19
+ throw errors.invalidSwapTx()
20
+ //if transaction has not been authorized yet by the channel account
21
+ const isSorobanTx = tx.operations[0].auth?.length > 0
22
+ if (isSorobanTx) {
23
+ //send tx back to the server
24
+ if (!tx.signatures.length) {
25
+ //sign auth
26
+ await authorizeInvocation(client, tx)
27
+ tx = await signTx(client, tx)
28
+ return tx.toXDR()//wait for the signed tx from the server to wrap it with fee bump tx
29
+ }
30
+ } else {
31
+ //sign transaction
32
+ tx = await signTx(client, tx)
33
+ }
34
+ //wrap with fee bump
35
+ let wrapped = TransactionBuilder.buildFeeBumpTransaction(client.trader, txRequest.networkFee, tx, client.network)
36
+ //sign fee bump wrapper tx
37
+ wrapped = await signTx(client, wrapped)
38
+ //respond with signed transaction
39
+ return wrapped.toXDR()
40
+ }
41
+
42
+ /**
43
+ * @param {StellarBrokerClient} client
44
+ * @param {TransactionI} tx
45
+ * @return {Promise<TransactionI>}
46
+ * @private
47
+ */
48
+ async function authorizeInvocation(client, tx) {
49
+ //from https://github.com/stellar/js-stellar-base/blob/e3d6fc3351e7d242b374c7c6057668366364a279/src/auth.js#L97
50
+ const auth = tx.operations[0].auth[0]
51
+ /** @type {xdr.SorobanAddressCredentials} */
52
+ const addrAuth = auth.credentials().address()
53
+ addrAuth.signatureExpirationLedger(tx.ledgerBounds.maxLedger + 1)
54
+
55
+ const networkId = hash(Buffer.from(tx.networkPassphrase))
56
+ const preimage = xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(
57
+ new xdr.HashIdPreimageSorobanAuthorization({
58
+ networkId,
59
+ nonce: addrAuth.nonce(),
60
+ invocation: auth.rootInvocation(),
61
+ signatureExpirationLedger: addrAuth.signatureExpirationLedger()
62
+ })
63
+ )
64
+ const payload = hash(preimage.toXDR())
65
+ const signature = await client.authorization.authorize(payload)
66
+ const publicKey = StrKey.encodeEd25519PublicKey(addrAuth.address().accountId().value())
67
+ const sigScVal = nativeToScVal(
68
+ {
69
+ public_key: StrKey.decodeEd25519PublicKey(publicKey),
70
+ signature
71
+ },
72
+ {
73
+ type: {
74
+ public_key: ['symbol', null],
75
+ signature: ['symbol', null]
76
+ }
77
+ }
78
+ )
79
+
80
+ addrAuth.signature(xdr.ScVal.scvVec([sigScVal]))
81
+ }
82
+
83
+ /**
84
+ * @param {StellarBrokerClient} client
85
+ * @param {TransactionI} tx
86
+ * @return {Promise<TransactionI>}
87
+ * @private
88
+ */
89
+ function signTx(client, tx) {
90
+ return client.authorization.authorize(tx)
91
+ .then(tx => {
92
+ if (typeof tx === 'string') { //normalize response
93
+ tx = TransactionBuilder.fromXDR(tx, client.network)
94
+ }
95
+ if (!tx || tx.signatures?.length < 1)
96
+ throw new Error(`Transaction was not signed`)
97
+ return tx
98
+ })
99
+ .catch(e => {
100
+ console.error(e)
101
+ throw errors.failedToSignTx()
102
+ })
103
+ }
104
+
105
+ /**
106
+ * @param {StellarBrokerClient} client
107
+ * @param {Transaction} tx
108
+ * @throws {StellarBrokerError} Invalid swap transaction received
109
+ */
110
+ function validateTransaction(client, tx) {
111
+ /*if (!(tx instanceof FeeBumpTransaction))
112
+ return false
113
+ const {innerTransaction} = tx
114
+ if (tx.feeSource !== this.source)
115
+ return false*/
116
+ for (let swap of tx.operations) {
117
+ if (swap.type === 'invokeHostFunction')
118
+ continue //TODO: add additional validation
119
+ if (swap.type !== 'pathPaymentStrictSend' && swap.type !== 'pathPaymentStrictReceive')
120
+ return false
121
+ const isFee = swap.destination !== client.trader
122
+ if (isFee) {
123
+ if (swap.type !== 'pathPaymentStrictSend')
124
+ return false
125
+ if ((swap.source && swap.source !== client.trader))
126
+ return false
127
+ } else {
128
+ if ((swap.source && swap.source !== swap.destination) || swap.destination !== client.trader)
129
+ return false
130
+ }
131
+ }
132
+ //TODO: check assets and amounts
133
+ return true
162
134
  }