@keetanetwork/anchor 0.0.37 → 0.0.38

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.
@@ -1,13 +1,14 @@
1
1
  import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
2
2
  import * as KeetaAnchorHTTPServer from '../../lib/http-server/index.js';
3
3
  import { KeetaNet } from '../../client/index.js';
4
- import { KeetaAnchorUserError } from '../../lib/error.js';
5
- import { assertConversionInputCanonicalJSON, assertConversionQuoteJSON, Errors } from './common.js';
4
+ import { KeetaAnchorError, KeetaAnchorUserError } from '../../lib/error.js';
5
+ import { assertConversionInputCanonicalJSON, assertKeetaFXAnchorClientCreateExchangeRequestJSON, Errors } from './common.js';
6
6
  import * as Signing from '../../lib/utils/signing.js';
7
7
  import { KeetaAnchorQueueRunner, KeetaAnchorQueueStorageDriverMemory } from '../../lib/queue/index.js';
8
8
  import { KeetaAnchorQueuePipelineAdvanced } from '../../lib/queue/pipeline.js';
9
9
  import { assertNever } from '../../lib/utils/never.js';
10
10
  import * as typia from 'typia';
11
+ import { assertExchangeBlockParameters } from './util.js';
11
12
  /**
12
13
  * Enable additional runtime "paranoid" checks in the FX server.
13
14
  *
@@ -75,6 +76,31 @@ async function requestToAccounts(config, request) {
75
76
  signer: signer
76
77
  });
77
78
  }
79
+ export function toValidateQuoteInput(input) {
80
+ const ret = {
81
+ account: KeetaNet.lib.Account.toAccount(input.account),
82
+ convertedAmount: BigInt(input.convertedAmount),
83
+ cost: {
84
+ amount: BigInt(input.cost.amount),
85
+ token: KeetaNet.lib.Account.toAccount(input.cost.token)
86
+ }
87
+ };
88
+ if ('convertedAmountBound' in input && input.convertedAmountBound !== undefined) {
89
+ ret.convertedAmountBound = BigInt(input.convertedAmountBound);
90
+ }
91
+ if ('signed' in input && input.signed !== undefined) {
92
+ ret.signed = input.signed;
93
+ }
94
+ if ('request' in input && input.request !== undefined) {
95
+ ret.request = {
96
+ from: KeetaNet.lib.Account.toAccount(input.request.from),
97
+ to: KeetaNet.lib.Account.toAccount(input.request.to),
98
+ amount: BigInt(input.request.amount),
99
+ affinity: input.request.affinity
100
+ };
101
+ }
102
+ return (ret);
103
+ }
78
104
  class KeetaFXAnchorQueuePipelineStage1 extends KeetaAnchorQueueRunner {
79
105
  serverConfig;
80
106
  sequential = true;
@@ -108,12 +134,17 @@ class KeetaFXAnchorQueuePipelineStage1 extends KeetaAnchorQueueRunner {
108
134
  */
