@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/tx-processor.js
CHANGED
|
@@ -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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
}
|