@usherlabs/cex-broker 0.2.0 → 0.2.2

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/dist/index.js CHANGED
@@ -272907,20 +272907,33 @@ function loadPolicy(policyPath) {
272907
272907
  if (error) {
272908
272908
  throw new Error(`Policy validation failed: ${error.details.map((d) => d.message).join("; ")}`);
272909
272909
  }
272910
- const normalizedPolicy = value;
272911
- normalizedPolicy.withdraw.rule = normalizedPolicy.withdraw.rule.map((rule) => ({
272912
- ...rule,
272913
- exchange: rule.exchange.trim().toUpperCase(),
272914
- network: rule.network.trim().toUpperCase(),
272915
- whitelist: rule.whitelist.map((a) => a.trim().toLowerCase())
272916
- }));
272917
- normalizedPolicy.order.rule.limits = normalizedPolicy.order.rule.limits ?? [];
272918
- return normalizedPolicy;
272910
+ return normalizePolicyConfig(value);
272919
272911
  } catch (error) {
272920
272912
  console.error("Failed to load policy:", error);
272921
272913
  throw new Error("Policy configuration could not be loaded");
272922
272914
  }
272923
272915
  }
272916
+ function normalizePolicyConfig(policy) {
272917
+ return {
272918
+ ...policy,
272919
+ withdraw: {
272920
+ ...policy.withdraw,
272921
+ rule: policy.withdraw.rule.map((rule) => ({
272922
+ ...rule,
272923
+ exchange: rule.exchange.trim().toUpperCase(),
272924
+ network: rule.network.trim().toUpperCase(),
272925
+ whitelist: rule.whitelist.map((address) => address.trim().toLowerCase())
272926
+ }))
272927
+ },
272928
+ order: {
272929
+ ...policy.order,
272930
+ rule: {
272931
+ ...policy.order.rule,
272932
+ limits: policy.order.rule.limits ?? []
272933
+ }
272934
+ }
272935
+ };
272936
+ }
272924
272937
  function getWithdrawRulePriority(rule, exchange, network) {
272925
272938
  const exchangeMatch = rule.exchange === exchange || rule.exchange === "*";
272926
272939
  const networkMatch = rule.network === network || rule.network === "*";
@@ -272939,15 +272952,16 @@ function getWithdrawRulePriority(rule, exchange, network) {
272939
272952
  return 1;
272940
272953
  }
272941
272954
  function validateWithdraw(policy, exchange, network, recipientAddress, _amount, _ticker) {
272955
+ const normalizedPolicy = normalizePolicyConfig(policy);
272942
272956
  const exchangeNorm = exchange.trim().toUpperCase();
272943
272957
  const networkNorm = network.trim().toUpperCase();
272944
- const matchingRules = policy.withdraw.rule.map((rule) => ({
272958
+ const matchingRules = normalizedPolicy.withdraw.rule.map((rule) => ({
272945
272959
  rule,
272946
272960
  priority: getWithdrawRulePriority(rule, exchangeNorm, networkNorm)
272947
272961
  })).filter((r) => r.priority > 0).sort((a, b2) => b2.priority - a.priority);
272948
272962
  const withdrawRule = matchingRules[0]?.rule;
272949
272963
  if (!withdrawRule) {
272950
- const allowedPairs = policy.withdraw.rule.map((r) => `${r.exchange}:${r.network}`);
272964
+ const allowedPairs = normalizedPolicy.withdraw.rule.map((r) => `${r.exchange}:${r.network}`);
272951
272965
  return {
272952
272966
  valid: false,
272953
272967
  error: `Network ${networkNorm} is not allowed for exchange ${exchangeNorm}. Allowed exchange/network pairs: ${allowedPairs.join(", ")}`
@@ -277256,10 +277270,77 @@ var descriptor = {
277256
277270
  };
277257
277271
  var node_descriptor_default = descriptor;
277258
277272
 
277273
+ // src/utils/payload-reader.ts
277274
+ class PayloadValidationError extends Error {
277275
+ constructor(message) {
277276
+ super(`ValidationError: ${message}`);
277277
+ this.name = "PayloadValidationError";
277278
+ }
277279
+ }
277280
+ function parseNumberField(value, fieldName) {
277281
+ if (typeof value !== "string" && typeof value !== "number") {
277282
+ throw new PayloadValidationError(`'${fieldName}' must be a numeric string`);
277283
+ }
277284
+ const parsed = typeof value === "number" ? value : Number(value.trim());
277285
+ if (!Number.isFinite(parsed)) {
277286
+ throw new PayloadValidationError(`'${fieldName}' must be a valid number`);
277287
+ }
277288
+ return parsed;
277289
+ }
277290
+ function parseJsonField(value, fieldName, expected) {
277291
+ const parsedValue = typeof value === "string" ? (() => {
277292
+ try {
277293
+ return JSON.parse(value);
277294
+ } catch {
277295
+ throw new PayloadValidationError(`Failed to parse JSON for '${fieldName}'`);
277296
+ }
277297
+ })() : value;
277298
+ if (expected === "jsonObject") {
277299
+ if (typeof parsedValue !== "object" || parsedValue === null || Array.isArray(parsedValue)) {
277300
+ throw new PayloadValidationError(`'${fieldName}' must be a JSON object`);
277301
+ }
277302
+ return parsedValue;
277303
+ }
277304
+ if (!Array.isArray(parsedValue)) {
277305
+ throw new PayloadValidationError(`'${fieldName}' must be a JSON array`);
277306
+ }
277307
+ return parsedValue;
277308
+ }
277309
+
277310
+ class PayloadReader {
277311
+ payload;
277312
+ constructor(payload) {
277313
+ this.payload = { ...payload ?? {} };
277314
+ }
277315
+ read(shape) {
277316
+ const prepared = { ...this.payload };
277317
+ for (const [fieldName, spec] of Object.entries(shape)) {
277318
+ const rawValue = prepared[fieldName];
277319
+ const isMissing = rawValue === undefined || rawValue === null;
277320
+ if (isMissing) {
277321
+ if (spec.required) {
277322
+ throw new PayloadValidationError(`'${fieldName}' is required`);
277323
+ }
277324
+ continue;
277325
+ }
277326
+ if (spec.type === "number") {
277327
+ prepared[fieldName] = parseNumberField(rawValue, fieldName);
277328
+ continue;
277329
+ }
277330
+ prepared[fieldName] = parseJsonField(rawValue, fieldName, spec.type);
277331
+ }
277332
+ return prepared;
277333
+ }
277334
+ }
277335
+
277259
277336
  // src/server.ts
277260
277337
  var packageDef = protoLoader.fromJSON(node_descriptor_default);
277261
277338
  var grpcObj = grpc.loadPackageDefinition(packageDef);
277262
277339
  var cexNode = grpcObj.cex_broker;
277340
+ function preparePayload(rawPayload, shape) {
277341
+ const payload = new PayloadReader(rawPayload);
277342
+ return payload.read(shape);
277343
+ }
277263
277344
  function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics) {
277264
277345
  const server = new grpc.Server;
277265
277346
  server.addService(cexNode.cex_service.service, {
@@ -277336,9 +277417,25 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277336
277417
  amount: import_joi2.default.number().positive().required(),
277337
277418
  transactionHash: import_joi2.default.string().required(),
277338
277419
  since: import_joi2.default.number(),
277339
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
277420
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
277340
277421
  });
277341
- const { value, error } = transactionSchema.validate(call.request.payload ?? {});
277422
+ let payload;
277423
+ try {
277424
+ payload = preparePayload(call.request.payload, {
277425
+ amount: { type: "number", required: true },
277426
+ since: { type: "number" },
277427
+ params: { type: "jsonObject" }
277428
+ });
277429
+ } catch (error2) {
277430
+ if (error2 instanceof PayloadValidationError) {
277431
+ return wrappedCallback({
277432
+ code: grpc.status.INVALID_ARGUMENT,
277433
+ message: error2.message
277434
+ }, null);
277435
+ }
277436
+ throw error2;
277437
+ }
277438
+ const { value, error } = transactionSchema.validate(payload);
277342
277439
  if (error) {
277343
277440
  return wrappedCallback({
277344
277441
  code: grpc.status.INVALID_ARGUMENT,
@@ -277399,7 +277496,7 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277399
277496
  }
277400
277497
  case Action.FetchAccountId: {
277401
277498
  try {
277402
- let accountId = await broker.fetchAccountId();
277499
+ const accountId = await broker.fetchAccountId();
277403
277500
  return wrappedCallback(null, {
277404
277501
  proof: verityProof,
277405
277502
  result: JSON.stringify({ accountId })
@@ -277447,20 +277544,20 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277447
277544
  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([]),
277448
277545
  params: import_joi2.default.object().default({})
277449
277546
  });
277450
- const rawPayload = call.request.payload ?? {};
277451
- const preparedPayload = { ...rawPayload };
277547
+ let preparedPayload;
277452
277548
  try {
277453
- if (typeof preparedPayload.args === "string") {
277454
- preparedPayload.args = JSON.parse(preparedPayload.args);
277455
- }
277456
- if (typeof preparedPayload.params === "string") {
277457
- preparedPayload.params = JSON.parse(preparedPayload.params);
277549
+ preparedPayload = preparePayload(call.request.payload, {
277550
+ args: { type: "jsonArray" },
277551
+ params: { type: "jsonObject" }
277552
+ });
277553
+ } catch (error) {
277554
+ if (error instanceof PayloadValidationError) {
277555
+ return wrappedCallback({
277556
+ code: grpc.status.INVALID_ARGUMENT,
277557
+ message: error.message
277558
+ }, null);
277458
277559
  }
277459
- } catch {
277460
- return wrappedCallback({
277461
- code: grpc.status.INVALID_ARGUMENT,
277462
- message: "ValidationError: Failed to parse JSON for 'args' or 'params'"
277463
- }, null);
277560
+ throw error;
277464
277561
  }
277465
277562
  const { value: callValue, error: callError } = callSchema.validate(preparedPayload);
277466
277563
  if (callError) {
@@ -277517,11 +277614,24 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277517
277614
  const {
277518
277615
  value: fetchDepositAddresses,
277519
277616
  error: errorFetchDepositAddresses
277520
- } = fetchDepositAddressesSchema.validate(call.request.payload ?? {});
277617
+ } = (() => {
277618
+ try {
277619
+ const payload = preparePayload(call.request.payload, {
277620
+ params: { type: "jsonObject" }
277621
+ });
277622
+ return fetchDepositAddressesSchema.validate(payload);
277623
+ } catch (error) {
277624
+ if (error instanceof PayloadValidationError) {
277625
+ return { value: null, error };
277626
+ }
277627
+ throw error;
277628
+ }
277629
+ })();
277521
277630
  if (errorFetchDepositAddresses) {
277631
+ const message = errorFetchDepositAddresses instanceof PayloadValidationError ? errorFetchDepositAddresses.message : `ValidationError: ${errorFetchDepositAddresses.message}`;
277522
277632
  return wrappedCallback({
277523
277633
  code: grpc.status.INVALID_ARGUMENT,
277524
- message: `ValidationError: ${errorFetchDepositAddresses?.message}`
277634
+ message
277525
277635
  }, null);
277526
277636
  }
277527
277637
  try {
@@ -277565,16 +277675,31 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277565
277675
  recipientAddress: import_joi2.default.string().required(),
277566
277676
  amount: import_joi2.default.number().positive().required(),
277567
277677
  chain: import_joi2.default.string().required(),
277568
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
277678
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
277569
277679
  });
277570
- const { value: transferValue, error: transferError } = transferSchema.validate(call.request.payload ?? {});
277680
+ let payload;
277681
+ try {
277682
+ payload = preparePayload(call.request.payload, {
277683
+ amount: { type: "number", required: true },
277684
+ params: { type: "jsonObject" }
277685
+ });
277686
+ } catch (error) {
277687
+ if (error instanceof PayloadValidationError) {
277688
+ return wrappedCallback({
277689
+ code: grpc.status.INVALID_ARGUMENT,
277690
+ message: error.message
277691
+ }, null);
277692
+ }
277693
+ throw error;
277694
+ }
277695
+ const { value: transferValue, error: transferError } = transferSchema.validate(payload);
277571
277696
  if (transferError) {
277572
277697
  return wrappedCallback({
277573
277698
  code: grpc.status.INVALID_ARGUMENT,
277574
277699
  message: `ValidationError:" ${transferError?.message}`
277575
277700
  }, null);
277576
277701
  }
277577
- const transferValidation = validateWithdraw(policy, cex3, transferValue.chain, transferValue.recipientAddress, Number(transferValue.amount), symbol);
277702
+ const transferValidation = validateWithdraw(policy, cex3, transferValue.chain, transferValue.recipientAddress, transferValue.amount, symbol);
277578
277703
  if (!transferValidation.valid) {
277579
277704
  return wrappedCallback({
277580
277705
  code: grpc.status.PERMISSION_DENIED,
@@ -277590,7 +277715,7 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277590
277715
  message: `Broker ${cex3} doesnt support this ${transferValue.chain} for token ${symbol}`
277591
277716
  }, null);
277592
277717
  }
277593
- const transaction = await broker.withdraw(symbol, Number(transferValue.amount), transferValue.recipientAddress, undefined, { network: transferValue.chain });
277718
+ const transaction = await broker.withdraw(symbol, transferValue.amount, transferValue.recipientAddress, undefined, { network: transferValue.chain });
277594
277719
  log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
277595
277720
  wrappedCallback(null, {
277596
277721
  proof: verityProof,
@@ -277612,9 +277737,25 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277612
277737
  fromToken: import_joi2.default.string().required(),
277613
277738
  toToken: import_joi2.default.string().required(),
277614
277739
  price: import_joi2.default.number().positive().required(),
277615
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
277740
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
277616
277741
  });
277617
- const { value: orderValue, error: orderError } = createOrderSchema.validate(call.request.payload ?? {});
277742
+ let payload;
277743
+ try {
277744
+ payload = preparePayload(call.request.payload, {
277745
+ amount: { type: "number", required: true },
277746
+ price: { type: "number", required: true },
277747
+ params: { type: "jsonObject" }
277748
+ });
277749
+ } catch (error) {
277750
+ if (error instanceof PayloadValidationError) {
277751
+ return wrappedCallback({
277752
+ code: grpc.status.INVALID_ARGUMENT,
277753
+ message: error.message
277754
+ }, null);
277755
+ }
277756
+ throw error;
277757
+ }
277758
+ const { value: orderValue, error: orderError } = createOrderSchema.validate(payload);
277618
277759
  if (orderError) {
277619
277760
  return wrappedCallback({
277620
277761
  code: grpc.status.INVALID_ARGUMENT,
@@ -277628,14 +277769,14 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277628
277769
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
277629
277770
  }, null);
277630
277771
  }
277631
- const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, Number(orderValue.amount), Number(orderValue.price));
277772
+ const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price);
277632
277773
  if (!resolution.valid || !resolution.symbol || !resolution.side) {
277633
277774
  return wrappedCallback({
277634
277775
  code: grpc.status.INVALID_ARGUMENT,
277635
277776
  message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
277636
277777
  }, null);
277637
277778
  }
277638
- const order = await broker.createOrder(resolution.symbol, orderValue.orderType, resolution.side, Number(resolution.amountBase ?? orderValue.amount), Number(orderValue.price), orderValue.params ?? {});
277779
+ const order = await broker.createOrder(resolution.symbol, orderValue.orderType, resolution.side, resolution.amountBase ?? orderValue.amount, orderValue.price, orderValue.params ?? {});
277639
277780
  wrappedCallback(null, { result: JSON.stringify({ ...order }) });
277640
277781
  } catch (error) {
277641
277782
  log.error({ error });
@@ -277649,9 +277790,23 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277649
277790
  case Action.GetOrderDetails: {
277650
277791
  const getOrderSchema = import_joi2.default.object({
277651
277792
  orderId: import_joi2.default.string().required(),
277652
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
277793
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
277653
277794
  });
277654
- const { value: getOrderValue, error: getOrderError } = getOrderSchema.validate(call.request.payload ?? {});
277795
+ let payload;
277796
+ try {
277797
+ payload = preparePayload(call.request.payload, {
277798
+ params: { type: "jsonObject" }
277799
+ });
277800
+ } catch (error) {
277801
+ if (error instanceof PayloadValidationError) {
277802
+ return wrappedCallback({
277803
+ code: grpc.status.INVALID_ARGUMENT,
277804
+ message: error.message
277805
+ }, null);
277806
+ }
277807
+ throw error;
277808
+ }
277809
+ const { value: getOrderValue, error: getOrderError } = getOrderSchema.validate(payload);
277655
277810
  if (getOrderError) {
277656
277811
  return wrappedCallback({
277657
277812
  code: grpc.status.INVALID_ARGUMENT,
@@ -277689,9 +277844,23 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277689
277844
  case Action.CancelOrder: {
277690
277845
  const cancelOrderSchema = import_joi2.default.object({
277691
277846
  orderId: import_joi2.default.string().required(),
277692
- params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.string()).default({})
277847
+ params: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.alternatives(import_joi2.default.string(), import_joi2.default.number())).default({})
277693
277848
  });
277694
- const { value: cancelOrderValue, error: cancelOrderError } = cancelOrderSchema.validate(call.request.payload ?? {});
277849
+ let payload;
277850
+ try {
277851
+ payload = preparePayload(call.request.payload, {
277852
+ params: { type: "jsonObject" }
277853
+ });
277854
+ } catch (error) {
277855
+ if (error instanceof PayloadValidationError) {
277856
+ return wrappedCallback({
277857
+ code: grpc.status.INVALID_ARGUMENT,
277858
+ message: error.message
277859
+ }, null);
277860
+ }
277861
+ throw error;
277862
+ }
277863
+ const { value: cancelOrderValue, error: cancelOrderError } = cancelOrderSchema.validate(payload);
277695
277864
  if (cancelOrderError) {
277696
277865
  return wrappedCallback({
277697
277866
  code: grpc.status.INVALID_ARGUMENT,
@@ -277808,7 +277977,7 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
277808
277977
  });
277809
277978
  const subscriptionTypeName = (() => {
277810
277979
  for (const [key, value] of Object.entries(SubscriptionType)) {
277811
- if (value === subscriptionType && isNaN(Number(key))) {
277980
+ if (value === subscriptionType && Number.isNaN(Number(key))) {
277812
277981
  return key;
277813
277982
  }
277814
277983
  }
@@ -278225,7 +278394,7 @@ class CEXBroker {
278225
278394
  this.policy = loadPolicy(policies);
278226
278395
  this.port = config?.port ?? 8086;
278227
278396
  } else {
278228
- this.policy = policies;
278397
+ this.policy = normalizePolicyConfig(policies);
278229
278398
  }
278230
278399
  if (this.#policyFilePath) {
278231
278400
  this.watchPolicyFile(this.#policyFilePath);
@@ -278296,4 +278465,4 @@ export {
278296
278465
  CEXBroker as default
278297
278466
  };
278298
278467
 
278299
- //# debugId=D39658BF795B50A864756E2164756E21
278468
+ //# debugId=23465A57A1AEB5E864756E2164756E21