@stellar-broker/client 0.3.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 CHANGED
@@ -8,10 +8,9 @@ npm i @stellar-broker/client
8
8
 
9
9
  ## Usage
10
10
 
11
- ### Example - Connect and trade in DirectFlow mode
11
+ ### Example - connect and trade with StellarBroker
12
12
 
13
- This flow is suitable for the majority of scenarios when a user plans to swap assets.
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,7 +95,7 @@ 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 want to get a single price quote.
98
+ streaming mode, and just wants to get a single price quote.
96
99
 
97
100
  ```js
98
101
  import {estimateSwap} from '@stellar-broker/client'
@@ -1,2 +1,2 @@
1
- !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("@stellar/stellar-base")):"function"==typeof define&&define.amd?define(["@stellar/stellar-base"],e):"object"==typeof exports?exports.stellarbroker=e(require("@stellar/stellar-base")):t.stellarbroker=e(t["@stellar/stellar-base"])}(this,(t=>(()=>{"use strict";var e={755:e=>{e.exports=t}},s={};function i(t){var o=s[t];if(void 0!==o)return o.exports;var r=s[t]={exports:{}};return e[t](r,r.exports,i),r.exports}i.d=(t,e)=>{for(var s in e)i.o(e,s)&&!i.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var o={};i.d(o,{default:()=>w});var r=i(755);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"),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,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"),failedToSignTx:()=>new n(22,"Failed to sign received transaction"),unsupportedEventType:t=>new n(31,"Unknown event type: "+t),serverError:t=>new n(101,"Server error: "+t)};function u(t,e,s){const i=new CustomEvent(t,{detail:e});return i[s||t]=e,i}function c(t){const{destination:e,sellingAsset:s,selling_asset:i,buyingAsset:o,buying_asset:r,sellingAmount:n,selling_amount:u,buyingAmount:c,buying_amount:h,slippageTolerance:d,slippage_tolerance:m,...g}=t,v={sellingAsset:l(s||i,"sellingAsset"),buyingAsset:l(o||r,"buyingAsset"),sellingAmount:p(n||u,"sellingAmount"),buyingAmount:p(c||h,"buyingAmount"),slippageTolerance:f(d||m||.02,"slippageTolerance"),flow:"direct"};if(v.buyingAsset===v.sellingAsset)throw a.invalidQuoteParam("buyingAsset","Buying asset can't be the same as selling asset");if(void 0===v.buyingAmount&&void 0===v.sellingAmount)throw a.invalidQuoteParam("sellingAmount",'Either "buyingAmount" or "sellingAmount" parameter is required');if(void 0!==v.buyingAmount&&void 0!==v.sellingAmount)throw a.invalidQuoteParam("sellingAmount",'Parameters "buyingAmount" and "sellingAmount" are mutually exclusive');return Object.assign(v,g),Object.freeze(v),v}function l(t,e){if("string"==typeof t){if("XLM"===t||"xlm"===t||"native"===t)return"XLM";if(t.includes(":")){const[s,i]=t.split(":");return h(s),d(i,e),s+"-"+i}if(t.includes("-")){const[s,i]=t.split("-");return h(s),d(i,e),t}}throw a.invalidQuoteParam(e,"Invalid asset")}function h(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 p(t,e){if(void 0===t)return;const s=parseFloat(t);if(isNaN(s)||s<=0)throw a.invalidQuoteParam(e,"Invalid asset amount: "+t);return t}function f(t,e){const s=parseFloat(t);if(isNaN(s))throw a.invalidQuoteParam(e,"Invalid slippage tolerance, number expected");if(s<0)throw a.invalidQuoteParam(e,"Slippage tolerance is too small, expected value >= 0");if(s>.5)throw a.invalidQuoteParam(e,"Slippage tolerance is too large, expected value < 0.5 (50%)");return s}class m{constructor(t){this.partnerKey=t.partnerKey,this.emitter=new EventTarget,this.network=r.Networks[(t.network||"PUBLIC").toUpperCase()]||t.network,this.flow=t.flow||"direct"}status="disconnected";socket;emitter;origin="https://api.stellar.broker";network="";quoteRequest;lastQuote;tradeQuote;partnerKey;authorization;source;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=()=>{console.log("Connection closed"),this.status="disconnected"},this.socket.onerror=t=>console.error(t),new Promise(((t,e)=>{const s=setTimeout((()=>e(this)),2e3);this.onSocketOpen=()=>{this.status="ready",this.heartbeat(),this.onSocketOpen=void 0,clearTimeout(s),t(this)}})))}processMessage(t){const e=JSON.parse(t.data);switch(e.type){case"connected":this.onSocketOpen&&this.onSocketOpen();break;case"quote":this.lastQuote=e.quote,this.lastQuote.ts=new Date,this.emitter.dispatchEvent(u("quote",this.lastQuote));break;case"tx":if("trade"!==this.status)return void console.log("Received tx in non-trading state",this.status,e);this.processTxRequest(e);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,this.source=void 0;break;case"progress":this.emitter.dispatchEvent(u("progress",{sold:e.sold,bought:e.bought},"status"));break;case"ping":this.heartbeat(),this.send({type:"pong"});break;case"error":this.stop(),this.emitter.dispatchEvent(u("error","Server error: "+e.error));break;default:console.log("Unknown message type: "+e.type)}}quote(t){if("trade"===this.status)throw a.tradeInProgress();this.quoteRequest=c(t),this.status="quote",this.connect().then((()=>{this.send({type:"quote",...this.quoteRequest})}))}stop(){"trade"!==this.status&&"quote"!==this.status||(this.send({type:"stop"}),this.tradeQuote=void 0,this.source=void 0,this.status="ready")}confirmQuote(t,e){if("quote"!==this.status)throw a.tradeInProgress();if(!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");if(!t||!r.StrKey.isValidEd25519PublicKey(t))throw a.invalidQuoteParam("account","Invalid trader account address: "+(t||"missing"));if("string"==typeof e)try{this.authorization=r.Keypair.fromSecret(e)}catch(t){throw a.invalidAuthorizationParam()}else"function"==typeof e&&(this.authorization=e);this.tradeQuote=this.lastQuote,this.source=t,this.send({type:"trade",account:t}),this.status="trade"}processTxRequest(t){let e;try{e=r.TransactionBuilder.fromXDR(t.xdr,this.network)}catch(t){throw a.invalidSwapTx()}if(!this.validateTransaction(e))throw a.invalidSwapTx();this.authorizeTx(e,t.networkFee).then((e=>{if(!e||e.signatures.length<1)return console.error(`Transaction ${t.hash} not signed`);this.send({type:"tx",hash:t.hash,xdr:e.toXDR()})})).catch((t=>{throw console.error(t),a.failedToSignTx()}))}async authorizeTx(t,e){this.authorization instanceof r.Keypair?t.sign(this.authorization):t=await v(this.authorization(t));let s=r.TransactionBuilder.buildFeeBumpTransaction(this.source,e,t,this.network);return this.authorization instanceof r.Keypair?s.sign(this.authorization):s=await v(this.authorization(s)),s}validateTransaction(t){for(let e of t.operations){if("pathPaymentStrictSend"!==e.type&&"pathPaymentStrictReceive"!==e.type)return!1;if(e.destination!==this.source){if("pathPaymentStrictSend"!==e.type)return!1;if(e.source&&e.source!==this.source)return!1}else if(e.source&&e.source!==e.destination||e.destination!==this.source)return!1}return!0}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()}),11e3)}on(t,e){if(!g.includes(t))throw a.unsupportedEventType(t);this.emitter.addEventListener(t,e)}once(t,e){if(!g.includes(t))throw a.unsupportedEventType(t);this.emitter.addEventListener(t,e,{once:!0})}off(t,e){if(!g.includes(t))throw a.unsupportedEventType(t);this.emitter.removeEventListener(t,e)}close(){try{this.socket.close()}catch(t){}}}const g=["quote","finished","progress","error"];function v(t){return!t instanceof Promise?t?Promise.resolve(t):Promise.reject():t}m.estimateSwap=async function(t){const e=Object.entries(c(t)).filter((t=>void 0!==t[1])).map((([t,e])=>encodeURIComponent(t)+"="+encodeURIComponent(e))),s=(t.origin||"https://api.stellar.broker")+"/quote?"+e.join("&");try{const t=await fetch(s).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 w=m;return o.default})()));
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
@@ -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/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-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\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\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\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__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","StellarBrokerClient","partnerKey","emitter","EventTarget","network","Networks","toUpperCase","status","socket","origin","quoteRequest","lastQuote","tradeQuote","authorization","source","connect","readyState","WebSocket","OPEN","Promise","resolve","encodeURIComponent","onmessage","processMessage","bind","onclose","console","log","onerror","e","error","confirm","reject","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","ensurePromise","wrapped","buildFeeBumpTransaction","swap","operations","stringify","pingHandler","warn","close","on","callback","StellarBrokerEvents","addEventListener","once","off","removeEventListener","callResult","estimateSwap","async","query","entries","filter","kv","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,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellar-broker/client",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Browser client library for StellarBroker service",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -17,7 +17,7 @@
17
17
  "author": "StellarBroker",
18
18
  "license": "MIT",
19
19
  "peerDependencies": {
20
- "@stellar/stellar-base": ">=11"
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,7 +1,8 @@
1
- import {Networks, TransactionBuilder, Keypair, FeeBumpTransaction, StrKey} from '@stellar/stellar-base'
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 {processTxRequest} from './tx-processor.js'
5
6
 
6
7
  export default class StellarBrokerClient {
7
8
  /**
@@ -12,8 +13,25 @@ export default class StellarBrokerClient {
12
13
  this.emitter = new EventTarget()
13
14
  this.network = Networks[(params.network || 'PUBLIC').toUpperCase()] || params.network
14
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
+ }
15
28
  }
16
29
 
30
+ /**
31
+ * @type {string}
32
+ * @private
33
+ */
34
+ uid
17
35
  /**
18
36
  * @type {ClientStatus}
19
37
  * @readonly
@@ -69,13 +87,12 @@ export default class StellarBrokerClient {
69
87
  * @private
70
88
  */
71
89
  authorization
72
-
73
90
  /**
74
91
  * Trader account public key
75
92
  * @type {string}
76
93
  * @readonly
77
94
  */
78
- source
95
+ trader
79
96
 
80
97
  /**
81
98
  * Connect to the StellarBroker server
@@ -84,12 +101,14 @@ export default class StellarBrokerClient {
84
101
  connect() {
85
102
  if (this.socket?.readyState === WebSocket.OPEN)
86
103
  return Promise.resolve(this) //already opened
87
-
88
104
  this.socket = new WebSocket(this.origin + '/ws?partner=' + encodeURIComponent(this.partnerKey))
89
105
  this.socket.onmessage = this.processMessage.bind(this)
90
106
  this.socket.onclose = () => {
107
+ if (this.status !== 'disconnected') {
108
+ this.status = 'disconnected'
109
+ this.notifyError(errors.notConnected())
110
+ }
91
111
  console.log('Connection closed')
92
- this.status = 'disconnected'
93
112
  }
94
113
 
95
114
  this.socket.onerror = e => console.error(e)
@@ -115,9 +134,13 @@ export default class StellarBrokerClient {
115
134
  const raw = JSON.parse(message.data)
116
135
  switch (raw.type) {
117
136
  case 'connected':
137
+ this.uid = raw.uid
118
138
  if (this.onSocketOpen) {
119
139
  this.onSocketOpen()
120
140
  }
141
+ if (this.status === 'disconnected') {
142
+ this.status = 'ready'
143
+ }
121
144
  break
122
145
  case 'quote':
123
146
  this.lastQuote = raw.quote
@@ -125,12 +148,24 @@ export default class StellarBrokerClient {
125
148
  //send event to the client app
126
149
  this.emitter.dispatchEvent(buildEvent('quote', this.lastQuote))
127
150
  break
151
+ case 'paused':
152
+ //quotation paused due to inactivity
153
+ this.emitter.dispatchEvent(buildEvent('paused', {}))
154
+ break
128
155
  case 'tx':
129
156
  if (this.status !== 'trade') {
130
157
  console.log('Received tx in non-trading state', this.status, raw)
131
158
  return //skip unless trading is in progress
132
159
  }
133
- this.processTxRequest(raw)
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))
134
169
  break
135
170
  case 'stop':
136
171
  this.emitter.dispatchEvent(buildEvent('finished', {
@@ -140,7 +175,6 @@ export default class StellarBrokerClient {
140
175
  }, 'result'))
141
176
  this.status = 'ready'
142
177
  this.tradeQuote = undefined
143
- this.source = undefined
144
178
  break
145
179
  case 'progress':
146
180
  this.emitter.dispatchEvent(buildEvent('progress', {
@@ -149,12 +183,14 @@ export default class StellarBrokerClient {
149
183
  }, 'status'))
150
184
  break
151
185
  case 'ping':
152
- this.heartbeat()
153
- this.send({type: 'pong'})
186
+ if (raw.uid === this.uid) {
187
+ this.heartbeat()
188
+ this.send({type: 'pong', uid: this.uid})
189
+ }
154
190
  break
155
191
  case 'error':
156
192
  this.stop()
157
- this.emitter.dispatchEvent(buildEvent('error', 'Server error: ' + raw.error))
193
+ this.notifyError(raw.error)
158
194
  break
159
195
  default:
160
196
  console.log('Unknown message type: ' + raw.type)
@@ -173,6 +209,7 @@ export default class StellarBrokerClient {
173
209
  this.status = 'quote'
174
210
  this.connect()
175
211
  .then(() => {
212
+ this.status = 'quote'
176
213
  this.send({
177
214
  type: 'quote',
178
215
  ...this.quoteRequest
@@ -180,137 +217,35 @@ export default class StellarBrokerClient {
180
217
  })
181
218
  }
182
219
 
183
- /**
184
- * Stop quotation/trading
185
- */
186
- stop() {
187
- if (this.status !== 'trade' && this.status !== 'quote')
188
- return
189
- this.send({
190
- type: 'stop'
191
- })
192
- this.tradeQuote = undefined
193
- this.source = undefined
194
- this.status = 'ready'
195
- }
196
-
197
- /**
198
- * Confirm current quote and start trading
199
- * @param {string} account - Trader account address
200
- * @param {string|ClientAuthorizationCallback} authorization - Authorization method, either account secret key or an authorization callback
201
- */
202
- confirmQuote(account, authorization) {
203
- if (this.status !== 'quote')
220
+ confirmQuote() {
221
+ if (this.status === 'disconnected')
222
+ throw errors.notConnected()
223
+ if (this.status === 'trade')
204
224
  throw errors.tradeInProgress()
205
- if (!this.lastQuote)
225
+ if (this.status !== 'quote' || !this.lastQuote)
206
226
  throw errors.quoteNotSet()
207
227
  if ((new Date() - this.lastQuote.ts) > 7000) //do not allow stale quotes quoted more than 7s ago
208
228
  throw errors.quoteExpired()
209
229
  if (this.lastQuote.status !== 'success')
210
230
  throw errors.quoteError(this.lastQuote.error || 'quote not available')
211
- if (!account || !StrKey.isValidEd25519PublicKey(account))
212
- throw errors.invalidQuoteParam('account', 'Invalid trader account address: ' + (!account ? 'missing' : account))
213
- if (typeof authorization === 'string') {
214
- try {
215
- this.authorization = Keypair.fromSecret(authorization)
216
- } catch (e) {
217
- throw errors.invalidAuthorizationParam()
218
- }
219
- } else if (typeof authorization === 'function') {
220
- this.authorization = authorization
221
- }
231
+
222
232
  this.tradeQuote = this.lastQuote
223
- this.source = account
224
233
  this.send({
225
234
  type: 'trade',
226
- account
235
+ account: this.trader
227
236
  })
228
237
  this.status = 'trade'
229
238
  }
230
239
 
231
240
  /**
232
- * @param {{xdr: string, hash: string}} raw
233
- * @private
234
- */
235
- processTxRequest(raw) {
236
- //parse incoming transaction
237
- let tx
238
- try {
239
- tx = TransactionBuilder.fromXDR(raw.xdr, this.network)
240
- } catch (e) {
241
- throw errors.invalidSwapTx()
242
- }
243
- //check that transaction is correct
244
- if (!this.validateTransaction(tx))
245
- throw errors.invalidSwapTx()
246
- //sign transaction
247
- this.authorizeTx(tx, raw.networkFee)
248
- .then(tx => {
249
- if (!tx || tx.signatures.length < 1)
250
- return console.error(`Transaction ${raw.hash} not signed`)
251
- //respond with signed transaction
252
- this.send({
253
- type: 'tx',
254
- hash: raw.hash,
255
- xdr: tx.toXDR()
256
- })
257
- })
258
- .catch(e => {
259
- console.error(e)
260
- throw errors.failedToSignTx()
261
- })
262
- }
263
-
264
- /**
265
- * @param {Transaction} tx
266
- * @param {string} networkFee
267
- * @return {Promise<FeeBumpTransaction>}
268
- * @private
269
- */
270
- async authorizeTx(tx, networkFee) {
271
- //sign tx
272
- if (this.authorization instanceof Keypair) {
273
- tx.sign(this.authorization)
274
- } else {
275
- tx = await ensurePromise(this.authorization(tx))
276
- }
277
- //wrap with fee bump
278
- let wrapped = TransactionBuilder.buildFeeBumpTransaction(this.source, networkFee, tx, this.network)
279
- //sign fee bump wrapper tx
280
- if (this.authorization instanceof Keypair) {
281
- wrapped.sign(this.authorization)
282
- } else {
283
- wrapped = await ensurePromise(this.authorization(wrapped))
284
- }
285
- return wrapped
286
- }
287
-
288
- /**
289
- * @param {Transaction} tx
290
- * @throws {StellarBrokerError} Invalid swap transaction received
241
+ * Stop quotation/trading
291
242
  */
292
- validateTransaction(tx) {
293
- /*if (!(tx instanceof FeeBumpTransaction))
294
- return false
295
- const {innerTransaction} = tx
296
- if (tx.feeSource !== this.source)
297
- return false*/
298
- for (let swap of tx.operations) {
299
- if (swap.type !== 'pathPaymentStrictSend' && swap.type !== 'pathPaymentStrictReceive')
300
- return false
301
- const isFee = swap.destination !== this.source
302
- if (isFee) {
303
- if (swap.type !== 'pathPaymentStrictSend')
304
- return false
305
- if ((swap.source && swap.source !== this.source))
306
- return false
307
- } else {
308
- if ((swap.source && swap.source !== swap.destination) || swap.destination !== this.source)
309
- return false
310
- }
311
- }
312
- //TODO: check assets and amounts
313
- 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'
314
249
  }
315
250
 
316
251
  /**
@@ -321,12 +256,15 @@ export default class StellarBrokerClient {
321
256
  this.socket.send(JSON.stringify(data))
322
257
  }
323
258
 
259
+ /**
260
+ * @private
261
+ */
324
262
  heartbeat() {
325
263
  clearTimeout(this.pingHandler)
326
264
  this.pingHandler = setTimeout(() => {
327
265
  console.warn('Lost connection with the server')
328
266
  this.socket.close()
329
- }, 11_000) // 11 seconds heartbeat timeout
267
+ }, 7_000) // 7 seconds heartbeat timeout
330
268
  }
331
269
 
332
270
  /**
@@ -335,8 +273,7 @@ export default class StellarBrokerClient {
335
273
  * @param {function} callback
336
274
  */
337
275
  on(type, callback) {
338
- if (!StellarBrokerEvents.includes(type))
339
- throw errors.unsupportedEventType(type)
276
+ validateEventType(type)
340
277
  this.emitter.addEventListener(type, callback)
341
278
  }
342
279
 
@@ -346,8 +283,7 @@ export default class StellarBrokerClient {
346
283
  * @param {function} callback
347
284
  */
348
285
  once(type, callback) {
349
- if (!StellarBrokerEvents.includes(type))
350
- throw errors.unsupportedEventType(type)
286
+ validateEventType(type)
351
287
  this.emitter.addEventListener(type, callback, {once: true})
352
288
  }
353
289
 
@@ -357,8 +293,7 @@ export default class StellarBrokerClient {
357
293
  * @param {function} callback
358
294
  */
359
295
  off(type, callback) {
360
- if (!StellarBrokerEvents.includes(type))
361
- throw errors.unsupportedEventType(type)
296
+ validateEventType(type)
362
297
  this.emitter.removeEventListener(type, callback)
363
298
  }
364
299
 
@@ -367,28 +302,40 @@ export default class StellarBrokerClient {
367
302
  */
368
303
  close() {
369
304
  try {
305
+ this.status = 'disconnected'
370
306
  this.socket.close()
371
307
  } catch (e) {
372
308
  }
373
309
  }
374
- }
375
310
 
376
- export const StellarBrokerEvents = ['quote', 'finished', 'progress', 'error']
377
-
378
- function ensurePromise(callResult) {
379
- if (!callResult instanceof Promise) {
380
- if (callResult)
381
- return Promise.resolve(callResult)
382
- return Promise.reject()
311
+ /**
312
+ * @param {Error} e
313
+ * @private
314
+ */
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
- return callResult
385
323
  }
386
324
 
325
+ function validateEventType(type) {
326
+ if (!StellarBrokerEvents.includes(type))
327
+ throw errors.unsupportedEventType(type)
328
+ }
329
+
330
+ export const StellarBrokerEvents = ['quote', 'paused', 'progress', 'finished', 'error']
331
+
387
332
  /**
388
333
  * @typedef {object} ClientInitializationParams
389
334
  * @property {string} [network] - Stellar network identifier or passphrase
390
335
  * @property {string} [partnerKey] - Partner key
391
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
392
339
  */
393
340
 
394
341
  /**
@@ -396,7 +343,7 @@ function ensurePromise(callResult) {
396
343
  */
397
344
 
398
345
  /**
399
- * @typedef {'quote'|'finished'|'progress'|'error'} StellarBrokerClientEvent
346
+ * @typedef {'quote'|'paused'|'progress'|'finished'|'error'} StellarBrokerClientEvent
400
347
  */
401
348
 
402
349
  /**
@@ -418,5 +365,5 @@ function ensurePromise(callResult) {
418
365
  */
419
366
 
420
367
  /**
421
- * @typedef {function(FeeBumpTransaction):Promise<FeeBumpTransaction>} ClientAuthorizationCallback
368
+ * @typedef {function(TransactionI|Buffer):Promise<TransactionI|Buffer>} ClientAuthorizationCallback
422
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, 'Server error: ' + message)
50
+ return new StellarBrokerError(101, message)
48
51
  }
49
52
  }
50
53
 
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
@@ -149,4 +151,3 @@ export class StellarBrokerError extends Error {
149
151
  }
150
152
 
151
153
  export type ClientAuthorizationCallback = (arg0: FeeBumpTransaction) => Promise<FeeBumpTransaction>;
152
- import {FeeBumpTransaction} from "@stellar/stellar-base";
@@ -1,5 +1,6 @@
1
- import {StrKey} from '@stellar/stellar-base'
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
+ }