109
135
  async processor(entry) {
110
136
  const { block, expected, request } = entry.request;
111
- const expectedToken = expected.token;
112
- const expectedAmount = expected.amount;
113
137
  const config = this.serverConfig;
114
138
  let userClient;
115
139
  if (KeetaNet.UserClient.isInstance(config.client)) {
116
140
  userClient = config.client;
141
+ if (!(userClient.account.comparePublicKey(entry.request.account))) {
142
+ return ({
143
+ status: 'failed_permanently',
144
+ output: null,
145
+ error: `Mismatched account for FX request with configured UserClient account`
146
+ });
147
+ }
117
148
  }
118
149
  else {
119
150
  const { signer, account: checkAccount } = await requestToAccounts(config, request);
@@ -206,7 +237,10 @@ class KeetaFXAnchorQueuePipelineStage1 extends KeetaAnchorQueueRunner {
206
237
  }
207
238
  }
208
239
  /* We are clear to attempt the swap now */
209
- const swapBlocks = await userClient.acceptSwapRequest({ block, expected: { token: expectedToken, amount: BigInt(expectedAmount) } });
240
+ const builder = userClient.initBuilder();
241
+ builder.send(block.account, expected.send.amount, expected.send.token);
242
+ const sendBlock = await builder.computeBlocks();
243
+ const swapBlocks = [...sendBlock.blocks, block];
210
244
  const publishOptions = {};
211
245
  if (userClient.config.generateFeeBlock !== undefined) {
212
246
  publishOptions.generateFeeBlock = userClient.config.generateFeeBlock;
@@ -233,8 +267,14 @@ class KeetaFXAnchorQueuePipelineStage1 extends KeetaAnchorQueueRunner {
233
267
  block: Buffer.from(request.block.toBytes()).toString('base64'),
234
268
  request: request.request,
235
269
  expected: {
236
- token: request.expected.token.publicKeyString.get(),
237
- amount: request.expected.amount.toString()
270
+ receive: {
271
+ token: request.expected.receive.token.publicKeyString.get(),
272
+ amount: request.expected.receive.amount.toString()
273
+ },
274
+ send: {
275
+ token: request.expected.send.token.publicKeyString.get(),
276
+ amount: request.expected.send.amount.toString()
277
+ }
238
278
  }
239
279
  };
240
280
  return (retval);
@@ -254,8 +294,14 @@ class KeetaFXAnchorQueuePipelineStage1 extends KeetaAnchorQueueRunner {
254
294
  block: new KeetaNet.lib.Block(reqJSON.block),
255
295
  request: reqJSON.request,
256
296
  expected: {
257
- token: KeetaNet.lib.Account.fromPublicKeyString(reqJSON.expected.token).assertKeyType(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN),
258
- amount: BigInt(reqJSON.expected.amount)
297
+ receive: {
298
+ token: KeetaNet.lib.Account.fromPublicKeyString(reqJSON.expected.receive.token).assertKeyType(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN),
299
+ amount: BigInt(reqJSON.expected.receive.amount)
300
+ },
301
+ send: {
302
+ token: KeetaNet.lib.Account.fromPublicKeyString(reqJSON.expected.send.token).assertKeyType(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN),
303
+ amount: BigInt(reqJSON.expected.send.amount)
304
+ }
259
305
  }
260
306
  };
261
307
  return (retval);
@@ -365,6 +411,7 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
365
411
  quoteSigner;
366
412
  fx;
367
413
  pipeline;
414
+ quoteConfiguration;
368
415
  pipelineAutoRunInterval = null;
369
416
  constructor(config) {
370
417
  super(config);
@@ -372,6 +419,7 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
372
419
  this.client = config.client;
373
420
  this.fx = config.fx;
374
421
  this.quoteSigner = config.quoteSigner;
422
+ this.quoteConfiguration = config.quoteConfiguration ?? { requiresQuote: true };
375
423
  /*
376
424
  * Setup the accounts
377
425
  */
@@ -494,23 +542,56 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
494
542
  }
495
543
  const conversion = assertConversionInputCanonicalJSON(postData.request);
496
544
  const rateAndFee = await config.fx.getConversionRateAndFee(conversion);
545
+ let requiresQuoteBody;
546
+ if (instance.quoteConfiguration.requiresQuote) {
547
+ requiresQuoteBody = { requiresQuote: true };
548
+ }
549
+ else {
550
+ if (rateAndFee.convertedAmountBound === undefined) {
551
+ instance.logger.warn('POST /api/getEstimate', 'FX configuration indicates quotes are not required, but "convertedAmountBound" was not provided in the rate and fee response');
552
+ }
553
+ else {
554
+ if (conversion.affinity === 'to' && (BigInt(conversion.amount) > rateAndFee.convertedAmountBound)) {
555
+ throw (new KeetaAnchorError('Affinity is to, but bound is less than estimated sent amount'));
556
+ }
557
+ if (conversion.affinity === 'from' && (BigInt(conversion.amount) < rateAndFee.convertedAmountBound)) {
558
+ throw (new KeetaAnchorError('Affinity is from, but bound is greater than estimated received amount'));
559
+ }
560
+ }
561
+ requiresQuoteBody = { requiresQuote: false, account: rateAndFee.account };
562
+ }
497
563
  const estimateResponse = {
498
564
  ok: true,
499
565
  estimate: KeetaNet.lib.Utils.Conversion.toJSONSerializable({
500
566
  request: conversion,
501
567
  convertedAmount: rateAndFee.convertedAmount,
568
+ convertedAmountBound: rateAndFee.convertedAmountBound,
502
569
  expectedCost: {
503
570
  min: rateAndFee.cost.amount,
504
571
  max: rateAndFee.cost.amount,
505
572
  token: rateAndFee.cost.token
506
- }
573
+ },
574
+ ...requiresQuoteBody
507
575
  })
508
576
  };
509
577
  return ({
510
578
  output: JSON.stringify(estimateResponse)
511
579
  });
512
580
  };
