@stellar-broker/client 0.2.0 → 0.4.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 +10 -7
- package/lib/stellarbroker.js +1 -1
- package/lib/stellarbroker.js.map +1 -1
- package/package.json +4 -4
- package/src/asset.js +47 -0
- package/src/client.js +87 -150
- package/src/errors.js +6 -3
- package/src/estimate.js +4 -2
- package/src/index.d.ts +7 -1
- package/src/index.js +1 -0
- package/src/quote-request.js +2 -33
- package/src/stroops.js +65 -0
- package/src/tx-processor.js +162 -0
- package/webpack.config.cjs +0 -43
package/README.md
CHANGED
|
@@ -8,10 +8,9 @@ npm i @stellar-broker/client
|
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
|
-
### Example -
|
|
11
|
+
### Example - connect and trade with StellarBroker
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
It might consume more resources on the client side since the client keeps an open connection and constantly receives
|
|
13
|
+
The client connects to the router server, keeps an open connection and constantly receives
|
|
15
14
|
price quote updates from the server.
|
|
16
15
|
|
|
17
16
|
```js
|
|
@@ -22,7 +21,7 @@ const client = new StellarBrokerClient({partnerKey: '<your_partner_key>'})
|
|
|
22
21
|
|
|
23
22
|
//subscribe to the quote notifications
|
|
24
23
|
client.on('quote', e => {
|
|
25
|
-
console.log('Received quote from the server', e.quote)
|
|
24
|
+
console.log('Received route quote from the server', e.quote)
|
|
26
25
|
/*{
|
|
27
26
|
"status": "success",
|
|
28
27
|
"sellingAsset": "USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
|
|
@@ -40,6 +39,10 @@ client.on('quote', e => {
|
|
|
40
39
|
}*/
|
|
41
40
|
})
|
|
42
41
|
|
|
42
|
+
client.on('paused', e => {
|
|
43
|
+
console.log('Quotation paused due to inactivity. Call `quote` method to resume.')
|
|
44
|
+
})
|
|
45
|
+
|
|
43
46
|
client.on('finished', e => {
|
|
44
47
|
console.log('Trade finished', e.result)
|
|
45
48
|
/*{
|
|
@@ -92,12 +95,12 @@ client.close()
|
|
|
92
95
|
### Example - Get swap estimate without trading
|
|
93
96
|
|
|
94
97
|
Swap estimate may be handy in scenarios when the client has no intention to trade or receive price quote updates in
|
|
95
|
-
streaming mode, and just
|
|
98
|
+
streaming mode, and just wants to get a single price quote.
|
|
96
99
|
|
|
97
100
|
```js
|
|
98
|
-
import
|
|
101
|
+
import {estimateSwap} from '@stellar-broker/client'
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
estimateSwap({
|
|
101
104
|
sellingAsset: 'xlm',
|
|
102
105
|
buyingAsset: 'USD-GDK2GNB4Q6FKNW2GNJIQFARI4RMSHV5DN5G4BBXX2F24RT5I4QT7TWZ7',
|
|
103
106
|
sellingAmount: '1000',
|
package/lib/stellarbroker.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("@stellar/stellar-
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("@stellar/stellar-sdk")):"function"==typeof define&&define.amd?define(["@stellar/stellar-sdk"],e):"object"==typeof exports?exports.stellarbroker=e(require("@stellar/stellar-sdk")):t.stellarbroker=e(t["@stellar/stellar-sdk"])}(this,(t=>(()=>{"use strict";var e={346:e=>{e.exports=t}},o={};function s(t){var i=o[t];if(void 0!==i)return i.exports;var r=o[t]={exports:{}};return e[t](r,r.exports,s),r.exports}s.d=(t,e)=>{for(var o in e)s.o(e,o)&&!s.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var i={};s.d(i,{default:()=>b});var r=s(346);class n extends Error{constructor(t,e){super(e),this.code=t}code=0}const a={invalidInitParam:t=>new n(1,`Invalid parameter value: "${t}"`),invalidAuthorizationParam:()=>new n(2,"Invalid authorization secret key or callback provided"),notConnected:()=>new n(3,"Client not connected to the server"),quoteNotSet:()=>new n(11,"Price quote not available"),quoteExpired:()=>new n(12,"Price quote expired"),quoteError:t=>new n(13,"Price quotation error: "+t),invalidQuoteParam:(t="asset",e)=>new n(14,`Invalid quote request parameter: "${t}". ${e}`),tradeInProgress:()=>new n(20,"Cannot change quote while trade is in progress"),invalidSwapTx:()=>new n(21,"Invalid swap transaction received from the server"),failedToSignTx:()=>new n(22,"Failed to sign received transaction"),unsupportedEventType:t=>new n(31,"Unknown event type: "+t),serverError:t=>new n(101,t)};function u(t,e,o){const s=new CustomEvent(t,{detail:e});return s[o||t]=e,s}function c(t,e){if("string"==typeof t){if("XLM"===t||"xlm"===t||"native"===t)return"XLM";if(t.includes(":")){const[o,s]=t.split(":");return l(o),d(s,e),o+"-"+s}if(t.includes("-")){const[o,s]=t.split("-");return l(o),d(s,e),t}}throw a.invalidQuoteParam(e,"Invalid asset")}function l(t,e){if(!/^[a-zA-Z0-9]{1,12}$/.test(t))throw a.invalidQuoteParam(e,"Invalid asset code: "+(t||"missing"));return t}function d(t,e){if(!t||!r.StrKey.isValidEd25519PublicKey(t))throw a.invalidQuoteParam(e,"Invalid account address: "+(t||"missing"));return t}function h(t){const{destination:e,sellingAsset:o,selling_asset:s,buyingAsset:i,buying_asset:r,sellingAmount:n,selling_amount:u,buyingAmount:l,buying_amount:d,slippageTolerance:h,slippage_tolerance:g,...m}=t,y={sellingAsset:c(o||s,"sellingAsset"),buyingAsset:c(i||r,"buyingAsset"),sellingAmount:p(n||u,"sellingAmount"),buyingAmount:p(l||d,"buyingAmount"),slippageTolerance:f(h||g||.02,"slippageTolerance"),flow:"direct"};if(y.buyingAsset===y.sellingAsset)throw a.invalidQuoteParam("buyingAsset","Buying asset can't be the same as selling asset");if(void 0===y.buyingAmount&&void 0===y.sellingAmount)throw a.invalidQuoteParam("sellingAmount",'Either "buyingAmount" or "sellingAmount" parameter is required');if(void 0!==y.buyingAmount&&void 0!==y.sellingAmount)throw a.invalidQuoteParam("sellingAmount",'Parameters "buyingAmount" and "sellingAmount" are mutually exclusive');return Object.assign(y,m),Object.freeze(y),y}function p(t,e){if(void 0===t)return;const o=parseFloat(t);if(isNaN(o)||o<=0)throw a.invalidQuoteParam(e,"Invalid asset amount: "+t);return t}function f(t,e){const o=parseFloat(t);if(isNaN(o))throw a.invalidQuoteParam(e,"Invalid slippage tolerance, number expected");if(o<0)throw a.invalidQuoteParam(e,"Slippage tolerance is too small, expected value >= 0");if(o>.5)throw a.invalidQuoteParam(e,"Slippage tolerance is too large, expected value < 0.5 (50%)");return o}async function g(t,e){let o;return o=t.authorization instanceof r.Keypair?new Promise(((o,s)=>{try{e.sign(t.authorization),o(e)}catch(t){s(t)}})):m(t.authorization(e)),o.then((e=>{if("string"==typeof e&&(e=r.TransactionBuilder.fromXDR(e,t.network)),!e||e.signatures?.length<1)throw new Error("Transaction was not signed");return e})).catch((t=>{throw console.error(t),a.failedToSignTx()}))}function m(t){if(!(t instanceof Promise)){if(!t)return Promise.reject();t=Promise.resolve(t)}return t}class y{constructor(t){if(this.partnerKey=t.partnerKey,this.emitter=new EventTarget,this.network=r.Networks[(t.network||"PUBLIC").toUpperCase()]||t.network,this.flow=t.flow||"direct",!t.account||!r.StrKey.isValidEd25519PublicKey(t.account))throw a.invalidQuoteParam("account","Invalid trader account address: "+(t.account?t.account:"missing"));if(this.trader=t.account,"string"==typeof t.authorization)try{this.authorization=r.Keypair.fromSecret(t.authorization)}catch(t){throw a.invalidAuthorizationParam()}else"function"==typeof t.authorization&&(this.authorization=t.authorization)}uid;status="disconnected";socket;emitter;origin="https://api.stellar.broker";network="";quoteRequest;lastQuote;tradeQuote;partnerKey;authorization;trader;connect(){return this.socket?.readyState===WebSocket.OPEN?Promise.resolve(this):(this.socket=new WebSocket(this.origin+"/ws?partner="+encodeURIComponent(this.partnerKey)),this.socket.onmessage=this.processMessage.bind(this),this.socket.onclose=()=>{"disconnected"!==this.status&&(this.status="disconnected",this.notifyError(a.notConnected())),console.log("Connection closed")},this.socket.onerror=t=>console.error(t),new Promise(((t,e)=>{const o=setTimeout((()=>e(this)),2e3);this.onSocketOpen=()=>{this.status="ready",this.heartbeat(),this.onSocketOpen=void 0,clearTimeout(o),t(this)}})))}processMessage(t){const e=JSON.parse(t.data);switch(e.type){case"connected":this.uid=e.uid,this.onSocketOpen&&this.onSocketOpen(),"disconnected"===this.status&&(this.status="ready");break;case"quote":this.lastQuote=e.quote,this.lastQuote.ts=new Date,this.emitter.dispatchEvent(u("quote",this.lastQuote));break;case"paused":this.emitter.dispatchEvent(u("paused",{}));break;case"tx":if("trade"!==this.status)return void console.log("Received tx in non-trading state",this.status,e);(async function(t,e){let o;try{o=r.TransactionBuilder.fromXDR(e.xdr,t.network)}catch(t){throw a.invalidSwapTx()}if(!function(t,e){for(let o of e.operations)if("invokeHostFunction"!==o.type){if("pathPaymentStrictSend"!==o.type&&"pathPaymentStrictReceive"!==o.type)return!1;if(o.destination!==t.trader){if("pathPaymentStrictSend"!==o.type)return!1;if(o.source&&o.source!==t.trader)return!1}else if(o.source&&o.source!==o.destination||o.destination!==t.trader)return!1}return!0}(t,o))throw a.invalidSwapTx();if(o.operations[0].auth?.length>0){if(!o.signatures.length)return await async function(t,e){const o=e.operations[0].auth[0],s=o.credentials().address();s.signatureExpirationLedger(e.ledgerBounds.maxLedger+1);const i=(0,r.hash)(Buffer.from(e.networkPassphrase)),n=r.xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(new r.xdr.HashIdPreimageSorobanAuthorization({networkId:i,nonce:s.nonce(),invocation:o.rootInvocation(),signatureExpirationLedger:s.signatureExpirationLedger()})),a=(0,r.hash)(n.toXDR());let u;u=t.authorization instanceof r.Keypair?t.authorization.sign(a):await m(t.authorization(a));const c=r.StrKey.encodeEd25519PublicKey(s.address().accountId().value()),l=(0,r.nativeToScVal)({public_key:r.StrKey.decodeEd25519PublicKey(c),signature:u},{type:{public_key:["symbol",null],signature:["symbol",null]}});s.signature(r.xdr.ScVal.scvVec([l]))}(t,o),o=await g(t,o),o.toXDR()}else o=await g(t,o);let s=r.TransactionBuilder.buildFeeBumpTransaction(t.trader,e.networkFee,o,t.network);return s=await g(t,s),s.toXDR()})(this,e).then((t=>{this.send({type:"tx",hash:e.hash,xdr:t})})).catch((t=>this.notifyError(t)));break;case"stop":this.emitter.dispatchEvent(u("finished",{status:e.status,sold:e.sold,bought:e.bought},"result")),this.status="ready",this.tradeQuote=void 0;break;case"progress":this.emitter.dispatchEvent(u("progress",{sold:e.sold,bought:e.bought},"status"));break;case"ping":e.uid===this.uid&&(this.heartbeat(),this.send({type:"pong",uid:this.uid}));break;case"error":this.stop(),this.notifyError(e.error);break;default:console.log("Unknown message type: "+e.type)}}quote(t){if("trade"===this.status)throw a.tradeInProgress();this.quoteRequest=h(t),this.status="quote",this.connect().then((()=>{this.status="quote",this.send({type:"quote",...this.quoteRequest})}))}confirmQuote(){if("disconnected"===this.status)throw a.notConnected();if("trade"===this.status)throw a.tradeInProgress();if("quote"!==this.status||!this.lastQuote)throw a.quoteNotSet();if(new Date-this.lastQuote.ts>7e3)throw a.quoteExpired();if("success"!==this.lastQuote.status)throw a.quoteError(this.lastQuote.error||"quote not available");this.tradeQuote=this.lastQuote,this.send({type:"trade",account:this.trader}),this.status="trade"}stop(){"trade"!==this.status&&"quote"!==this.status||(this.send({type:"stop"}),this.tradeQuote=void 0,this.status="ready")}send(t){this.socket.send(JSON.stringify(t))}heartbeat(){clearTimeout(this.pingHandler),this.pingHandler=setTimeout((()=>{console.warn("Lost connection with the server"),this.socket.close()}),7e3)}on(t,e){v(t),this.emitter.addEventListener(t,e)}once(t,e){v(t),this.emitter.addEventListener(t,e,{once:!0})}off(t,e){v(t),this.emitter.removeEventListener(t,e)}close(){try{this.status="disconnected",this.socket.close()}catch(t){}}notifyError(t){console.error(t);try{this.emitter.dispatchEvent(u("error",t instanceof Error?t.message:t))}catch(t){console.error(t)}}}function v(t){if(!w.includes(t))throw a.unsupportedEventType(t)}const w=["quote","paused","progress","finished","error"];y.estimateSwap=async function(t){const e=Object.entries(h(t)).filter((t=>void 0!==t[1])).map((([t,e])=>encodeURIComponent(t)+"="+encodeURIComponent(e))),o=(t.origin||"https://api.stellar.broker")+"/quote?"+e.join("&");try{const t=await fetch(o).then((t=>t.json()));if("success"!==t.status)throw a.quoteError(t.error||"Quote not available");return t}catch(t){if(t instanceof n)throw t;throw a.quoteError("Failed to fetch quote")}};const b=y;return i.default})()));
|
|
2
2
|
//# sourceMappingURL=stellarbroker.js.map
|
package/lib/stellarbroker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stellarbroker.js","mappings":"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","sources":["webpack://stellarbroker/webpack/universalModuleDefinition","webpack://stellarbroker/external umd \"@stellar/stellar-base\"","webpack://stellarbroker/webpack/bootstrap","webpack://stellarbroker/webpack/runtime/define property getters","webpack://stellarbroker/webpack/runtime/hasOwnProperty shorthand","webpack://stellarbroker/./src/errors.js","webpack://stellarbroker/./src/events.js","webpack://stellarbroker/./src/quote-request.js","webpack://stellarbroker/./src/client.js","webpack://stellarbroker/./src/index.js","webpack://stellarbroker/./src/estimate.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"@stellar/stellar-base\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"@stellar/stellar-base\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"stellarbroker\"] = factory(require(\"@stellar/stellar-base\"));\n\telse\n\t\troot[\"stellarbroker\"] = factory(root[\"@stellar/stellar-base\"]);\n})(this, (__WEBPACK_EXTERNAL_MODULE__755__) => {\nreturn ","module.exports = __WEBPACK_EXTERNAL_MODULE__755__;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","export class StellarBrokerError extends Error {\r\n constructor(code, message) {\r\n super(message)\r\n this.code = code\r\n }\r\n\r\n /**\r\n * Numeric error code\r\n * @type {number}\r\n * @readonly\r\n */\r\n code = 0\r\n}\r\n\r\nconst errors = {\r\n invalidInitParam(param) {\r\n return new StellarBrokerError(1, `Invalid parameter value: \"${param}\"`)\r\n },\r\n invalidAuthorizationParam() {\r\n return new StellarBrokerError(2, 'Invalid authorization secret key or callback provided')\r\n },\r\n quoteNotSet() {\r\n return new StellarBrokerError(11, 'Price quote not available')\r\n },\r\n quoteExpired() {\r\n return new StellarBrokerError(12, 'Price quote expired')\r\n },\r\n quoteError(message) {\r\n return new StellarBrokerError(13, 'Price quotation error: ' + message)\r\n },\r\n invalidQuoteParam(invalidParamName, details) {\r\n return new StellarBrokerError(14, `Invalid quote request parameter: \"${invalidParamName}\". ${details}`)\r\n },\r\n tradeInProgress() {\r\n return new StellarBrokerError(20, 'Cannot change quote while trade is in progress')\r\n },\r\n invalidSwapTx() {\r\n return new StellarBrokerError(21, 'Invalid swap transaction received')\r\n },\r\n failedToSignTx() {\r\n return new StellarBrokerError(22, 'Failed to sign received transaction')\r\n },\r\n unsupportedEventType(type) {\r\n return new StellarBrokerError(31, 'Unknown event type: ' + type)\r\n },\r\n serverError(message) {\r\n return new StellarBrokerError(101, 'Server error: ' + message)\r\n }\r\n}\r\n\r\nexport default errors","/**\r\n * @param {string} type\r\n * @param {{}} data\r\n * @param {string} [key]\r\n * @return {CustomEvent}\r\n */\r\nexport function buildEvent(type, data, key) {\r\n const evt = new CustomEvent(type, {detail: data})\r\n evt[key || type] = data\r\n return evt\r\n}","import {StrKey} from '@stellar/stellar-base'\r\nimport errors from './errors.js'\r\n\r\n/**\r\n * @typedef {object} SwapQuoteParams\r\n * @property {string} sellingAsset - Asset to sell\r\n * @property {string} buyingAsset - Asset to buy\r\n * @property {string} [sellingAmount] - Amount of selling asset\r\n * @property {string} [buyingAmount] - Amount of buying asset\r\n * @property {number} [slippageTolerance] - Swap slippage tolerance (0.02 by default - 2%)\r\n * @property {string} [destination] - Trader account address\r\n */\r\n\r\n/**\r\n * @param {SwapQuoteParams} params\r\n * @return {SwapQuoteParams}\r\n */\r\nexport function validateQuoteRequest(params) {\r\n const {\r\n destination, sellingAsset, selling_asset, buyingAsset, buying_asset, sellingAmount, selling_amount,\r\n buyingAmount, buying_amount, slippageTolerance, slippage_tolerance, ...other\r\n } = params\r\n const res = {\r\n sellingAsset: parseAsset(sellingAsset || selling_asset, 'sellingAsset'),\r\n buyingAsset: parseAsset(buyingAsset || buying_asset, 'buyingAsset'),\r\n sellingAmount: parseAmount(sellingAmount || selling_amount, 'sellingAmount'),\r\n buyingAmount: parseAmount(buyingAmount || buying_amount, 'buyingAmount'),\r\n slippageTolerance: parseSlippageTolerance(slippageTolerance || slippage_tolerance || 0.02, 'slippageTolerance'),\r\n flow: 'direct'\r\n }\r\n if (res.buyingAsset === res.sellingAsset)\r\n throw errors.invalidQuoteParam('buyingAsset', 'Buying asset can\\'t be the same as selling asset')\r\n if (res.buyingAmount === undefined && res.sellingAmount === undefined)\r\n throw errors.invalidQuoteParam('sellingAmount', 'Either \"buyingAmount\" or \"sellingAmount\" parameter is required')\r\n if (res.buyingAmount !== undefined && res.sellingAmount !== undefined)\r\n throw errors.invalidQuoteParam('sellingAmount', 'Parameters \"buyingAmount\" and \"sellingAmount\" are mutually exclusive')\r\n Object.assign(res, other) //add remaining optional params\r\n Object.freeze(res)\r\n return res\r\n}\r\n\r\nfunction parseAsset(asset, parameter) {\r\n if (typeof asset === 'string') {\r\n if (asset === 'XLM' || asset === 'xlm' || asset === 'native')\r\n return 'XLM'\r\n if (asset.includes(':')) {\r\n const [code, issuer] = asset.split(':')\r\n validateCode(code)\r\n validateAccount(issuer, parameter)\r\n return code + '-' + issuer\r\n }\r\n if (asset.includes('-')) {\r\n const [code, issuer] = asset.split('-')\r\n validateCode(code)\r\n validateAccount(issuer, parameter)\r\n return asset\r\n }\r\n }\r\n throw errors.invalidQuoteParam(parameter, 'Invalid asset')\r\n}\r\n\r\nfunction validateCode(code, parameter) {\r\n if (!/^[a-zA-Z0-9]{1,12}$/.test(code))\r\n throw errors.invalidQuoteParam(parameter, 'Invalid asset code: ' + (!code ? 'missing' : code))\r\n return code\r\n}\r\n\r\nfunction validateAccount(account, param) {\r\n if (!account || !StrKey.isValidEd25519PublicKey(account))\r\n throw errors.invalidQuoteParam(param, 'Invalid account address: ' + (!account ? 'missing' : account))\r\n return account\r\n}\r\n\r\nfunction parseAmount(amount, parameter) {\r\n if (amount === undefined)\r\n return undefined\r\n const parsed = parseFloat(amount)\r\n if (isNaN(parsed) || parsed <= 0)\r\n throw errors.invalidQuoteParam(parameter, 'Invalid asset amount: ' + amount)\r\n return amount\r\n}\r\n\r\nfunction parseSlippageTolerance(src, parameter) {\r\n const tolerance = parseFloat(src)\r\n if (isNaN(tolerance))\r\n throw errors.invalidQuoteParam(parameter, 'Invalid slippage tolerance, number expected')\r\n if (tolerance < 0)\r\n throw errors.invalidQuoteParam(parameter, 'Slippage tolerance is too small, expected value >= 0')\r\n if (tolerance > 0.5)\r\n throw errors.invalidQuoteParam(parameter, 'Slippage tolerance is too large, expected value < 0.5 (50%)')\r\n return tolerance\r\n}","import {Networks, TransactionBuilder, Keypair, FeeBumpTransaction, StrKey} from '@stellar/stellar-base'\r\nimport errors from './errors.js'\r\nimport {buildEvent} from './events.js'\r\nimport {validateQuoteRequest} from './quote-request.js'\r\nimport {estimateSwap} from './estimate.js'\r\n\r\nexport default class StellarBrokerClient {\r\n /**\r\n * @param {ClientInitializationParams} params\r\n */\r\n constructor(params) {\r\n this.partnerKey = params.partnerKey\r\n this.emitter = new EventTarget()\r\n this.network = Networks[(params.network || 'PUBLIC').toUpperCase()] || params.network\r\n this.flow = params.flow || 'direct'\r\n }\r\n\r\n /**\r\n * @type {ClientStatus}\r\n * @readonly\r\n */\r\n status = 'disconnected'\r\n /**\r\n * @type {WebSocket}\r\n * @private\r\n */\r\n socket\r\n /**\r\n * @type {EventTarget}\r\n * @private\r\n */\r\n emitter\r\n /**\r\n * @type {string}\r\n * @private\r\n */\r\n origin = 'https://api.stellar.broker'\r\n /**\r\n * Stellar network passphrase\r\n * @type {string}\r\n * @readonly\r\n */\r\n network = ''\r\n /**\r\n * Last quote request submitted by the client\r\n * @type {SwapQuoteParams}\r\n * @readonly\r\n */\r\n quoteRequest\r\n /**\r\n * Last quote received from the sever\r\n * @type {SwapQuoteResult}\r\n * @readonly\r\n */\r\n lastQuote\r\n /**\r\n * Current quote accepted for the trading\r\n * @private\r\n */\r\n tradeQuote\r\n /**\r\n * API key of the partner\r\n * @type {string}\r\n * @readonly\r\n */\r\n partnerKey\r\n /**\r\n * Account secret key (for direct swap flow)\r\n * @type {Keypair|ClientAuthorizationCallback}\r\n * @private\r\n */\r\n authorization\r\n\r\n /**\r\n * Trader account public key\r\n * @type {string}\r\n * @readonly\r\n */\r\n source\r\n\r\n /**\r\n * Connect to the StellarBroker server\r\n * @return {Promise<StellarBrokerClient>}\r\n */\r\n connect() {\r\n if (this.socket?.readyState === WebSocket.OPEN)\r\n return Promise.resolve(this) //already opened\r\n\r\n this.socket = new WebSocket(this.origin + '/ws?partner=' + encodeURIComponent(this.partnerKey))\r\n this.socket.onmessage = this.processMessage.bind(this)\r\n this.socket.onclose = () => {\r\n console.log('Connection closed')\r\n this.status = 'disconnected'\r\n }\r\n\r\n this.socket.onerror = e => console.error(e)\r\n\r\n return new Promise((confirm, reject) => { //TODO: use 'once'\r\n const expirationTimeout = setTimeout(() => reject(this), 2000) // 2s timeout\r\n this.onSocketOpen = () => {\r\n this.status = 'ready'\r\n this.heartbeat()\r\n this.onSocketOpen = undefined\r\n clearTimeout(expirationTimeout)\r\n confirm(this)\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Process incoming message\r\n * @param message\r\n * @private\r\n */\r\n processMessage(message) {\r\n const raw = JSON.parse(message.data)\r\n switch (raw.type) {\r\n case 'connected':\r\n if (this.onSocketOpen) {\r\n this.onSocketOpen()\r\n }\r\n break\r\n case 'quote':\r\n this.lastQuote = raw.quote\r\n this.lastQuote.ts = new Date()\r\n //send event to the client app\r\n this.emitter.dispatchEvent(buildEvent('quote', this.lastQuote))\r\n break\r\n case 'tx':\r\n if (this.status !== 'trade') {\r\n console.log('Received tx in non-trading state', this.status, raw)\r\n return //skip unless trading is in progress\r\n }\r\n this.processTxRequest(raw)\r\n break\r\n case 'stop':\r\n this.emitter.dispatchEvent(buildEvent('finished', {\r\n status: raw.status,\r\n sold: raw.sold,\r\n bought: raw.bought\r\n }, 'result'))\r\n this.status = 'ready'\r\n this.tradeQuote = undefined\r\n this.source = undefined\r\n break\r\n case 'progress':\r\n this.emitter.dispatchEvent(buildEvent('progress', {\r\n sold: raw.sold,\r\n bought: raw.bought\r\n }, 'status'))\r\n break\r\n case 'ping':\r\n this.heartbeat()\r\n this.send({type: 'pong'})\r\n break\r\n case 'error':\r\n this.stop()\r\n this.emitter.dispatchEvent(buildEvent('error', 'Server error: ' + raw.error))\r\n break\r\n default:\r\n console.log('Unknown message type: ' + raw.type)\r\n break\r\n }\r\n }\r\n\r\n /**\r\n * Request swap quote\r\n * @param {SwapQuoteParams} params - Quote parameters\r\n */\r\n quote(params) {\r\n if (this.status === 'trade')\r\n throw errors.tradeInProgress()\r\n this.quoteRequest = validateQuoteRequest(params)\r\n this.status = 'quote'\r\n this.connect()\r\n .then(() => {\r\n this.send({\r\n type: 'quote',\r\n ...this.quoteRequest\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Stop quotation/trading\r\n */\r\n stop() {\r\n if (this.status !== 'trade' && this.status !== 'quote')\r\n return\r\n this.send({\r\n type: 'stop'\r\n })\r\n this.tradeQuote = undefined\r\n this.source = undefined\r\n this.status = 'ready'\r\n }\r\n\r\n /**\r\n * Confirm current quote and start trading\r\n * @param {string} account - Trader account address\r\n * @param {string|ClientAuthorizationCallback} authorization - Authorization method, either account secret key or an authorization callback\r\n */\r\n confirmQuote(account, authorization) {\r\n if (this.status !== 'quote')\r\n throw errors.tradeInProgress()\r\n if (!this.lastQuote)\r\n throw errors.quoteNotSet()\r\n if ((new Date() - this.lastQuote.ts) > 7000) //do not allow stale quotes quoted more than 7s ago\r\n throw errors.quoteExpired()\r\n if (this.lastQuote.status !== 'success')\r\n throw errors.quoteError(this.lastQuote.error || 'quote not available')\r\n if (!account || !StrKey.isValidEd25519PublicKey(account))\r\n throw errors.invalidQuoteParam('account', 'Invalid trader account address: ' + (!account ? 'missing' : account))\r\n if (typeof authorization === 'string') {\r\n try {\r\n this.authorization = Keypair.fromSecret(authorization)\r\n } catch (e) {\r\n throw errors.invalidAuthorizationParam()\r\n }\r\n } else if (typeof authorization === 'function') {\r\n this.authorization = authorization\r\n }\r\n this.tradeQuote = this.lastQuote\r\n this.source = account\r\n this.send({\r\n type: 'trade',\r\n account\r\n })\r\n this.status = 'trade'\r\n }\r\n\r\n /**\r\n * @param {{xdr: string, hash: string}} raw\r\n * @private\r\n */\r\n processTxRequest(raw) {\r\n //parse incoming transaction\r\n let tx\r\n try {\r\n tx = TransactionBuilder.fromXDR(raw.xdr, this.network)\r\n } catch (e) {\r\n throw errors.invalidSwapTx()\r\n }\r\n //check that transaction is correct\r\n if (!this.validateTransaction(tx))\r\n throw errors.invalidSwapTx()\r\n //sign transaction\r\n this.authorizeTx(tx, raw.networkFee)\r\n .then(tx => {\r\n if (!tx || tx.signatures.length < 1)\r\n return console.error(`Transaction ${raw.hash} not signed`)\r\n //respond with signed transaction\r\n this.send({\r\n type: 'tx',\r\n hash: raw.hash,\r\n xdr: tx.toXDR()\r\n })\r\n })\r\n .catch(e => {\r\n console.error(e)\r\n throw errors.failedToSignTx()\r\n })\r\n }\r\n\r\n /**\r\n * @param {Transaction} tx\r\n * @param {string} networkFee\r\n * @return {Promise<FeeBumpTransaction>}\r\n * @private\r\n */\r\n async authorizeTx(tx, networkFee) {\r\n //sign tx\r\n if (this.authorization instanceof Keypair) {\r\n tx.sign(this.authorization)\r\n } else {\r\n tx = await ensurePromise(this.authorization(tx))\r\n }\r\n //wrap with fee bump\r\n let wrapped = TransactionBuilder.buildFeeBumpTransaction(this.source, networkFee, tx, this.network)\r\n //sign fee bump wrapper tx\r\n if (this.authorization instanceof Keypair) {\r\n wrapped.sign(this.authorization)\r\n } else {\r\n wrapped = await ensurePromise(this.authorization(wrapped))\r\n }\r\n return wrapped\r\n }\r\n\r\n /**\r\n * @param {Transaction} tx\r\n * @throws {StellarBrokerError} Invalid swap transaction received\r\n */\r\n validateTransaction(tx) {\r\n /*if (!(tx instanceof FeeBumpTransaction))\r\n return false\r\n const {innerTransaction} = tx\r\n if (tx.feeSource !== this.source)\r\n return false*/\r\n for (let swap of tx.operations) {\r\n if (swap.type !== 'pathPaymentStrictSend' && swap.type !== 'pathPaymentStrictReceive')\r\n return false\r\n const isFee = swap.destination !== this.source\r\n if (isFee) {\r\n if (swap.type !== 'pathPaymentStrictSend')\r\n return false\r\n if ((swap.source && swap.source !== this.source))\r\n return false\r\n } else {\r\n if ((swap.source && swap.source !== swap.destination) || swap.destination !== this.source)\r\n return false\r\n }\r\n }\r\n //TODO: check assets and amounts\r\n return true\r\n }\r\n\r\n /**\r\n * @param {{}} data\r\n * @private\r\n */\r\n send(data) {\r\n this.socket.send(JSON.stringify(data))\r\n }\r\n\r\n heartbeat() {\r\n clearTimeout(this.pingHandler)\r\n this.pingHandler = setTimeout(() => {\r\n console.warn('Lost connection with the server')\r\n this.socket.close()\r\n }, 11_000) // 11 seconds heartbeat timeout\r\n }\r\n\r\n /**\r\n * Add event listener\r\n * @param {StellarBrokerClientEvent} type\r\n * @param {function} callback\r\n */\r\n on(type, callback) {\r\n if (!StellarBrokerEvents.includes(type))\r\n throw errors.unsupportedEventType(type)\r\n this.emitter.addEventListener(type, callback)\r\n }\r\n\r\n /**\r\n * Add event listener that will be executed once\r\n * @param {StellarBrokerClientEvent} type\r\n * @param {function} callback\r\n */\r\n once(type, callback) {\r\n if (!StellarBrokerEvents.includes(type))\r\n throw errors.unsupportedEventType(type)\r\n this.emitter.addEventListener(type, callback, {once: true})\r\n }\r\n\r\n /**\r\n * Remove event listener\r\n * @param {StellarBrokerClientEvent} type\r\n * @param {function} callback\r\n */\r\n off(type, callback) {\r\n if (!StellarBrokerEvents.includes(type))\r\n throw errors.unsupportedEventType(type)\r\n this.emitter.removeEventListener(type, callback)\r\n }\r\n\r\n /**\r\n * Close underlying connection and finalize the client\r\n */\r\n close() {\r\n try {\r\n this.socket.close()\r\n } catch (e) {\r\n }\r\n }\r\n\r\n /**\r\n * Request single swap quote estimate without trading\r\n * @param {SwapQuoteParams} params - Quote parameters\r\n * @return {Promise<SwapQuoteResult>}\r\n */\r\n static estimateSwap(params) {\r\n return estimateSwap(params)\r\n }\r\n}\r\n\r\nexport const StellarBrokerEvents = ['quote', 'finished', 'progress', 'error']\r\n\r\nfunction ensurePromise(callResult) {\r\n if (!callResult instanceof Promise) {\r\n if (callResult)\r\n return Promise.resolve(callResult)\r\n return Promise.reject()\r\n }\r\n return callResult\r\n}\r\n\r\n/**\r\n * @typedef {object} ClientInitializationParams\r\n * @property {string} [network] - Stellar network identifier or passphrase\r\n * @property {string} [partnerKey] - Partner key\r\n * @property {SwapFlowMode} [flow] - Swap flow mode\r\n */\r\n\r\n/**\r\n * @typedef {'disconnected'|'ready'|'quote'|'trade'} ClientStatus\r\n */\r\n\r\n/**\r\n * @typedef {'quote'|'finished'|'progress'|'error'} StellarBrokerClientEvent\r\n */\r\n\r\n/**\r\n * @typedef {'direct'} SwapFlowMode\r\n */\r\n\r\n/**\r\n * @typedef {object} SwapQuoteResult\r\n * @property {string} sellingAsset\r\n * @property {string} buyingAsset\r\n * @property {number} slippageTolerance\r\n * @property {string} destination\r\n * @property {number} ledger\r\n * @property {string} [sellingAmount]\r\n * @property {string} [estimatedBuyingAmount]\r\n * @property {string} [buyingAmount]\r\n * @property {string} [estimatedSellingAmount]\r\n * @property {{selling: string, buying: string, path: string[]}} [directTrade]\r\n */\r\n\r\n/**\r\n * @typedef {function(FeeBumpTransaction):Promise<FeeBumpTransaction>} ClientAuthorizationCallback\r\n */","import StellarBrokerClient from './client.js'\r\n\r\nexport default StellarBrokerClient","import errors, {StellarBrokerError} from './errors.js'\r\nimport {validateQuoteRequest} from './quote-request.js'\r\n\r\n/**\r\n * Estimate price swap without trading\r\n * @param {SwapQuoteParams} params\r\n * @return {Promise<SwapQuoteResult>}\r\n */\r\nexport async function estimateSwap(params) {\r\n const query = Object.entries(validateQuoteRequest(params)).map(([param, value]) => encodeURIComponent(param) + '=' + encodeURIComponent(value))\r\n const url = (params.origin || 'https://api.stellar.broker') + '/quote?' + query.join('&')\r\n try {\r\n const res = await fetch(url).then(r => r.json())\r\n if (res.status !== 'success')\r\n throw errors.quoteError(res.error || 'Quote not available')\r\n return res\r\n } catch (e) {\r\n if (e instanceof StellarBrokerError)\r\n throw e\r\n throw errors.quoteError('Failed to fetch quote')\r\n return null\r\n }\r\n}"],"names":["root","factory","exports","module","require","define","amd","this","__WEBPACK_EXTERNAL_MODULE__755__","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","d","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","StellarBrokerError","Error","constructor","code","message","super","invalidInitParam","param","invalidAuthorizationParam","quoteNotSet","quoteExpired","quoteError","invalidQuoteParam","invalidParamName","details","tradeInProgress","invalidSwapTx","failedToSignTx","unsupportedEventType","type","serverError","buildEvent","data","evt","CustomEvent","detail","validateQuoteRequest","params","destination","sellingAsset","selling_asset","buyingAsset","buying_asset","sellingAmount","selling_amount","buyingAmount","buying_amount","slippageTolerance","slippage_tolerance","other","res","parseAsset","parseAmount","parseSlippageTolerance","flow","errors","assign","freeze","asset","parameter","includes","issuer","split","validateCode","validateAccount","test","account","StrKey","isValidEd25519PublicKey","amount","parsed","parseFloat","isNaN","src","tolerance","StellarBrokerEvents","ensurePromise","callResult","Promise","resolve","reject","partnerKey","emitter","EventTarget","network","Networks","toUpperCase","status","socket","origin","quoteRequest","lastQuote","tradeQuote","authorization","source","connect","readyState","WebSocket","OPEN","encodeURIComponent","onmessage","processMessage","bind","onclose","console","log","onerror","e","error","confirm","expirationTimeout","setTimeout","onSocketOpen","heartbeat","clearTimeout","raw","JSON","parse","quote","ts","Date","dispatchEvent","processTxRequest","sold","bought","send","stop","then","confirmQuote","Keypair","fromSecret","tx","TransactionBuilder","fromXDR","xdr","validateTransaction","authorizeTx","networkFee","signatures","length","hash","toXDR","catch","sign","wrapped","buildFeeBumpTransaction","swap","operations","stringify","pingHandler","warn","close","on","callback","addEventListener","once","off","removeEventListener","estimateSwap","async","query","entries","map","value","url","join","fetch","r","json"],"sourceRoot":""}
|
|
1
|
+
{"version":3,"file":"stellarbroker.js","mappings":"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","sources":["webpack://stellarbroker/webpack/universalModuleDefinition","webpack://stellarbroker/external umd \"@stellar/stellar-sdk\"","webpack://stellarbroker/webpack/bootstrap","webpack://stellarbroker/webpack/runtime/define property getters","webpack://stellarbroker/webpack/runtime/hasOwnProperty shorthand","webpack://stellarbroker/./src/errors.js","webpack://stellarbroker/./src/events.js","webpack://stellarbroker/./src/asset.js","webpack://stellarbroker/./src/quote-request.js","webpack://stellarbroker/./src/tx-processor.js","webpack://stellarbroker/./src/client.js","webpack://stellarbroker/./src/cjs.js","webpack://stellarbroker/./src/estimate.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"@stellar/stellar-sdk\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"@stellar/stellar-sdk\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"stellarbroker\"] = factory(require(\"@stellar/stellar-sdk\"));\n\telse\n\t\troot[\"stellarbroker\"] = factory(root[\"@stellar/stellar-sdk\"]);\n})(this, (__WEBPACK_EXTERNAL_MODULE__346__) => {\nreturn ","module.exports = __WEBPACK_EXTERNAL_MODULE__346__;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","export class StellarBrokerError extends Error {\r\n constructor(code, message) {\r\n super(message)\r\n this.code = code\r\n }\r\n\r\n /**\r\n * Numeric error code\r\n * @type {number}\r\n * @readonly\r\n */\r\n code = 0\r\n}\r\n\r\nconst errors = {\r\n invalidInitParam(param) {\r\n return new StellarBrokerError(1, `Invalid parameter value: \"${param}\"`)\r\n },\r\n invalidAuthorizationParam() {\r\n return new StellarBrokerError(2, 'Invalid authorization secret key or callback provided')\r\n },\r\n notConnected() {\r\n return new StellarBrokerError(3, 'Client not connected to the server')\r\n },\r\n quoteNotSet() {\r\n return new StellarBrokerError(11, 'Price quote not available')\r\n },\r\n quoteExpired() {\r\n return new StellarBrokerError(12, 'Price quote expired')\r\n },\r\n quoteError(message) {\r\n return new StellarBrokerError(13, 'Price quotation error: ' + message)\r\n },\r\n invalidQuoteParam(invalidParamName = 'asset', details) {\r\n return new StellarBrokerError(14, `Invalid quote request parameter: \"${invalidParamName}\". ${details}`)\r\n },\r\n tradeInProgress() {\r\n return new StellarBrokerError(20, 'Cannot change quote while trade is in progress')\r\n },\r\n invalidSwapTx() {\r\n return new StellarBrokerError(21, 'Invalid swap transaction received from the server')\r\n },\r\n failedToSignTx() {\r\n return new StellarBrokerError(22, 'Failed to sign received transaction')\r\n },\r\n unsupportedEventType(type) {\r\n return new StellarBrokerError(31, 'Unknown event type: ' + type)\r\n },\r\n serverError(message) {\r\n return new StellarBrokerError(101, message)\r\n }\r\n}\r\n\r\nexport default errors","/**\r\n * @param {string} type\r\n * @param {{}} data\r\n * @param {string} [key]\r\n * @return {CustomEvent}\r\n */\r\nexport function buildEvent(type, data, key) {\r\n const evt = new CustomEvent(type, {detail: data})\r\n evt[key || type] = data\r\n return evt\r\n}","import {Asset, StrKey} from '@stellar/stellar-sdk'\r\nimport errors from './errors.js'\r\n\r\n/**\r\n * @param {string} asset\r\n * @return {Asset}\r\n */\r\nexport function convertToStellarAsset(asset) {\r\n if (asset === 'xlm' || asset === 'XLM')\r\n return Asset.native()\r\n const [code, issuer] = asset.includes('-') ?\r\n asset.split('-') :\r\n asset.split(':')\r\n return new Asset(code, issuer)\r\n}\r\n\r\nexport function parseAsset(asset, parameter) {\r\n if (typeof asset === 'string') {\r\n if (asset === 'XLM' || asset === 'xlm' || asset === 'native')\r\n return 'XLM'\r\n if (asset.includes(':')) {\r\n const [code, issuer] = asset.split(':')\r\n validateCode(code)\r\n validateAccount(issuer, parameter)\r\n return code + '-' + issuer\r\n }\r\n if (asset.includes('-')) {\r\n const [code, issuer] = asset.split('-')\r\n validateCode(code)\r\n validateAccount(issuer, parameter)\r\n return asset\r\n }\r\n }\r\n throw errors.invalidQuoteParam(parameter, 'Invalid asset')\r\n}\r\n\r\nfunction validateCode(code, parameter) {\r\n if (!/^[a-zA-Z0-9]{1,12}$/.test(code))\r\n throw errors.invalidQuoteParam(parameter, 'Invalid asset code: ' + (!code ? 'missing' : code))\r\n return code\r\n}\r\n\r\nfunction validateAccount(account, param) {\r\n if (!account || !StrKey.isValidEd25519PublicKey(account))\r\n throw errors.invalidQuoteParam(param, 'Invalid account address: ' + (!account ? 'missing' : account))\r\n return account\r\n}\r\n","import {StrKey} from '@stellar/stellar-sdk'\r\nimport errors from './errors.js'\r\nimport {parseAsset} from './asset.js'\r\n\r\n/**\r\n * @typedef {object} SwapQuoteParams\r\n * @property {string} sellingAsset - Asset to sell\r\n * @property {string} buyingAsset - Asset to buy\r\n * @property {string} [sellingAmount] - Amount of selling asset\r\n * @property {string} [buyingAmount] - Amount of buying asset\r\n * @property {number} [slippageTolerance] - Swap slippage tolerance (0.02 by default - 2%)\r\n * @property {string} [destination] - Trader account address\r\n */\r\n\r\n/**\r\n * @param {SwapQuoteParams} params\r\n * @return {SwapQuoteParams}\r\n */\r\nexport function validateQuoteRequest(params) {\r\n const {\r\n destination, sellingAsset, selling_asset, buyingAsset, buying_asset, sellingAmount, selling_amount,\r\n buyingAmount, buying_amount, slippageTolerance, slippage_tolerance, ...other\r\n } = params\r\n const res = {\r\n sellingAsset: parseAsset(sellingAsset || selling_asset, 'sellingAsset'),\r\n buyingAsset: parseAsset(buyingAsset || buying_asset, 'buyingAsset'),\r\n sellingAmount: parseAmount(sellingAmount || selling_amount, 'sellingAmount'),\r\n buyingAmount: parseAmount(buyingAmount || buying_amount, 'buyingAmount'),\r\n slippageTolerance: parseSlippageTolerance(slippageTolerance || slippage_tolerance || 0.02, 'slippageTolerance'),\r\n flow: 'direct'\r\n }\r\n if (res.buyingAsset === res.sellingAsset)\r\n throw errors.invalidQuoteParam('buyingAsset', 'Buying asset can\\'t be the same as selling asset')\r\n if (res.buyingAmount === undefined && res.sellingAmount === undefined)\r\n throw errors.invalidQuoteParam('sellingAmount', 'Either \"buyingAmount\" or \"sellingAmount\" parameter is required')\r\n if (res.buyingAmount !== undefined && res.sellingAmount !== undefined)\r\n throw errors.invalidQuoteParam('sellingAmount', 'Parameters \"buyingAmount\" and \"sellingAmount\" are mutually exclusive')\r\n Object.assign(res, other) //add remaining optional params\r\n Object.freeze(res)\r\n return res\r\n}\r\n\r\nfunction parseAmount(amount, parameter) {\r\n if (amount === undefined)\r\n return undefined\r\n const parsed = parseFloat(amount)\r\n if (isNaN(parsed) || parsed <= 0)\r\n throw errors.invalidQuoteParam(parameter, 'Invalid asset amount: ' + amount)\r\n return amount\r\n}\r\n\r\nfunction parseSlippageTolerance(src, parameter) {\r\n const tolerance = parseFloat(src)\r\n if (isNaN(tolerance))\r\n throw errors.invalidQuoteParam(parameter, 'Invalid slippage tolerance, number expected')\r\n if (tolerance < 0)\r\n throw errors.invalidQuoteParam(parameter, 'Slippage tolerance is too small, expected value >= 0')\r\n if (tolerance > 0.5)\r\n throw errors.invalidQuoteParam(parameter, 'Slippage tolerance is too large, expected value < 0.5 (50%)')\r\n return tolerance\r\n}","import {hash, Keypair, nativeToScVal, StrKey, TransactionBuilder, xdr} from '@stellar/stellar-sdk'\r\nimport errors from './errors.js'\r\n\r\n/**\r\n * @param {StellarBrokerClient} client\r\n * @param {{xdr: string, hash: string, networkFee: string, [confirmed]: boolean}} txRequest\r\n * @return {Promise<string>}\r\n */\r\nexport async function processTxRequest(client, txRequest) {\r\n //parse incoming transaction\r\n let tx\r\n try {\r\n tx = TransactionBuilder.fromXDR(txRequest.xdr, client.network)\r\n } catch (e) {\r\n throw errors.invalidSwapTx()\r\n }\r\n //check that transaction is correct\r\n if (!validateTransaction(client, tx))\r\n throw errors.invalidSwapTx()\r\n //if transaction has not been authorized yet by the channel account\r\n const isSorobanTx = tx.operations[0].auth?.length > 0\r\n if (isSorobanTx) {\r\n //send tx back to the server\r\n if (!tx.signatures.length) {\r\n //sign auth\r\n await authorizeInvocation(client, tx)\r\n tx = await signTx(client, tx)\r\n return tx.toXDR()//wait for the signed tx from the server to wrap it with fee bump tx\r\n }\r\n } else {\r\n //sign transaction\r\n tx = await signTx(client, tx)\r\n }\r\n //wrap with fee bump\r\n let wrapped = TransactionBuilder.buildFeeBumpTransaction(client.trader, txRequest.networkFee, tx, client.network)\r\n //sign fee bump wrapper tx\r\n wrapped = await signTx(client, wrapped)\r\n //respond with signed transaction\r\n return wrapped.toXDR()\r\n}\r\n\r\n/**\r\n * @param {StellarBrokerClient} client\r\n * @param {TransactionI} tx\r\n * @return {Promise<TransactionI>}\r\n * @private\r\n */\r\nasync function authorizeInvocation(client, tx) {\r\n //from https://github.com/stellar/js-stellar-base/blob/e3d6fc3351e7d242b374c7c6057668366364a279/src/auth.js#L97\r\n const auth = tx.operations[0].auth[0]\r\n /** @type {xdr.SorobanAddressCredentials} */\r\n const addrAuth = auth.credentials().address()\r\n addrAuth.signatureExpirationLedger(tx.ledgerBounds.maxLedger + 1)\r\n\r\n const networkId = hash(Buffer.from(tx.networkPassphrase))\r\n const preimage = xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(\r\n new xdr.HashIdPreimageSorobanAuthorization({\r\n networkId,\r\n nonce: addrAuth.nonce(),\r\n invocation: auth.rootInvocation(),\r\n signatureExpirationLedger: addrAuth.signatureExpirationLedger()\r\n })\r\n )\r\n const payload = hash(preimage.toXDR())\r\n let signature\r\n if (client.authorization instanceof Keypair) {\r\n signature = client.authorization.sign(payload)\r\n } else {\r\n signature = await promisify(client.authorization(payload))\r\n }\r\n const publicKey = StrKey.encodeEd25519PublicKey(addrAuth.address().accountId().value())\r\n const sigScVal = nativeToScVal(\r\n {\r\n public_key: StrKey.decodeEd25519PublicKey(publicKey),\r\n signature\r\n },\r\n {\r\n type: {\r\n public_key: ['symbol', null],\r\n signature: ['symbol', null]\r\n }\r\n }\r\n )\r\n\r\n addrAuth.signature(xdr.ScVal.scvVec([sigScVal]))\r\n}\r\n\r\n/**\r\n * @param {StellarBrokerClient} client\r\n * @param {TransactionI} tx\r\n * @return {Promise<TransactionI>}\r\n * @private\r\n */\r\nasync function signTx(client, tx) {\r\n let promise\r\n if (client.authorization instanceof Keypair) {\r\n promise = new Promise((resolve, reject) => {\r\n try {\r\n tx.sign(client.authorization)\r\n resolve(tx)\r\n } catch (e) {\r\n reject(e)\r\n }\r\n })\r\n } else {\r\n //normalize external auth result\r\n promise = promisify(client.authorization(tx))\r\n }\r\n return promise\r\n .then(tx => {\r\n if (typeof tx === 'string') { //normalize response\r\n tx = TransactionBuilder.fromXDR(tx, client.network)\r\n }\r\n if (!tx || tx.signatures?.length < 1)\r\n throw new Error(`Transaction was not signed`)\r\n return tx\r\n })\r\n .catch(e => {\r\n console.error(e)\r\n throw errors.failedToSignTx()\r\n })\r\n}\r\n\r\n/**\r\n * @param {StellarBrokerClient} client\r\n * @param {Transaction} tx\r\n * @throws {StellarBrokerError} Invalid swap transaction received\r\n */\r\nfunction validateTransaction(client, tx) {\r\n /*if (!(tx instanceof FeeBumpTransaction))\r\n return false\r\n const {innerTransaction} = tx\r\n if (tx.feeSource !== this.source)\r\n return false*/\r\n for (let swap of tx.operations) {\r\n if (swap.type === 'invokeHostFunction')\r\n continue //TODO: add additional validation\r\n if (swap.type !== 'pathPaymentStrictSend' && swap.type !== 'pathPaymentStrictReceive')\r\n return false\r\n const isFee = swap.destination !== client.trader\r\n if (isFee) {\r\n if (swap.type !== 'pathPaymentStrictSend')\r\n return false\r\n if ((swap.source && swap.source !== client.trader))\r\n return false\r\n } else {\r\n if ((swap.source && swap.source !== swap.destination) || swap.destination !== client.trader)\r\n return false\r\n }\r\n }\r\n //TODO: check assets and amounts\r\n return true\r\n}\r\n\r\nfunction promisify(callResult) {\r\n if (!(callResult instanceof Promise)) {\r\n if (!callResult)\r\n return Promise.reject()\r\n callResult = Promise.resolve(callResult)\r\n }\r\n return callResult\r\n}","import {Networks, Keypair, TransactionI, StrKey} from '@stellar/stellar-sdk'\r\nimport errors from './errors.js'\r\nimport {buildEvent} from './events.js'\r\nimport {validateQuoteRequest} from './quote-request.js'\r\nimport {processTxRequest} from './tx-processor.js'\r\n\r\nexport default class StellarBrokerClient {\r\n /**\r\n * @param {ClientInitializationParams} params\r\n */\r\n constructor(params) {\r\n this.partnerKey = params.partnerKey\r\n this.emitter = new EventTarget()\r\n this.network = Networks[(params.network || 'PUBLIC').toUpperCase()] || params.network\r\n this.flow = params.flow || 'direct'\r\n if (!params.account || !StrKey.isValidEd25519PublicKey(params.account))\r\n throw errors.invalidQuoteParam('account', 'Invalid trader account address: ' + (!params.account ? 'missing' : params.account))\r\n this.trader = params.account\r\n if (typeof params.authorization === 'string') {\r\n try {\r\n this.authorization = Keypair.fromSecret(params.authorization)\r\n } catch (e) {\r\n throw errors.invalidAuthorizationParam()\r\n }\r\n } else if (typeof params.authorization === 'function') {\r\n this.authorization = params.authorization\r\n }\r\n }\r\n\r\n /**\r\n * @type {string}\r\n * @private\r\n */\r\n uid\r\n /**\r\n * @type {ClientStatus}\r\n * @readonly\r\n */\r\n status = 'disconnected'\r\n /**\r\n * @type {WebSocket}\r\n * @private\r\n */\r\n socket\r\n /**\r\n * @type {EventTarget}\r\n * @private\r\n */\r\n emitter\r\n /**\r\n * @type {string}\r\n * @private\r\n */\r\n origin = 'https://api.stellar.broker'\r\n /**\r\n * Stellar network passphrase\r\n * @type {string}\r\n * @readonly\r\n */\r\n network = ''\r\n /**\r\n * Last quote request submitted by the client\r\n * @type {SwapQuoteParams}\r\n * @readonly\r\n */\r\n quoteRequest\r\n /**\r\n * Last quote received from the sever\r\n * @type {SwapQuoteResult}\r\n * @readonly\r\n */\r\n lastQuote\r\n /**\r\n * Current quote accepted for the trading\r\n * @private\r\n */\r\n tradeQuote\r\n /**\r\n * API key of the partner\r\n * @type {string}\r\n * @readonly\r\n */\r\n partnerKey\r\n /**\r\n * Account secret key (for direct swap flow)\r\n * @type {Keypair|ClientAuthorizationCallback}\r\n * @private\r\n */\r\n authorization\r\n /**\r\n * Trader account public key\r\n * @type {string}\r\n * @readonly\r\n */\r\n trader\r\n\r\n /**\r\n * Connect to the StellarBroker server\r\n * @return {Promise<StellarBrokerClient>}\r\n */\r\n connect() {\r\n if (this.socket?.readyState === WebSocket.OPEN)\r\n return Promise.resolve(this) //already opened\r\n this.socket = new WebSocket(this.origin + '/ws?partner=' + encodeURIComponent(this.partnerKey))\r\n this.socket.onmessage = this.processMessage.bind(this)\r\n this.socket.onclose = () => {\r\n if (this.status !== 'disconnected') {\r\n this.status = 'disconnected'\r\n this.notifyError(errors.notConnected())\r\n }\r\n console.log('Connection closed')\r\n }\r\n\r\n this.socket.onerror = e => console.error(e)\r\n\r\n return new Promise((confirm, reject) => { //TODO: use 'once'\r\n const expirationTimeout = setTimeout(() => reject(this), 2000) // 2s timeout\r\n this.onSocketOpen = () => {\r\n this.status = 'ready'\r\n this.heartbeat()\r\n this.onSocketOpen = undefined\r\n clearTimeout(expirationTimeout)\r\n confirm(this)\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Process incoming message\r\n * @param message\r\n * @private\r\n */\r\n processMessage(message) {\r\n const raw = JSON.parse(message.data)\r\n switch (raw.type) {\r\n case 'connected':\r\n this.uid = raw.uid\r\n if (this.onSocketOpen) {\r\n this.onSocketOpen()\r\n }\r\n if (this.status === 'disconnected') {\r\n this.status = 'ready'\r\n }\r\n break\r\n case 'quote':\r\n this.lastQuote = raw.quote\r\n this.lastQuote.ts = new Date()\r\n //send event to the client app\r\n this.emitter.dispatchEvent(buildEvent('quote', this.lastQuote))\r\n break\r\n case 'paused':\r\n //quotation paused due to inactivity\r\n this.emitter.dispatchEvent(buildEvent('paused', {}))\r\n break\r\n case 'tx':\r\n if (this.status !== 'trade') {\r\n console.log('Received tx in non-trading state', this.status, raw)\r\n return //skip unless trading is in progress\r\n }\r\n processTxRequest(this, raw)\r\n .then(xdr => {\r\n this.send({\r\n type: 'tx',\r\n hash: raw.hash,\r\n xdr\r\n })\r\n })\r\n .catch(e => this.notifyError(e))\r\n break\r\n case 'stop':\r\n this.emitter.dispatchEvent(buildEvent('finished', {\r\n status: raw.status,\r\n sold: raw.sold,\r\n bought: raw.bought\r\n }, 'result'))\r\n this.status = 'ready'\r\n this.tradeQuote = undefined\r\n break\r\n case 'progress':\r\n this.emitter.dispatchEvent(buildEvent('progress', {\r\n sold: raw.sold,\r\n bought: raw.bought\r\n }, 'status'))\r\n break\r\n case 'ping':\r\n if (raw.uid === this.uid) {\r\n this.heartbeat()\r\n this.send({type: 'pong', uid: this.uid})\r\n }\r\n break\r\n case 'error':\r\n this.stop()\r\n this.notifyError(raw.error)\r\n break\r\n default:\r\n console.log('Unknown message type: ' + raw.type)\r\n break\r\n }\r\n }\r\n\r\n /**\r\n * Request swap quote\r\n * @param {SwapQuoteParams} params - Quote parameters\r\n */\r\n quote(params) {\r\n if (this.status === 'trade')\r\n throw errors.tradeInProgress()\r\n this.quoteRequest = validateQuoteRequest(params)\r\n this.status = 'quote'\r\n this.connect()\r\n .then(() => {\r\n this.status = 'quote'\r\n this.send({\r\n type: 'quote',\r\n ...this.quoteRequest\r\n })\r\n })\r\n }\r\n\r\n confirmQuote() {\r\n if (this.status === 'disconnected')\r\n throw errors.notConnected()\r\n if (this.status === 'trade')\r\n throw errors.tradeInProgress()\r\n if (this.status !== 'quote' || !this.lastQuote)\r\n throw errors.quoteNotSet()\r\n if ((new Date() - this.lastQuote.ts) > 7000) //do not allow stale quotes quoted more than 7s ago\r\n throw errors.quoteExpired()\r\n if (this.lastQuote.status !== 'success')\r\n throw errors.quoteError(this.lastQuote.error || 'quote not available')\r\n\r\n this.tradeQuote = this.lastQuote\r\n this.send({\r\n type: 'trade',\r\n account: this.trader\r\n })\r\n this.status = 'trade'\r\n }\r\n\r\n /**\r\n * Stop quotation/trading\r\n */\r\n stop() {\r\n if (this.status !== 'trade' && this.status !== 'quote')\r\n return\r\n this.send({type: 'stop'})\r\n this.tradeQuote = undefined\r\n this.status = 'ready'\r\n }\r\n\r\n /**\r\n * @param {{}} data\r\n * @private\r\n */\r\n send(data) {\r\n this.socket.send(JSON.stringify(data))\r\n }\r\n\r\n /**\r\n * @private\r\n */\r\n heartbeat() {\r\n clearTimeout(this.pingHandler)\r\n this.pingHandler = setTimeout(() => {\r\n console.warn('Lost connection with the server')\r\n this.socket.close()\r\n }, 7_000) // 7 seconds heartbeat timeout\r\n }\r\n\r\n /**\r\n * Add event listener\r\n * @param {StellarBrokerClientEvent} type\r\n * @param {function} callback\r\n */\r\n on(type, callback) {\r\n validateEventType(type)\r\n this.emitter.addEventListener(type, callback)\r\n }\r\n\r\n /**\r\n * Add event listener that will be executed once\r\n * @param {StellarBrokerClientEvent} type\r\n * @param {function} callback\r\n */\r\n once(type, callback) {\r\n validateEventType(type)\r\n this.emitter.addEventListener(type, callback, {once: true})\r\n }\r\n\r\n /**\r\n * Remove event listener\r\n * @param {StellarBrokerClientEvent} type\r\n * @param {function} callback\r\n */\r\n off(type, callback) {\r\n validateEventType(type)\r\n this.emitter.removeEventListener(type, callback)\r\n }\r\n\r\n /**\r\n * Close underlying connection and finalize the client\r\n */\r\n close() {\r\n try {\r\n this.status = 'disconnected'\r\n this.socket.close()\r\n } catch (e) {\r\n }\r\n }\r\n\r\n /**\r\n * @param {Error} e\r\n * @private\r\n */\r\n notifyError(e) {\r\n console.error(e)\r\n try {\r\n this.emitter.dispatchEvent(buildEvent('error', e instanceof Error ? e.message : e))\r\n } catch (e) {\r\n console.error(e)\r\n }\r\n }\r\n}\r\n\r\nfunction validateEventType(type) {\r\n if (!StellarBrokerEvents.includes(type))\r\n throw errors.unsupportedEventType(type)\r\n}\r\n\r\nexport const StellarBrokerEvents = ['quote', 'paused', 'progress', 'finished', 'error']\r\n\r\n/**\r\n * @typedef {object} ClientInitializationParams\r\n * @property {string} [network] - Stellar network identifier or passphrase\r\n * @property {string} [partnerKey] - Partner key\r\n * @property {SwapFlowMode} [flow] - Swap flow mode\r\n * @property {string} account - Trader account address\r\n * @property {string|ClientAuthorizationCallback} authorization - Authorization method, either account secret key or an authorization callback\r\n */\r\n\r\n/**\r\n * @typedef {'disconnected'|'ready'|'quote'|'trade'} ClientStatus\r\n */\r\n\r\n/**\r\n * @typedef {'quote'|'paused'|'progress'|'finished'|'error'} StellarBrokerClientEvent\r\n */\r\n\r\n/**\r\n * @typedef {'direct'} SwapFlowMode\r\n */\r\n\r\n/**\r\n * @typedef {object} SwapQuoteResult\r\n * @property {string} sellingAsset\r\n * @property {string} buyingAsset\r\n * @property {number} slippageTolerance\r\n * @property {string} destination\r\n * @property {number} ledger\r\n * @property {string} [sellingAmount]\r\n * @property {string} [estimatedBuyingAmount]\r\n * @property {string} [buyingAmount]\r\n * @property {string} [estimatedSellingAmount]\r\n * @property {{selling: string, buying: string, path: string[]}} [directTrade]\r\n */\r\n\r\n/**\r\n * @typedef {function(TransactionI|Buffer):Promise<TransactionI|Buffer>} ClientAuthorizationCallback\r\n */","import StellarBrokerClient from './client.js'\r\nimport {estimateSwap} from './estimate.js'\r\n\r\nStellarBrokerClient.estimateSwap = estimateSwap\r\n\r\nexport default StellarBrokerClient","import errors, {StellarBrokerError} from './errors.js'\r\nimport {validateQuoteRequest} from './quote-request.js'\r\n\r\n/**\r\n * Request single swap quote estimate without trading\r\n * @param {SwapQuoteParams} params\r\n * @return {Promise<SwapQuoteResult>}\r\n */\r\nexport async function estimateSwap(params) {\r\n const query = Object.entries(validateQuoteRequest(params))\r\n .filter(kv => kv[1] !== undefined)\r\n .map(([param, value]) => encodeURIComponent(param) + '=' + encodeURIComponent(value))\r\n const url = (params.origin || 'https://api.stellar.broker') + '/quote?' + query.join('&')\r\n try {\r\n const res = await fetch(url).then(r => r.json())\r\n if (res.status !== 'success')\r\n throw errors.quoteError(res.error || 'Quote not available')\r\n return res\r\n } catch (e) {\r\n if (e instanceof StellarBrokerError)\r\n throw e\r\n throw errors.quoteError('Failed to fetch quote')\r\n return null\r\n }\r\n}"],"names":["root","factory","exports","module","require","define","amd","this","__WEBPACK_EXTERNAL_MODULE__346__","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","d","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","StellarBrokerError","Error","constructor","code","message","super","invalidInitParam","param","invalidAuthorizationParam","notConnected","quoteNotSet","quoteExpired","quoteError","invalidQuoteParam","invalidParamName","details","tradeInProgress","invalidSwapTx","failedToSignTx","unsupportedEventType","type","serverError","buildEvent","data","evt","CustomEvent","detail","parseAsset","asset","parameter","includes","issuer","split","validateCode","validateAccount","errors","test","account","StrKey","isValidEd25519PublicKey","validateQuoteRequest","params","destination","sellingAsset","selling_asset","buyingAsset","buying_asset","sellingAmount","selling_amount","buyingAmount","buying_amount","slippageTolerance","slippage_tolerance","other","res","parseAmount","parseSlippageTolerance","flow","assign","freeze","amount","parsed","parseFloat","isNaN","src","tolerance","async","signTx","client","tx","promise","authorization","Keypair","Promise","resolve","reject","sign","e","promisify","then","TransactionBuilder","fromXDR","network","signatures","length","catch","console","error","callResult","StellarBrokerClient","partnerKey","emitter","EventTarget","Networks","toUpperCase","trader","fromSecret","uid","status","socket","origin","quoteRequest","lastQuote","tradeQuote","connect","readyState","WebSocket","OPEN","encodeURIComponent","onmessage","processMessage","bind","onclose","notifyError","log","onerror","confirm","expirationTimeout","setTimeout","onSocketOpen","heartbeat","clearTimeout","raw","JSON","parse","quote","ts","Date","dispatchEvent","txRequest","xdr","swap","operations","source","validateTransaction","auth","addrAuth","credentials","address","signatureExpirationLedger","ledgerBounds","maxLedger","networkId","hash","Buffer","from","networkPassphrase","preimage","HashIdPreimage","envelopeTypeSorobanAuthorization","HashIdPreimageSorobanAuthorization","nonce","invocation","rootInvocation","payload","toXDR","signature","publicKey","encodeEd25519PublicKey","accountId","value","sigScVal","nativeToScVal","public_key","decodeEd25519PublicKey","ScVal","scvVec","authorizeInvocation","wrapped","buildFeeBumpTransaction","networkFee","processTxRequest","send","sold","bought","stop","confirmQuote","stringify","pingHandler","warn","close","on","callback","validateEventType","addEventListener","once","off","removeEventListener","StellarBrokerEvents","estimateSwap","query","entries","filter","kv","map","url","join","fetch","r","json"],"sourceRoot":""}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stellar-broker/client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Browser client library for StellarBroker",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Browser client library for StellarBroker service",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"module": "src/index.js",
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "https://github.com/stellar-broker/client"
|
|
16
16
|
},
|
|
17
|
-
"author": "",
|
|
17
|
+
"author": "StellarBroker",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@stellar/stellar-
|
|
20
|
+
"@stellar/stellar-sdk": ">=12"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@babel/core": "^7.24.8",
|
package/src/asset.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {Asset, StrKey} from '@stellar/stellar-sdk'
|
|
2
|
+
import errors from './errors.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} asset
|
|
6
|
+
* @return {Asset}
|
|
7
|
+
*/
|
|
8
|
+
export function convertToStellarAsset(asset) {
|
|
9
|
+
if (asset === 'xlm' || asset === 'XLM')
|
|
10
|
+
return Asset.native()
|
|
11
|
+
const [code, issuer] = asset.includes('-') ?
|
|
12
|
+
asset.split('-') :
|
|
13
|
+
asset.split(':')
|
|
14
|
+
return new Asset(code, issuer)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parseAsset(asset, parameter) {
|
|
18
|
+
if (typeof asset === 'string') {
|
|
19
|
+
if (asset === 'XLM' || asset === 'xlm' || asset === 'native')
|
|
20
|
+
return 'XLM'
|
|
21
|
+
if (asset.includes(':')) {
|
|
22
|
+
const [code, issuer] = asset.split(':')
|
|
23
|
+
validateCode(code)
|
|
24
|
+
validateAccount(issuer, parameter)
|
|
25
|
+
return code + '-' + issuer
|
|
26
|
+
}
|
|
27
|
+
if (asset.includes('-')) {
|
|
28
|
+
const [code, issuer] = asset.split('-')
|
|
29
|
+
validateCode(code)
|
|
30
|
+
validateAccount(issuer, parameter)
|
|
31
|
+
return asset
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw errors.invalidQuoteParam(parameter, 'Invalid asset')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateCode(code, parameter) {
|
|
38
|
+
if (!/^[a-zA-Z0-9]{1,12}$/.test(code))
|
|
39
|
+
throw errors.invalidQuoteParam(parameter, 'Invalid asset code: ' + (!code ? 'missing' : code))
|
|
40
|
+
return code
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateAccount(account, param) {
|
|
44
|
+
if (!account || !StrKey.isValidEd25519PublicKey(account))
|
|
45
|
+
throw errors.invalidQuoteParam(param, 'Invalid account address: ' + (!account ? 'missing' : account))
|
|
46
|
+
return account
|
|
47
|
+
}
|
package/src/client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {Networks,
|
|
1
|
+
import {Networks, Keypair, TransactionI, StrKey} from '@stellar/stellar-sdk'
|
|
2
2
|
import errors from './errors.js'
|
|
3
3
|
import {buildEvent} from './events.js'
|
|
4
4
|
import {validateQuoteRequest} from './quote-request.js'
|
|
5
|
-
import {
|
|
5
|
+
import {processTxRequest} from './tx-processor.js'
|
|
6
6
|
|
|
7
7
|
export default class StellarBrokerClient {
|
|
8
8
|
/**
|
|
@@ -13,8 +13,25 @@ export default class StellarBrokerClient {
|
|
|
13
13
|
this.emitter = new EventTarget()
|
|
14
14
|
this.network = Networks[(params.network || 'PUBLIC').toUpperCase()] || params.network
|
|
15
15
|
this.flow = params.flow || 'direct'
|
|
16
|
+
if (!params.account || !StrKey.isValidEd25519PublicKey(params.account))
|
|
17
|
+
throw errors.invalidQuoteParam('account', 'Invalid trader account address: ' + (!params.account ? 'missing' : params.account))
|
|
18
|
+
this.trader = params.account
|
|
19
|
+
if (typeof params.authorization === 'string') {
|
|
20
|
+
try {
|
|
21
|
+
this.authorization = Keypair.fromSecret(params.authorization)
|
|
22
|
+
} catch (e) {
|
|
23
|
+
throw errors.invalidAuthorizationParam()
|
|
24
|
+
}
|
|
25
|
+
} else if (typeof params.authorization === 'function') {
|
|
26
|
+
this.authorization = params.authorization
|
|
27
|
+
}
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @type {string}
|
|
32
|
+
* @private
|
|
33
|
+
*/
|
|
34
|
+
uid
|
|
18
35
|
/**
|
|
19
36
|
* @type {ClientStatus}
|
|
20
37
|
* @readonly
|
|
@@ -70,13 +87,12 @@ export default class StellarBrokerClient {
|
|
|
70
87
|
* @private
|
|
71
88
|
*/
|
|
72
89
|
authorization
|
|
73
|
-
|
|
74
90
|
/**
|
|
75
91
|
* Trader account public key
|
|
76
92
|
* @type {string}
|
|
77
93
|
* @readonly
|
|
78
94
|
*/
|
|
79
|
-
|
|
95
|
+
trader
|
|
80
96
|
|
|
81
97
|
/**
|
|
82
98
|
* Connect to the StellarBroker server
|
|
@@ -85,12 +101,14 @@ export default class StellarBrokerClient {
|
|
|
85
101
|
connect() {
|
|
86
102
|
if (this.socket?.readyState === WebSocket.OPEN)
|
|
87
103
|
return Promise.resolve(this) //already opened
|
|
88
|
-
|
|
89
104
|
this.socket = new WebSocket(this.origin + '/ws?partner=' + encodeURIComponent(this.partnerKey))
|
|
90
105
|
this.socket.onmessage = this.processMessage.bind(this)
|
|
91
106
|
this.socket.onclose = () => {
|
|
107
|
+
if (this.status !== 'disconnected') {
|
|
108
|
+
this.status = 'disconnected'
|
|
109
|
+
this.notifyError(errors.notConnected())
|
|
110
|
+
}
|
|
92
111
|
console.log('Connection closed')
|
|
93
|
-
this.status = 'disconnected'
|
|
94
112
|
}
|
|
95
113
|
|
|
96
114
|
this.socket.onerror = e => console.error(e)
|
|
@@ -116,9 +134,13 @@ export default class StellarBrokerClient {
|
|
|
116
134
|
const raw = JSON.parse(message.data)
|
|
117
135
|
switch (raw.type) {
|
|
118
136
|
case 'connected':
|
|
137
|
+
this.uid = raw.uid
|
|
119
138
|
if (this.onSocketOpen) {
|
|
120
139
|
this.onSocketOpen()
|
|
121
140
|
}
|
|
141
|
+
if (this.status === 'disconnected') {
|
|
142
|
+
this.status = 'ready'
|
|
143
|
+
}
|
|
122
144
|
break
|
|
123
145
|
case 'quote':
|
|
124
146
|
this.lastQuote = raw.quote
|
|
@@ -126,12 +148,24 @@ export default class StellarBrokerClient {
|
|
|
126
148
|
//send event to the client app
|
|
127
149
|
this.emitter.dispatchEvent(buildEvent('quote', this.lastQuote))
|
|
128
150
|
break
|
|
151
|
+
case 'paused':
|
|
152
|
+
//quotation paused due to inactivity
|
|
153
|
+
this.emitter.dispatchEvent(buildEvent('paused', {}))
|
|
154
|
+
break
|
|
129
155
|
case 'tx':
|
|
130
156
|
if (this.status !== 'trade') {
|
|
131
157
|
console.log('Received tx in non-trading state', this.status, raw)
|
|
132
158
|
return //skip unless trading is in progress
|
|
133
159
|
}
|
|
134
|
-
|
|
160
|
+
processTxRequest(this, raw)
|
|
161
|
+
.then(xdr => {
|
|
162
|
+
this.send({
|
|
163
|
+
type: 'tx',
|
|
164
|
+
hash: raw.hash,
|
|
165
|
+
xdr
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
.catch(e => this.notifyError(e))
|
|
135
169
|
break
|
|
136
170
|
case 'stop':
|
|
137
171
|
this.emitter.dispatchEvent(buildEvent('finished', {
|
|
@@ -141,7 +175,6 @@ export default class StellarBrokerClient {
|
|
|
141
175
|
}, 'result'))
|
|
142
176
|
this.status = 'ready'
|
|
143
177
|
this.tradeQuote = undefined
|
|
144
|
-
this.source = undefined
|
|
145
178
|
break
|
|
146
179
|
case 'progress':
|
|
147
180
|
this.emitter.dispatchEvent(buildEvent('progress', {
|
|
@@ -150,12 +183,14 @@ export default class StellarBrokerClient {
|
|
|
150
183
|
}, 'status'))
|
|
151
184
|
break
|
|
152
185
|
case 'ping':
|
|
153
|
-
this.
|
|
154
|
-
|
|
186
|
+
if (raw.uid === this.uid) {
|
|
187
|
+
this.heartbeat()
|
|
188
|
+
this.send({type: 'pong', uid: this.uid})
|
|
189
|
+
}
|
|
155
190
|
break
|
|
156
191
|
case 'error':
|
|
157
192
|
this.stop()
|
|
158
|
-
this.
|
|
193
|
+
this.notifyError(raw.error)
|
|
159
194
|
break
|
|
160
195
|
default:
|
|
161
196
|
console.log('Unknown message type: ' + raw.type)
|
|
@@ -174,6 +209,7 @@ export default class StellarBrokerClient {
|
|
|
174
209
|
this.status = 'quote'
|
|
175
210
|
this.connect()
|
|
176
211
|
.then(() => {
|
|
212
|
+
this.status = 'quote'
|
|
177
213
|
this.send({
|
|
178
214
|
type: 'quote',
|
|
179
215
|
...this.quoteRequest
|
|
@@ -181,137 +217,35 @@ export default class StellarBrokerClient {
|
|
|
181
217
|
})
|
|
182
218
|
}
|
|
183
219
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (this.status !== 'trade' && this.status !== 'quote')
|
|
189
|
-
return
|
|
190
|
-
this.send({
|
|
191
|
-
type: 'stop'
|
|
192
|
-
})
|
|
193
|
-
this.tradeQuote = undefined
|
|
194
|
-
this.source = undefined
|
|
195
|
-
this.status = 'ready'
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Confirm current quote and start trading
|
|
200
|
-
* @param {string} account - Trader account address
|
|
201
|
-
* @param {string|ClientAuthorizationCallback} authorization - Authorization method, either account secret key or an authorization callback
|
|
202
|
-
*/
|
|
203
|
-
confirmQuote(account, authorization) {
|
|
204
|
-
if (this.status !== 'quote')
|
|
220
|
+
confirmQuote() {
|
|
221
|
+
if (this.status === 'disconnected')
|
|
222
|
+
throw errors.notConnected()
|
|
223
|
+
if (this.status === 'trade')
|
|
205
224
|
throw errors.tradeInProgress()
|
|
206
|
-
if (!this.lastQuote)
|
|
225
|
+
if (this.status !== 'quote' || !this.lastQuote)
|
|
207
226
|
throw errors.quoteNotSet()
|
|
208
227
|
if ((new Date() - this.lastQuote.ts) > 7000) //do not allow stale quotes quoted more than 7s ago
|
|
209
228
|
throw errors.quoteExpired()
|
|
210
229
|
if (this.lastQuote.status !== 'success')
|
|
211
230
|
throw errors.quoteError(this.lastQuote.error || 'quote not available')
|
|
212
|
-
|
|
213
|
-
throw errors.invalidQuoteParam('account', 'Invalid trader account address: ' + (!account ? 'missing' : account))
|
|
214
|
-
if (typeof authorization === 'string') {
|
|
215
|
-
try {
|
|
216
|
-
this.authorization = Keypair.fromSecret(authorization)
|
|
217
|
-
} catch (e) {
|
|
218
|
-
throw errors.invalidAuthorizationParam()
|
|
219
|
-
}
|
|
220
|
-
} else if (typeof authorization === 'function') {
|
|
221
|
-
this.authorization = authorization
|
|
222
|
-
}
|
|
231
|
+
|
|
223
232
|
this.tradeQuote = this.lastQuote
|
|
224
|
-
this.source = account
|
|
225
233
|
this.send({
|
|
226
234
|
type: 'trade',
|
|
227
|
-
account
|
|
235
|
+
account: this.trader
|
|
228
236
|
})
|
|
229
237
|
this.status = 'trade'
|
|
230
238
|
}
|
|
231
239
|
|
|
232
240
|
/**
|
|
233
|
-
*
|
|
234
|
-
* @private
|
|
235
|
-
*/
|
|
236
|
-
processTxRequest(raw) {
|
|
237
|
-
//parse incoming transaction
|
|
238
|
-
let tx
|
|
239
|
-
try {
|
|
240
|
-
tx = TransactionBuilder.fromXDR(raw.xdr, this.network)
|
|
241
|
-
} catch (e) {
|
|
242
|
-
throw errors.invalidSwapTx()
|
|
243
|
-
}
|
|
244
|
-
//check that transaction is correct
|
|
245
|
-
if (!this.validateTransaction(tx))
|
|
246
|
-
throw errors.invalidSwapTx()
|
|
247
|
-
//sign transaction
|
|
248
|
-
this.authorizeTx(tx, raw.networkFee)
|
|
249
|
-
.then(tx => {
|
|
250
|
-
if (!tx || tx.signatures.length < 1)
|
|
251
|
-
return console.error(`Transaction ${raw.hash} not signed`)
|
|
252
|
-
//respond with signed transaction
|
|
253
|
-
this.send({
|
|
254
|
-
type: 'tx',
|
|
255
|
-
hash: raw.hash,
|
|
256
|
-
xdr: tx.toXDR()
|
|
257
|
-
})
|
|
258
|
-
})
|
|
259
|
-
.catch(e => {
|
|
260
|
-
console.error(e)
|
|
261
|
-
throw errors.failedToSignTx()
|
|
262
|
-
})
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* @param {Transaction} tx
|
|
267
|
-
* @param {string} networkFee
|
|
268
|
-
* @return {Promise<FeeBumpTransaction>}
|
|
269
|
-
* @private
|
|
270
|
-
*/
|
|
271
|
-
async authorizeTx(tx, networkFee) {
|
|
272
|
-
//sign tx
|
|
273
|
-
if (this.authorization instanceof Keypair) {
|
|
274
|
-
tx.sign(this.authorization)
|
|
275
|
-
} else {
|
|
276
|
-
tx = await ensurePromise(this.authorization(tx))
|
|
277
|
-
}
|
|
278
|
-
//wrap with fee bump
|
|
279
|
-
let wrapped = TransactionBuilder.buildFeeBumpTransaction(this.source, networkFee, tx, this.network)
|
|
280
|
-
//sign fee bump wrapper tx
|
|
281
|
-
if (this.authorization instanceof Keypair) {
|
|
282
|
-
wrapped.sign(this.authorization)
|
|
283
|
-
} else {
|
|
284
|
-
wrapped = await ensurePromise(this.authorization(wrapped))
|
|
285
|
-
}
|
|
286
|
-
return wrapped
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* @param {Transaction} tx
|
|
291
|
-
* @throws {StellarBrokerError} Invalid swap transaction received
|
|
241
|
+
* Stop quotation/trading
|
|
292
242
|
*/
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
for (let swap of tx.operations) {
|
|
300
|
-
if (swap.type !== 'pathPaymentStrictSend' && swap.type !== 'pathPaymentStrictReceive')
|
|
301
|
-
return false
|
|
302
|
-
const isFee = swap.destination !== this.source
|
|
303
|
-
if (isFee) {
|
|
304
|
-
if (swap.type !== 'pathPaymentStrictSend')
|
|
305
|
-
return false
|
|
306
|
-
if ((swap.source && swap.source !== this.source))
|
|
307
|
-
return false
|
|
308
|
-
} else {
|
|
309
|
-
if ((swap.source && swap.source !== swap.destination) || swap.destination !== this.source)
|
|
310
|
-
return false
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
//TODO: check assets and amounts
|
|
314
|
-
return true
|
|
243
|
+
stop() {
|
|
244
|
+
if (this.status !== 'trade' && this.status !== 'quote')
|
|
245
|
+
return
|
|
246
|
+
this.send({type: 'stop'})
|
|
247
|
+
this.tradeQuote = undefined
|
|
248
|
+
this.status = 'ready'
|
|
315
249
|
}
|
|
316
250
|
|
|
317
251
|
/**
|
|
@@ -322,12 +256,15 @@ export default class StellarBrokerClient {
|
|
|
322
256
|
this.socket.send(JSON.stringify(data))
|
|
323
257
|
}
|
|
324
258
|
|
|
259
|
+
/**
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
325
262
|
heartbeat() {
|
|
326
263
|
clearTimeout(this.pingHandler)
|
|
327
264
|
this.pingHandler = setTimeout(() => {
|
|
328
265
|
console.warn('Lost connection with the server')
|
|
329
266
|
this.socket.close()
|
|
330
|
-
},
|
|
267
|
+
}, 7_000) // 7 seconds heartbeat timeout
|
|
331
268
|
}
|
|
332
269
|
|
|
333
270
|
/**
|
|
@@ -336,8 +273,7 @@ export default class StellarBrokerClient {
|
|
|
336
273
|
* @param {function} callback
|
|
337
274
|
*/
|
|
338
275
|
on(type, callback) {
|
|
339
|
-
|
|
340
|
-
throw errors.unsupportedEventType(type)
|
|
276
|
+
validateEventType(type)
|
|
341
277
|
this.emitter.addEventListener(type, callback)
|
|
342
278
|
}
|
|
343
279
|
|
|
@@ -347,8 +283,7 @@ export default class StellarBrokerClient {
|
|
|
347
283
|
* @param {function} callback
|
|
348
284
|
*/
|
|
349
285
|
once(type, callback) {
|
|
350
|
-
|
|
351
|
-
throw errors.unsupportedEventType(type)
|
|
286
|
+
validateEventType(type)
|
|
352
287
|
this.emitter.addEventListener(type, callback, {once: true})
|
|
353
288
|
}
|
|
354
289
|
|
|
@@ -358,8 +293,7 @@ export default class StellarBrokerClient {
|
|
|
358
293
|
* @param {function} callback
|
|
359
294
|
*/
|
|
360
295
|
off(type, callback) {
|
|
361
|
-
|
|
362
|
-
throw errors.unsupportedEventType(type)
|
|
296
|
+
validateEventType(type)
|
|
363
297
|
this.emitter.removeEventListener(type, callback)
|
|
364
298
|
}
|
|
365
299
|
|
|
@@ -368,37 +302,40 @@ export default class StellarBrokerClient {
|
|
|
368
302
|
*/
|
|
369
303
|
close() {
|
|
370
304
|
try {
|
|
305
|
+
this.status = 'disconnected'
|
|
371
306
|
this.socket.close()
|
|
372
307
|
} catch (e) {
|
|
373
308
|
}
|
|
374
309
|
}
|
|
375
310
|
|
|
376
311
|
/**
|
|
377
|
-
*
|
|
378
|
-
* @
|
|
379
|
-
* @return {Promise<SwapQuoteResult>}
|
|
312
|
+
* @param {Error} e
|
|
313
|
+
* @private
|
|
380
314
|
*/
|
|
381
|
-
|
|
382
|
-
|
|
315
|
+
notifyError(e) {
|
|
316
|
+
console.error(e)
|
|
317
|
+
try {
|
|
318
|
+
this.emitter.dispatchEvent(buildEvent('error', e instanceof Error ? e.message : e))
|
|
319
|
+
} catch (e) {
|
|
320
|
+
console.error(e)
|
|
321
|
+
}
|
|
383
322
|
}
|
|
384
323
|
}
|
|
385
324
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (!callResult instanceof Promise) {
|
|
390
|
-
if (callResult)
|
|
391
|
-
return Promise.resolve(callResult)
|
|
392
|
-
return Promise.reject()
|
|
393
|
-
}
|
|
394
|
-
return callResult
|
|
325
|
+
function validateEventType(type) {
|
|
326
|
+
if (!StellarBrokerEvents.includes(type))
|
|
327
|
+
throw errors.unsupportedEventType(type)
|
|
395
328
|
}
|
|
396
329
|
|
|
330
|
+
export const StellarBrokerEvents = ['quote', 'paused', 'progress', 'finished', 'error']
|
|
331
|
+
|
|
397
332
|
/**
|
|
398
333
|
* @typedef {object} ClientInitializationParams
|
|
399
334
|
* @property {string} [network] - Stellar network identifier or passphrase
|
|
400
335
|
* @property {string} [partnerKey] - Partner key
|
|
401
336
|
* @property {SwapFlowMode} [flow] - Swap flow mode
|
|
337
|
+
* @property {string} account - Trader account address
|
|
338
|
+
* @property {string|ClientAuthorizationCallback} authorization - Authorization method, either account secret key or an authorization callback
|
|
402
339
|
*/
|
|
403
340
|
|
|
404
341
|
/**
|
|
@@ -406,7 +343,7 @@ function ensurePromise(callResult) {
|
|
|
406
343
|
*/
|
|
407
344
|
|
|
408
345
|
/**
|
|
409
|
-
* @typedef {'quote'|'
|
|
346
|
+
* @typedef {'quote'|'paused'|'progress'|'finished'|'error'} StellarBrokerClientEvent
|
|
410
347
|
*/
|
|
411
348
|
|
|
412
349
|
/**
|
|
@@ -428,5 +365,5 @@ function ensurePromise(callResult) {
|
|
|
428
365
|
*/
|
|
429
366
|
|
|
430
367
|
/**
|
|
431
|
-
* @typedef {function(
|
|
368
|
+
* @typedef {function(TransactionI|Buffer):Promise<TransactionI|Buffer>} ClientAuthorizationCallback
|
|
432
369
|
*/
|
package/src/errors.js
CHANGED
|
@@ -19,6 +19,9 @@ const errors = {
|
|
|
19
19
|
invalidAuthorizationParam() {
|
|
20
20
|
return new StellarBrokerError(2, 'Invalid authorization secret key or callback provided')
|
|
21
21
|
},
|
|
22
|
+
notConnected() {
|
|
23
|
+
return new StellarBrokerError(3, 'Client not connected to the server')
|
|
24
|
+
},
|
|
22
25
|
quoteNotSet() {
|
|
23
26
|
return new StellarBrokerError(11, 'Price quote not available')
|
|
24
27
|
},
|
|
@@ -28,14 +31,14 @@ const errors = {
|
|
|
28
31
|
quoteError(message) {
|
|
29
32
|
return new StellarBrokerError(13, 'Price quotation error: ' + message)
|
|
30
33
|
},
|
|
31
|
-
invalidQuoteParam(invalidParamName, details) {
|
|
34
|
+
invalidQuoteParam(invalidParamName = 'asset', details) {
|
|
32
35
|
return new StellarBrokerError(14, `Invalid quote request parameter: "${invalidParamName}". ${details}`)
|
|
33
36
|
},
|
|
34
37
|
tradeInProgress() {
|
|
35
38
|
return new StellarBrokerError(20, 'Cannot change quote while trade is in progress')
|
|
36
39
|
},
|
|
37
40
|
invalidSwapTx() {
|
|
38
|
-
return new StellarBrokerError(21, 'Invalid swap transaction received')
|
|
41
|
+
return new StellarBrokerError(21, 'Invalid swap transaction received from the server')
|
|
39
42
|
},
|
|
40
43
|
failedToSignTx() {
|
|
41
44
|
return new StellarBrokerError(22, 'Failed to sign received transaction')
|
|
@@ -44,7 +47,7 @@ const errors = {
|
|
|
44
47
|
return new StellarBrokerError(31, 'Unknown event type: ' + type)
|
|
45
48
|
},
|
|
46
49
|
serverError(message) {
|
|
47
|
-
return new StellarBrokerError(101,
|
|
50
|
+
return new StellarBrokerError(101, message)
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
|
package/src/estimate.js
CHANGED
|
@@ -2,12 +2,14 @@ import errors, {StellarBrokerError} from './errors.js'
|
|
|
2
2
|
import {validateQuoteRequest} from './quote-request.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Request single swap quote estimate without trading
|
|
6
6
|
* @param {SwapQuoteParams} params
|
|
7
7
|
* @return {Promise<SwapQuoteResult>}
|
|
8
8
|
*/
|
|
9
9
|
export async function estimateSwap(params) {
|
|
10
|
-
const query = Object.entries(validateQuoteRequest(params))
|
|
10
|
+
const query = Object.entries(validateQuoteRequest(params))
|
|
11
|
+
.filter(kv => kv[1] !== undefined)
|
|
12
|
+
.map(([param, value]) => encodeURIComponent(param) + '=' + encodeURIComponent(value))
|
|
11
13
|
const url = (params.origin || 'https://api.stellar.broker') + '/quote?' + query.join('&')
|
|
12
14
|
try {
|
|
13
15
|
const res = await fetch(url).then(r => r.json())
|
package/src/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {FeeBumpTransaction} from "@stellar/stellar-sdk";
|
|
2
|
+
|
|
1
3
|
export default class StellarBrokerClient {
|
|
2
4
|
/**
|
|
3
5
|
* @param {ClientInitializationParams} params
|
|
@@ -70,6 +72,11 @@ export default class StellarBrokerClient {
|
|
|
70
72
|
close(): void;
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Request single swap quote estimate without trading
|
|
77
|
+
*/
|
|
78
|
+
export function estimateSwap(params: SwapQuoteParams): Promise<SwapQuoteResult>;
|
|
79
|
+
|
|
73
80
|
export interface SwapQuoteParams {
|
|
74
81
|
/**
|
|
75
82
|
* Asset to sell
|
|
@@ -144,4 +151,3 @@ export class StellarBrokerError extends Error {
|
|
|
144
151
|
}
|
|
145
152
|
|
|
146
153
|
export type ClientAuthorizationCallback = (arg0: FeeBumpTransaction) => Promise<FeeBumpTransaction>;
|
|
147
|
-
import {FeeBumpTransaction} from "@stellar/stellar-base";
|
package/src/index.js
CHANGED
package/src/quote-request.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {StrKey} from '@stellar/stellar-
|
|
1
|
+
import {StrKey} from '@stellar/stellar-sdk'
|
|
2
2
|
import errors from './errors.js'
|
|
3
|
+
import {parseAsset} from './asset.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @typedef {object} SwapQuoteParams
|
|
@@ -39,38 +40,6 @@ export function validateQuoteRequest(params) {
|
|
|
39
40
|
return res
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function parseAsset(asset, parameter) {
|
|
43
|
-
if (typeof asset === 'string') {
|
|
44
|
-
if (asset === 'XLM' || asset === 'xlm' || asset === 'native')
|
|
45
|
-
return 'XLM'
|
|
46
|
-
if (asset.includes(':')) {
|
|
47
|
-
const [code, issuer] = asset.split(':')
|
|
48
|
-
validateCode(code)
|
|
49
|
-
validateAccount(issuer, parameter)
|
|
50
|
-
return code + '-' + issuer
|
|
51
|
-
}
|
|
52
|
-
if (asset.includes('-')) {
|
|
53
|
-
const [code, issuer] = asset.split('-')
|
|
54
|
-
validateCode(code)
|
|
55
|
-
validateAccount(issuer, parameter)
|
|
56
|
-
return asset
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
throw errors.invalidQuoteParam(parameter, 'Invalid asset')
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function validateCode(code, parameter) {
|
|
63
|
-
if (!/^[a-zA-Z0-9]{1,12}$/.test(code))
|
|
64
|
-
throw errors.invalidQuoteParam(parameter, 'Invalid asset code: ' + (!code ? 'missing' : code))
|
|
65
|
-
return code
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function validateAccount(account, param) {
|
|
69
|
-
if (!account || !StrKey.isValidEd25519PublicKey(account))
|
|
70
|
-
throw errors.invalidQuoteParam(param, 'Invalid account address: ' + (!account ? 'missing' : account))
|
|
71
|
-
return account
|
|
72
|
-
}
|
|
73
|
-
|
|
74
43
|
function parseAmount(amount, parameter) {
|
|
75
44
|
if (amount === undefined)
|
|
76
45
|
return undefined
|
package/src/stroops.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert arbitrary stringified amount to int64 representation
|
|
3
|
+
* @param {string|number} value
|
|
4
|
+
* @param {boolean} throwIfInvalid
|
|
5
|
+
* @return {BigInt}
|
|
6
|
+
*/
|
|
7
|
+
export function toStroops(value, throwIfInvalid = false) {
|
|
8
|
+
if (typeof value === 'number') {
|
|
9
|
+
if (value === 0)
|
|
10
|
+
return 0n
|
|
11
|
+
value = value.toFixed(7)
|
|
12
|
+
}
|
|
13
|
+
if (typeof value !== 'string' || !/^-?[\d.,]+$/.test(value)) {
|
|
14
|
+
if (throwIfInvalid)
|
|
15
|
+
throw new TypeError('Invalid number format')
|
|
16
|
+
return 0n
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
let [int, decimal = '0'] = value.split('.', 2)
|
|
20
|
+
let negative = false
|
|
21
|
+
if (int.startsWith('-')) {
|
|
22
|
+
negative = true
|
|
23
|
+
int = int.slice(1)
|
|
24
|
+
}
|
|
25
|
+
let res = BigInt(int) * 10000000n + BigInt(decimal.slice(0, 7).padEnd(7, '0'))
|
|
26
|
+
if (negative) {
|
|
27
|
+
res *= -1n
|
|
28
|
+
if (res < -0x8000000000000000n) {//overflow
|
|
29
|
+
if (throwIfInvalid)
|
|
30
|
+
throw new TypeError('Invalid number: Int64 overflow')
|
|
31
|
+
return 0n
|
|
32
|
+
}
|
|
33
|
+
} else if (res > 0xFFFFFFFFFFFFFFFFn) {//overflow
|
|
34
|
+
if (throwIfInvalid)
|
|
35
|
+
throw new TypeError('Invalid number: UInt64 overflow')
|
|
36
|
+
return 0n
|
|
37
|
+
}
|
|
38
|
+
return res
|
|
39
|
+
} catch (e) {
|
|
40
|
+
if (throwIfInvalid) {
|
|
41
|
+
if (e.message.startsWith('Invalid number: '))
|
|
42
|
+
throw e
|
|
43
|
+
throw new TypeError('Invalid number format')
|
|
44
|
+
}
|
|
45
|
+
return 0n
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Format int64 value representation in stroops as string
|
|
52
|
+
* @param {bigint} valueInStroops
|
|
53
|
+
* @return {string}
|
|
54
|
+
*/
|
|
55
|
+
export function fromStroops(valueInStroops) {
|
|
56
|
+
if (valueInStroops < 0n)
|
|
57
|
+
throw new TypeError('Invalid amount: ' + valueInStroops.toString())
|
|
58
|
+
const int = valueInStroops / 10000000n
|
|
59
|
+
const fract = valueInStroops % 10000000n
|
|
60
|
+
let res = int.toString()
|
|
61
|
+
if (fract) {
|
|
62
|
+
res += '.' + fract.toString().padStart(7, '0').replace(/0+$/, '')
|
|
63
|
+
}
|
|
64
|
+
return res
|
|
65
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
|
162
|
+
}
|
package/webpack.config.cjs
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const webpack = require('webpack')
|
|
3
|
-
|
|
4
|
-
module.exports = function () {
|
|
5
|
-
return {
|
|
6
|
-
mode: 'production',
|
|
7
|
-
devtool: 'source-map',
|
|
8
|
-
entry: {
|
|
9
|
-
stellarbroker: [path.join(__dirname, '/src/index.js')]
|
|
10
|
-
},
|
|
11
|
-
output: {
|
|
12
|
-
path: path.join(__dirname, './lib'),
|
|
13
|
-
filename: '[name].js',
|
|
14
|
-
library: {
|
|
15
|
-
name: 'stellarbroker',
|
|
16
|
-
type: 'umd',
|
|
17
|
-
export: 'default'
|
|
18
|
-
},
|
|
19
|
-
libraryTarget: 'umd',
|
|
20
|
-
globalObject: 'this'
|
|
21
|
-
},
|
|
22
|
-
module: {
|
|
23
|
-
rules: [
|
|
24
|
-
{
|
|
25
|
-
test: /\.js?$/,
|
|
26
|
-
loader: 'babel-loader',
|
|
27
|
-
exclude: /node_modules/
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
},
|
|
31
|
-
externals: {
|
|
32
|
-
'@stellar/stellar-base': '@stellar/stellar-base'
|
|
33
|
-
},
|
|
34
|
-
plugins: [
|
|
35
|
-
new webpack.DefinePlugin({
|
|
36
|
-
'process.env.NODE_ENV': JSON.stringify('production')
|
|
37
|
-
})
|
|
38
|
-
],
|
|
39
|
-
optimization: {
|
|
40
|
-
minimize: true
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|