@usherlabs/cex-broker 0.2.1 → 0.2.3

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.
@@ -313654,20 +313654,33 @@ function loadPolicy(policyPath) {
313654
313654
  if (error) {
313655
313655
  throw new Error(`Policy validation failed: ${error.details.map((d) => d.message).join("; ")}`);
313656
313656
  }
313657
- const normalizedPolicy = value;
313658
- normalizedPolicy.withdraw.rule = normalizedPolicy.withdraw.rule.map((rule) => ({
313659
- ...rule,
313660
- exchange: rule.exchange.trim().toUpperCase(),
313661
- network: rule.network.trim().toUpperCase(),
313662
- whitelist: rule.whitelist.map((a) => a.trim().toLowerCase())
313663
- }));
313664
- normalizedPolicy.order.rule.limits = normalizedPolicy.order.rule.limits ?? [];
313665
- return normalizedPolicy;
313657
+ return normalizePolicyConfig(value);
313666
313658
  } catch (error) {
313667
313659
  console.error("Failed to load policy:", error);
313668
313660
  throw new Error("Policy configuration could not be loaded");
313669
313661
  }
313670
313662
  }
313663
+ function normalizePolicyConfig(policy) {
313664
+ return {
313665
+ ...policy,
313666
+ withdraw: {
313667
+ ...policy.withdraw,
313668
+ rule: policy.withdraw.rule.map((rule) => ({
313669
+ ...rule,
313670
+ exchange: rule.exchange.trim().toUpperCase(),
313671
+ network: rule.network.trim().toUpperCase(),
313672
+ whitelist: rule.whitelist.map((address) => address.trim().toLowerCase())
313673
+ }))
313674
+ },
313675
+ order: {
313676
+ ...policy.order,
313677
+ rule: {
313678
+ ...policy.order.rule,
313679
+ limits: policy.order.rule.limits ?? []
313680
+ }
313681
+ }
313682
+ };
313683
+ }
313671
313684
  function getWithdrawRulePriority(rule, exchange, network) {
313672
313685
  const exchangeMatch = rule.exchange === exchange || rule.exchange === "*";
313673
313686
  const networkMatch = rule.network === network || rule.network === "*";
@@ -313686,15 +313699,16 @@ function getWithdrawRulePriority(rule, exchange, network) {
313686
313699
  return 1;
313687
313700
  }
313688
313701
  function validateWithdraw(policy, exchange, network, recipientAddress, _amount, _ticker) {
313702
+ const normalizedPolicy = normalizePolicyConfig(policy);
313689
313703
  const exchangeNorm = exchange.trim().toUpperCase();
313690
313704
  const networkNorm = network.trim().toUpperCase();
313691
- const matchingRules = policy.withdraw.rule.map((rule) => ({
313705
+ const matchingRules = normalizedPolicy.withdraw.rule.map((rule) => ({
313692
313706
  rule,
313693
313707
  priority: getWithdrawRulePriority(rule, exchangeNorm, networkNorm)
313694
313708
  })).filter((r) => r.priority > 0).sort((a, b2) => b2.priority - a.priority);
313695
313709
  const withdrawRule = matchingRules[0]?.rule;
313696
313710
  if (!withdrawRule) {
313697
- const allowedPairs = policy.withdraw.rule.map((r) => `${r.exchange}:${r.network}`);
313711
+ const allowedPairs = normalizedPolicy.withdraw.rule.map((r) => `${r.exchange}:${r.network}`);
313698
313712
  return {
313699
313713
  valid: false,
313700
313714
  error: `Network ${networkNorm} is not allowed for exchange ${exchangeNorm}. Allowed exchange/network pairs: ${allowedPairs.join(", ")}`
@@ -314303,10 +314317,77 @@ var descriptor = {
314303
314317
  };
314304
314318
  var node_descriptor_default = descriptor;
314305
314319
 
314320
+ // src/utils/payload-reader.ts
314321
+ class PayloadValidationError extends Error {
314322
+ constructor(message) {
314323
+ super(`ValidationError: ${message}`);
314324
+ this.name = "PayloadValidationError";
314325
+ }
314326
+ }
314327
+ function parseNumberField(value, fieldName) {
314328
+ if (typeof value !== "string" && typeof value !== "number") {
314329
+ throw new PayloadValidationError(`'${fieldName}' must be a numeric string`);
314330
+ }
314331
+ const parsed = typeof value === "number" ? value : Number(value.trim());
314332
+ if (!Number.isFinite(parsed)) {
314333
+ throw new PayloadValidationError(`'${fieldName}' must be a valid number`);
314334
+ }
314335
+ return parsed;
314336
+ }
314337
+ function parseJsonField(value, fieldName, expected) {
314338
+ const parsedValue = typeof value === "string" ? (() => {
314339
+ try {
314340
+ return JSON.parse(value);
314341
+ } catch {
314342
+ throw new PayloadValidationError(`Failed to parse JSON for '${fieldName}'`);
314343
+ }
314344
+ })() : value;
314345
+ if (expected === "jsonObject") {
314346
+ if (typeof parsedValue !== "object" || parsedValue === null || Array.isArray(parsedValue)) {
314347
+ throw new PayloadValidationError(`'${fieldName}' must be a JSON object`);
314348
+ }
314349
+ return parsedValue;
314350
+ }
314351
+ if (!Array.isArray(parsedValue)) {
314352
+ throw new PayloadValidationError(`'${fieldName}' must be a JSON array`);
314353
+ }
314354
+ return parsedValue;
314355
+ }
314356
+
314357
+ class PayloadReader {
314358
+ payload;
314359
+ constructor(payload) {
314360
+ this.payload = { ...payload ?? {} };
314361
+ }
314362
+ read(shape) {
314363
+ const prepared = { ...this.payload };
314364
+ for (const [fieldName, spec] of Object.entries(shape)) {
314365
+ const rawValue = prepared[fieldName];
314366
+ const isMissing = rawValue === undefined || rawValue === null;
314367
+ if (isMissing) {
314368
+ if (spec.required) {
314369
+ throw new PayloadValidationError(`'${fieldName}' is required`);
314370
+ }
314371
+ continue;
314372
+ }
314373
+ if (spec.type === "number") {
314374
+ prepared[fieldName] = parseNumberField(rawValue, fieldName);
314375
+ continue;
314376
+ }
314377
+ prepared[fieldName] = parseJsonField(rawValue, fieldName, spec.type);
314378
+ }
314379
+ return prepared;
314380
+ }
314381
+ }
314382
+
314306
314383
  // src/server.ts
314307
314384
  var packageDef = protoLoader.fromJSON(node_descriptor_default);
314308
314385
  var grpcObj = grpc.loadPackageDefinition(packageDef);
314309
314386
  var cexNode = grpcObj.cex_broker;
314387
+ function preparePayload(rawPayload, shape) {
314388
+ const payload = new PayloadReader(rawPayload);
314389
+ return payload.read(shape);
314390
+ }
314310
314391
  function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics) {
314311
314392
  const server = new grpc.Server;
314312
314393
  server.addService(cexNode.cex_service.service, {
@@ -314383,9 +314464,25 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314383
314464
  amount: import_joi2.default.number().positive().required(),
314384
314465
  transactionHash: import_joi2.default.string().required(),
314385
314466
  since: import_joi2.default.number(),
314386
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
314467
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
314387
314468
  });
314388
- const { value, error } = transactionSchema.validate(call.request.payload ?? {});
314469
+ let payload;
314470
+ try {
314471
+ payload = preparePayload(call.request.payload, {
314472
+ amount: { type: "number", required: true },
314473
+ since: { type: "number" },
314474
+ params: { type: "jsonObject" }
314475
+ });
314476
+ } catch (error2) {
314477
+ if (error2 instanceof PayloadValidationError) {
314478
+ return wrappedCallback({
314479
+ code: grpc.status.INVALID_ARGUMENT,
314480
+ message: error2.message
314481
+ }, null);
314482
+ }
314483
+ throw error2;
314484
+ }
314485
+ const { value, error } = transactionSchema.validate(payload);
314389
314486
  if (error) {
314390
314487
  return wrappedCallback({
314391
314488
  code: grpc.status.INVALID_ARGUMENT,
@@ -314494,20 +314591,20 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314494
314591
  args: import_joi2.default.array().items(import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number(), import_joi2.default.boolean(), import_joi2.default.object(), import_joi2.default.array())).default([]),
314495
314592
  params: import_joi2.default.object().default({})
314496
314593
  });
314497
- const rawPayload = call.request.payload ?? {};
314498
- const preparedPayload = { ...rawPayload };
314594
+ let preparedPayload;
314499
314595
  try {
314500
- if (typeof preparedPayload.args === "string") {
314501
- preparedPayload.args = JSON.parse(preparedPayload.args);
314502
- }
314503
- if (typeof preparedPayload.params === "string") {
314504
- preparedPayload.params = JSON.parse(preparedPayload.params);
314596
+ preparedPayload = preparePayload(call.request.payload, {
314597
+ args: { type: "jsonArray" },
314598
+ params: { type: "jsonObject" }
314599
+ });
314600
+ } catch (error) {
314601
+ if (error instanceof PayloadValidationError) {
314602
+ return wrappedCallback({
314603
+ code: grpc.status.INVALID_ARGUMENT,
314604
+ message: error.message
314605
+ }, null);
314505
314606
  }
314506
- } catch {
314507
- return wrappedCallback({
314508
- code: grpc.status.INVALID_ARGUMENT,
314509
- message: "ValidationError: Failed to parse JSON for 'args' or 'params'"
314510
- }, null);
314607
+ throw error;
314511
314608
  }
314512
314609
  const { value: callValue, error: callError } = callSchema.validate(preparedPayload);
314513
314610
  if (callError) {
@@ -314564,11 +314661,24 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314564
314661
  const {
314565
314662
  value: fetchDepositAddresses,
314566
314663
  error: errorFetchDepositAddresses
314567
- } = fetchDepositAddressesSchema.validate(call.request.payload ?? {});
314664
+ } = (() => {
314665
+ try {
314666
+ const payload = preparePayload(call.request.payload, {
314667
+ params: { type: "jsonObject" }
314668
+ });
314669
+ return fetchDepositAddressesSchema.validate(payload);
314670
+ } catch (error) {
314671
+ if (error instanceof PayloadValidationError) {
314672
+ return { value: null, error };
314673
+ }
314674
+ throw error;
314675
+ }
314676
+ })();
314568
314677
  if (errorFetchDepositAddresses) {
314678
+ const message = errorFetchDepositAddresses instanceof PayloadValidationError ? errorFetchDepositAddresses.message : `ValidationError: ${errorFetchDepositAddresses.message}`;
314569
314679
  return wrappedCallback({
314570
314680
  code: grpc.status.INVALID_ARGUMENT,
314571
- message: `ValidationError: ${errorFetchDepositAddresses?.message}`
314681
+ message
314572
314682
  }, null);
314573
314683
  }
314574
314684
  try {
@@ -314612,16 +314722,31 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314612
314722
  recipientAddress: import_joi2.default.string().required(),
314613
314723
  amount: import_joi2.default.number().positive().required(),
314614
314724
  chain: import_joi2.default.string().required(),
314615
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
314725
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
314616
314726
  });
314617
- const { value: transferValue, error: transferError } = transferSchema.validate(call.request.payload ?? {});
314727
+ let payload;
314728
+ try {
314729
+ payload = preparePayload(call.request.payload, {
314730
+ amount: { type: "number", required: true },
314731
+ params: { type: "jsonObject" }
314732
+ });
314733
+ } catch (error) {
314734
+ if (error instanceof PayloadValidationError) {
314735
+ return wrappedCallback({
314736
+ code: grpc.status.INVALID_ARGUMENT,
314737
+ message: error.message
314738
+ }, null);
314739
+ }
314740
+ throw error;
314741
+ }
314742
+ const { value: transferValue, error: transferError } = transferSchema.validate(payload);
314618
314743
  if (transferError) {
314619
314744
  return wrappedCallback({
314620
314745
  code: grpc.status.INVALID_ARGUMENT,
314621
314746
  message: `ValidationError:" ${transferError?.message}`
314622
314747
  }, null);
314623
314748
  }
314624
- const transferValidation = validateWithdraw(policy, cex3, transferValue.chain, transferValue.recipientAddress, Number(transferValue.amount), symbol);
314749
+ const transferValidation = validateWithdraw(policy, cex3, transferValue.chain, transferValue.recipientAddress, transferValue.amount, symbol);
314625
314750
  if (!transferValidation.valid) {
314626
314751
  return wrappedCallback({
314627
314752
  code: grpc.status.PERMISSION_DENIED,
@@ -314637,7 +314762,10 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314637
314762
  message: `Broker ${cex3} doesnt support this ${transferValue.chain} for token ${symbol}`
314638
314763
  }, null);
314639
314764
  }
314640
- const transaction = await broker.withdraw(symbol, Number(transferValue.amount), transferValue.recipientAddress, undefined, { network: transferValue.chain });
314765
+ const transaction = await broker.withdraw(symbol, transferValue.amount, transferValue.recipientAddress, undefined, {
314766
+ ...transferValue.params ?? {},
314767
+ network: transferValue.chain
314768
+ });
314641
314769
  log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
314642
314770
  wrappedCallback(null, {
314643
314771
  proof: verityProof,
@@ -314659,9 +314787,25 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314659
314787
  fromToken: import_joi2.default.string().required(),
314660
314788
  toToken: import_joi2.default.string().required(),
314661
314789
  price: import_joi2.default.number().positive().required(),
314662
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
314790
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
314663
314791
  });
314664
- const { value: orderValue, error: orderError } = createOrderSchema.validate(call.request.payload ?? {});
314792
+ let payload;
314793
+ try {
314794
+ payload = preparePayload(call.request.payload, {
314795
+ amount: { type: "number", required: true },
314796
+ price: { type: "number", required: true },
314797
+ params: { type: "jsonObject" }
314798
+ });
314799
+ } catch (error) {
314800
+ if (error instanceof PayloadValidationError) {
314801
+ return wrappedCallback({
314802
+ code: grpc.status.INVALID_ARGUMENT,
314803
+ message: error.message
314804
+ }, null);
314805
+ }
314806
+ throw error;
314807
+ }
314808
+ const { value: orderValue, error: orderError } = createOrderSchema.validate(payload);
314665
314809
  if (orderError) {
314666
314810
  return wrappedCallback({
314667
314811
  code: grpc.status.INVALID_ARGUMENT,
@@ -314675,14 +314819,14 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314675
314819
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
314676
314820
  }, null);
314677
314821
  }
314678
- const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, Number(orderValue.amount), Number(orderValue.price));
314822
+ const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price);
314679
314823
  if (!resolution.valid || !resolution.symbol || !resolution.side) {
314680
314824
  return wrappedCallback({
314681
314825
  code: grpc.status.INVALID_ARGUMENT,
314682
314826
  message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
314683
314827
  }, null);
314684
314828
  }
314685
- const order = await broker.createOrder(resolution.symbol, orderValue.orderType, resolution.side, Number(resolution.amountBase ?? orderValue.amount), Number(orderValue.price), orderValue.params ?? {});
314829
+ const order = await broker.createOrder(resolution.symbol, orderValue.orderType, resolution.side, resolution.amountBase ?? orderValue.amount, orderValue.price, orderValue.params ?? {});
314686
314830
  wrappedCallback(null, { result: JSON.stringify({ ...order }) });
314687
314831
  } catch (error) {
314688
314832
  log.error({ error });
@@ -314696,9 +314840,23 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314696
314840
  case Action.GetOrderDetails: {
314697
314841
  const getOrderSchema = import_joi2.default.object({
314698
314842
  orderId: import_joi2.default.string().required(),
314699
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
314843
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
314700
314844
  });
314701
- const { value: getOrderValue, error: getOrderError } = getOrderSchema.validate(call.request.payload ?? {});
314845
+ let payload;
314846
+ try {
314847
+ payload = preparePayload(call.request.payload, {
314848
+ params: { type: "jsonObject" }
314849
+ });
314850
+ } catch (error) {
314851
+ if (error instanceof PayloadValidationError) {
314852
+ return wrappedCallback({
314853
+ code: grpc.status.INVALID_ARGUMENT,
314854
+ message: error.message
314855
+ }, null);
314856
+ }
314857
+ throw error;
314858
+ }
314859
+ const { value: getOrderValue, error: getOrderError } = getOrderSchema.validate(payload);
314702
314860
  if (getOrderError) {
314703
314861
  return wrappedCallback({
314704
314862
  code: grpc.status.INVALID_ARGUMENT,
@@ -314736,9 +314894,23 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
314736
314894
  case Action.CancelOrder: {
314737
314895
  const cancelOrderSchema = import_joi2.default.object({
314738
314896
  orderId: import_joi2.default.string().required(),
314739
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
314897
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
314740
314898
  });
314741
- const { value: cancelOrderValue, error: cancelOrderError } = cancelOrderSchema.validate(call.request.payload ?? {});
314899
+ let payload;
314900
+ try {
314901
+ payload = preparePayload(call.request.payload, {
314902
+ params: { type: "jsonObject" }
314903
+ });
314904
+ } catch (error) {
314905
+ if (error instanceof PayloadValidationError) {
314906
+ return wrappedCallback({
314907
+ code: grpc.status.INVALID_ARGUMENT,
314908
+ message: error.message
314909
+ }, null);
314910
+ }
314911
+ throw error;
314912
+ }
314913
+ const { value: cancelOrderValue, error: cancelOrderError } = cancelOrderSchema.validate(payload);
314742
314914
  if (cancelOrderError) {
314743
314915
  return wrappedCallback({
314744
314916
  code: grpc.status.INVALID_ARGUMENT,
@@ -315272,7 +315444,7 @@ class CEXBroker {
315272
315444
  this.policy = loadPolicy(policies);
315273
315445
  this.port = config?.port ?? 8086;
315274
315446
  } else {
315275
- this.policy = policies;
315447
+ this.policy = normalizePolicyConfig(policies);
315276
315448
  }
315277
315449
  if (this.#policyFilePath) {
315278
315450
  this.watchPolicyFile(this.#policyFilePath);
@@ -31,6 +31,7 @@ export declare function selectBroker(brokers: {
31
31
  * Loads and validates policy configuration
32
32
  */
33
33
  export declare function loadPolicy(policyPath: string): PolicyConfig;
34
+ export declare function normalizePolicyConfig(policy: PolicyConfig): PolicyConfig;
34
35
  export declare function validateWithdraw(policy: PolicyConfig, exchange: string, network: string, recipientAddress: string, _amount: number, _ticker: string): {
35
36
  valid: boolean;
36
37
  error?: string;