581
+ async function getUnsignedQuoteData(conversion) {
582
+ const rateAndFee = await config.fx.getConversionRateAndFee(conversion);
583
+ if (PARANOID) {
584
+ const quoteAccount = rateAndFee.account;
585
+ if (!instance.accounts.has(quoteAccount)) {
586
+ throw (new Error('"getConversionRateAndFee" returned an account not configured for this server'));
587
+ }
588
+ }
589
+ return (rateAndFee);
590
+ }
513
591
  routes['POST /api/getQuote'] = async function (_ignore_params, postData) {
592
+ if (!instance.quoteConfiguration.requiresQuote && !instance.quoteConfiguration.issueQuotes) {
593
+ throw (new Errors.QuoteIssuanceDisabled());
594
+ }
514
595
  if (!postData || typeof postData !== 'object') {
515
596
  throw (new Error('No POST data provided'));
516
597
  }
@@ -518,17 +599,14 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
518
599
  throw (new Error('POST data missing request'));
519
600
  }
520
601
  const conversion = assertConversionInputCanonicalJSON(postData.request);
521
- const rateAndFee = await config.fx.getConversionRateAndFee(conversion);
522
- if (PARANOID) {
523
- const quoteAccount = rateAndFee.account;
524
- if (!instance.accounts.has(quoteAccount)) {
525
- throw (new Error('"getConversionRateAndFee" returned an account not configured for this server'));
526
- }
527
- }
602
+ const rateAndFee = await getUnsignedQuoteData(conversion);
528
603
  const unsignedQuote = KeetaNet.lib.Utils.Conversion.toJSONSerializable({
529
604
  request: conversion,
530
605
  ...rateAndFee
531
606
  });
607
+ if (config.quoteSigner === null) {
608
+ throw (new Error('Quote signer not configured, this is required when issuing quotes'));
609
+ }
532
610
  const signedQuote = await generateSignedQuote(config.quoteSigner, unsignedQuote);
533
611
  const quoteResponse = {
534
612
  ok: true,
@@ -545,64 +623,124 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
545
623
  if (!('request' in postData)) {
546
624
  throw (new Error('POST data missing request'));
547
625
  }
548
- const request = postData.request;
549
- if (!request || typeof request !== 'object') {
550
- throw (new Error('Request is not an object'));
551
- }
552
- if (!('quote' in request)) {
553
- throw (new Error('Quote is missing from request'));
554
- }
626
+ const request = assertKeetaFXAnchorClientCreateExchangeRequestJSON(postData.request);
555
627
  if (!('block' in request) || typeof request.block !== 'string') {
556
628
  throw (new Error('Block was not provided in exchange request'));
557
629
  }
558
- const quote = assertConversionQuoteJSON(request.quote);
559
- const isValidQuote = await verifySignedData(config.quoteSigner, quote);
560
- if (!isValidQuote) {
561
- throw (new Error('Invalid quote signature'));
562
- }
563
- /* Validate the quote using the optional callback */
564
- if (config.fx.validateQuote !== undefined) {
565
- const isAcceptable = await config.fx.validateQuote(quote);
566
- if (!isAcceptable) {
630
+ const block = new KeetaNet.lib.Block(request.block);
631
+ let quoteInput;
632
+ let conversionInput;
633
+ let shouldValidateQuote;
634
+ let liquidityAccount;
635
+ if ('quote' in request && 'estimate' in request && request.quote && request.estimate) {
636
+ throw (new Error('Request cannot contain both quote and estimate'));
637
+ }
638
+ else if ('quote' in request && request.quote) {
639
+ shouldValidateQuote = true;
640
+ quoteInput = request.quote;
641
+ conversionInput = quoteInput.request;
642
+ const isValidQuote = await (async () => {
643
+ if (config.quoteSigner === null) {
644
+ return (false);
645
+ }
646
+ return (await verifySignedData(config.quoteSigner, quoteInput));
647
+ })();
648
+ if (!isValidQuote) {
567
649
  throw (new Errors.QuoteValidationFailed());
568
650
  }
651
+ liquidityAccount = quoteInput.account;
569
652
  }
570
- const block = new KeetaNet.lib.Block(request.block);
571
- /* Get Expected Amount and Token to Verify Swap */
572
- const expectedToken = KeetaNet.lib.Account.fromPublicKeyString(quote.request.from);
573
- let expectedAmount = quote.request.affinity === 'from' ? BigInt(quote.request.amount) : BigInt(quote.convertedAmount);
574
- /* If cost is required verify the amounts and token. */
575
- if (BigInt(quote.cost.amount) > 0) {
576
- /* If swap token matches the cost token the add the amount since they should be combined in one block and will be checked in `acceptSwapRequest` */
577
- if (expectedToken.comparePublicKey(quote.cost.token)) {
578
- expectedAmount += BigInt(quote.cost.amount);
579
- /* If token is different then check block operations for matching amount and token */
653
+ else if ('request' in request && request.request) {
654
+ if (instance.quoteConfiguration.requiresQuote) {
655
+ throw (new Errors.QuoteRequired());
656
+ }
657
+ conversionInput = request.request;
658
+ quoteInput = await getUnsignedQuoteData(conversionInput);
659
+ if (instance.quoteConfiguration.validateQuoteBeforeExchange !== undefined) {
660
+ shouldValidateQuote = instance.quoteConfiguration.validateQuoteBeforeExchange;
580
661
  }
581
662
  else {
582
- let requestIncludesCost = false;
583
- for (const operation of block.operations) {
584
- if (operation.type === KeetaNet.lib.Block.OperationType.SEND) {
585
- const recipientMatches = operation.to.comparePublicKey(quote.account);
586
- const tokenMatches = operation.token.comparePublicKey(quote.cost.token);
587
- const amountMatches = operation.amount === BigInt(quote.cost.amount);
588
- if (recipientMatches && tokenMatches && amountMatches) {
589
- requestIncludesCost = true;
590
- }
663
+ shouldValidateQuote = config.fx.validateQuote !== undefined;
664
+ }
665
+ for (const operation of block.operations) {
666
+ if (operation.type === KeetaNet.lib.Block.OperationType.SEND) {
667
+ if (!config.accounts) {
668
+ throw (new Error('No accounts configured for FX server, cannot infer liquidity account from block'));
669
+ }
670
+ if (config.accounts.has(operation.to)) {
671
+ liquidityAccount = operation.to;
672
+ break;
591
673
  }
592
674
  }
593
- if (!requestIncludesCost) {
594
- throw (new Error('Exchange missing required cost'));
595
- }
596
675
  }
676
+ if (!liquidityAccount) {
677
+ throw (new KeetaAnchorUserError('Could not determine liquidity account from exchange block'));
678
+ }
679
+ }
680
+ else {
681
+ throw (new Error('Either quote or request must be provided (but not both) in exchange request'));
682
+ }
683
+ const parsedQuote = toValidateQuoteInput(quoteInput);
684
+ /* Validate the quote using the optional callback */
685
+ if (config.fx.validateQuote !== undefined && shouldValidateQuote) {
686
+ const isAcceptable = await config.fx.validateQuote(parsedQuote);
687
+ if (!isAcceptable) {
688
+ throw (new Errors.QuoteValidationFailed());
689
+ }
690
+ }
691
+ let expectedSendAmount;
692
+ let expectedReceiveAmount;
693
+ if (conversionInput.affinity === 'to') {
694
+ expectedSendAmount = BigInt(conversionInput.amount);
695
+ expectedReceiveAmount = parsedQuote.convertedAmount;
696
+ }
697
+ else {
698
+ expectedSendAmount = parsedQuote.convertedAmount;
699
+ expectedReceiveAmount = BigInt(conversionInput.amount);
700
+ }
701
+ const liquidityAccountInstance = KeetaNet.lib.Account.toAccount(liquidityAccount);
702
+ const userSendsMinimum = { [conversionInput.from]: expectedReceiveAmount };
703
+ const userWillReceiveMaximum = { [conversionInput.to]: expectedSendAmount };
704
+ if (parsedQuote.cost.amount > 0) {
705
+ const feeTokenPub = parsedQuote.cost.token.publicKeyString.get();
706
+ if (!userSendsMinimum[feeTokenPub]) {
707
+ userSendsMinimum[feeTokenPub] = 0n;
708
+ }
709
+ userSendsMinimum[feeTokenPub] += parsedQuote.cost.amount;
597
710
  }
711
+ let allowedLiquidityAccounts;
712
+ if (config.accounts) {
713
+ allowedLiquidityAccounts = config.accounts;
714
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
715
+ }
716
+ else if (config.account) {
717
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
718
+ allowedLiquidityAccounts = new KeetaNet.lib.Account.Set([config.account]);
719
+ }
720
+ else {
721
+ throw (new Error('config.account or config.accounts must be provided'));
722
+ }
723
+ assertExchangeBlockParameters({
724
+ block: block,
725
+ liquidityAccount: liquidityAccountInstance,
726
+ allowedLiquidityAccounts: allowedLiquidityAccounts,
727
+ userSendsMinimum: userSendsMinimum,
728
+ userWillReceiveMaximum: userWillReceiveMaximum
729
+ });
598
730
  /* Enqueue the exchange request */
599
731
  const exchangeID = await instance.pipeline.add({
600
- account: KeetaNet.lib.Account.fromPublicKeyString(quote.account),
732
+ account: liquidityAccountInstance,
601
733
  block: block,
602
- request: quote.request,
734
+ request: conversionInput,
603
735
  expected: {
604
- token: expectedToken,
605
- amount: BigInt(expectedAmount)
736
+ receive: {
737
+ token: KeetaNet.lib.Account.fromPublicKeyString(conversionInput.from),
738
+ amount: expectedReceiveAmount
739
+ },
740
+ send: {
741
+ token: KeetaNet.lib.Account.fromPublicKeyString(conversionInput.to),
742
+ amount: expectedSendAmount
743
+ }
606
744
  }
607
745
  });
608
746
  const exchangeResponse = {
@@ -709,7 +847,7 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
709
847
  * in a ".generated.ts" file but for simplicity of internal types
710
848
  * we keep them here.
711
849
  */
712
- const assertKeetaFXAnchorQueueStage1RequestJSON = (() => { const _io0 = input => 1 === input.version && "string" === typeof input.account && "string" === typeof input.block && ("object" === typeof input.request && null !== input.request && _io1(input.request)) && ("object" === typeof input.expected && null !== input.expected && _io2(input.expected)); const _io1 = input => "string" === typeof input.from && (RegExp(/^keeta_am(.*)/).test(input.from) || RegExp(/^keeta_an(.*)/).test(input.from) || RegExp(/^keeta_ao(.*)/).test(input.from) || RegExp(/^keeta_ap(.*)/).test(input.from) || RegExp(/^tyblocks_am(.*)/).test(input.from) || RegExp(/^tyblocks_an(.*)/).test(input.from) || RegExp(/^tyblocks_ao(.*)/).test(input.from) || RegExp(/^tyblocks_ap(.*)/).test(input.from)) && ("string" === typeof input.to && (RegExp(/^keeta_am(.*)/).test(input.to) || RegExp(/^keeta_an(.*)/).test(input.to) || RegExp(/^keeta_ao(.*)/).test(input.to) || RegExp(/^keeta_ap(.*)/).test(input.to) || RegExp(/^tyblocks_am(.*)/).test(input.to) || RegExp(/^tyblocks_an(.*)/).test(input.to) || RegExp(/^tyblocks_ao(.*)/).test(input.to) || RegExp(/^tyblocks_ap(.*)/).test(input.to))) && "string" === typeof input.amount && ("from" === input.affinity || "to" === input.affinity); const _io2 = input => "string" === typeof input.token && "string" === typeof input.amount; const _ao0 = (input, _path, _exceptionable = true) => (1 === input.version || __typia_transform__assertGuard._assertGuard(_exceptionable, {
850
+ const assertKeetaFXAnchorQueueStage1RequestJSON = (() => { const _io0 = input => 1 === input.version && "string" === typeof input.account && "string" === typeof input.block && ("object" === typeof input.request && null !== input.request && _io1(input.request)) && ("object" === typeof input.expected && null !== input.expected && _io2(input.expected)); const _io1 = input => "string" === typeof input.from && (RegExp(/^keeta_am(.*)/).test(input.from) || RegExp(/^keeta_an(.*)/).test(input.from) || RegExp(/^keeta_ao(.*)/).test(input.from) || RegExp(/^keeta_ap(.*)/).test(input.from) || RegExp(/^tyblocks_am(.*)/).test(input.from) || RegExp(/^tyblocks_an(.*)/).test(input.from) || RegExp(/^tyblocks_ao(.*)/).test(input.from) || RegExp(/^tyblocks_ap(.*)/).test(input.from)) && ("string" === typeof input.to && (RegExp(/^keeta_am(.*)/).test(input.to) || RegExp(/^keeta_an(.*)/).test(input.to) || RegExp(/^keeta_ao(.*)/).test(input.to) || RegExp(/^keeta_ap(.*)/).test(input.to) || RegExp(/^tyblocks_am(.*)/).test(input.to) || RegExp(/^tyblocks_an(.*)/).test(input.to) || RegExp(/^tyblocks_ao(.*)/).test(input.to) || RegExp(/^tyblocks_ap(.*)/).test(input.to))) && "string" === typeof input.amount && ("from" === input.affinity || "to" === input.affinity); const _io2 = input => "object" === typeof input.receive && null !== input.receive && _io3(input.receive) && ("object" === typeof input.send && null !== input.send && _io4(input.send)); const _io3 = input => "string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) && "string" === typeof input.amount; const _io4 = input => "string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) && "string" === typeof input.amount; const _ao0 = (input, _path, _exceptionable = true) => (1 === input.version || __typia_transform__assertGuard._assertGuard(_exceptionable, {
713
851
  method: "typia.createAssert",
714
852
  path: _path + ".version",
715
853
  expected: "1",
@@ -764,10 +902,40 @@ const assertKeetaFXAnchorQueueStage1RequestJSON = (() => { const _io0 = input =>
764
902
  path: _path + ".affinity",
765
903
  expected: "(\"from\" | \"to\")",
766
904
  value: input.affinity
767
- }, _errorFactory)); const _ao2 = (input, _path, _exceptionable = true) => ("string" === typeof input.token || __typia_transform__assertGuard._assertGuard(_exceptionable, {
905
+ }, _errorFactory)); const _ao2 = (input, _path, _exceptionable = true) => (("object" === typeof input.receive && null !== input.receive || __typia_transform__assertGuard._assertGuard(_exceptionable, {
906
+ method: "typia.createAssert",
907
+ path: _path + ".receive",
908
+ expected: "__type.o2",
909
+ value: input.receive
910
+ }, _errorFactory)) && _ao3(input.receive, _path + ".receive", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
911
+ method: "typia.createAssert",
912
+ path: _path + ".receive",
913
+ expected: "__type.o2",
914
+ value: input.receive
915
+ }, _errorFactory)) && (("object" === typeof input.send && null !== input.send || __typia_transform__assertGuard._assertGuard(_exceptionable, {
916
+ method: "typia.createAssert",
917
+ path: _path + ".send",
918
+ expected: "__type.o3",
919
+ value: input.send
920
+ }, _errorFactory)) && _ao4(input.send, _path + ".send", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
921
+ method: "typia.createAssert",
922
+ path: _path + ".send",
923
+ expected: "__type.o3",
924
+ value: input.send
925
+ }, _errorFactory)); const _ao3 = (input, _path, _exceptionable = true) => ("string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
768
926
  method: "typia.createAssert",
769
927
  path: _path + ".token",
928
+ expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}`)",
929
+ value: input.token
930
+ }, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
931
+ method: "typia.createAssert",
932
+ path: _path + ".amount",
770
933
  expected: "string",
934
+ value: input.amount
935
+ }, _errorFactory)); const _ao4 = (input, _path, _exceptionable = true) => ("string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
936
+ method: "typia.createAssert",
937
+ path: _path + ".token",
938
+ expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}`)",
771
939
  value: input.token
772
940
  }, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
773
941
  method: "typia.createAssert",