Advertisement
tenequm

Untitled

Sep 13th, 2024
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import { Command, Option } from 'nest-commander';
  2. import {
  3.   getUtxos,
  4.   OpenMinterTokenInfo,
  5.   getTokenMinter,
  6.   logerror,
  7.   getTokenMinterCount,
  8.   isOpenMinter,
  9.   sleep,
  10.   needRetry,
  11.   unScaleByDecimals,
  12.   getTokens,
  13.   btc,
  14.   TokenMetadata,
  15. } from 'src/common';
  16. import { openMint } from './ft.open-minter';
  17. import { ConfigService, SpendService, WalletService } from 'src/providers';
  18. import { Inject } from '@nestjs/common';
  19. import { log } from 'console';
  20. import { findTokenMetadataById, scaleConfig } from 'src/token';
  21. import Decimal from 'decimal.js';
  22. import {
  23.   BoardcastCommand,
  24.   BoardcastCommandOptions,
  25. } from '../boardcast.command';
  26. import { broadcastMergeTokenTxs, mergeTokens } from '../send/merge';
  27. import { calcTotalAmount, sendToken } from '../send/ft';
  28. import { pickLargeFeeUtxo } from '../send/pick';
  29. interface MintCommandOptions extends BoardcastCommandOptions {
  30.   id: string;
  31.   merge: boolean;
  32.   new?: number;
  33. }
  34.  
  35. function getRandomInt(max: number) {
  36.   return Math.floor(Math.random() * max);
  37. }
  38.  
  39. @Command({
  40.   name: 'mint',
  41.   description: 'Mint a token',
  42. })
  43. export class MintCommand extends BoardcastCommand {
  44.   constructor(
  45.     @Inject() private readonly spendService: SpendService,
  46.     @Inject() protected readonly walletService: WalletService,
  47.     @Inject() protected readonly configService: ConfigService,
  48.   ) {
  49.     super(spendService, walletService, configService);
  50.   }
  51.  
  52.   async cat_cli_run(
  53.     passedParams: string[],
  54.     options?: MintCommandOptions,
  55.   ): Promise<void> {
  56.     try {
  57.       if (options.id) {
  58.         const address = this.walletService.getAddress();
  59.         const token = await findTokenMetadataById(
  60.           this.configService,
  61.           options.id,
  62.         );
  63.  
  64.         if (!token) {
  65.           console.error(`No token found for tokenId: ${options.id}`);
  66.           return;
  67.         }
  68.  
  69.         const scaledInfo = scaleConfig(token.info as OpenMinterTokenInfo);
  70.  
  71.         let amount: bigint | undefined;
  72.  
  73.         if (passedParams[0]) {
  74.           try {
  75.             const d = new Decimal(passedParams[0]).mul(
  76.               Math.pow(10, scaledInfo.decimals),
  77.             );
  78.             amount = BigInt(d.toString());
  79.           } catch (error) {
  80.             logerror(`Invalid amount: "${passedParams[0]}"`, error);
  81.             return;
  82.           }
  83.         }
  84.  
  85.         const MAX_RETRY_COUNT = 10;
  86.  
  87.         for (let index = 0; index < MAX_RETRY_COUNT; index++) {
  88.           if (options.merge) {
  89.             await this.merge(token, address);
  90.           }
  91.           const feeRate = await this.getFeeRate();
  92.           const feeUtxos = await this.getFeeUTXOs(address);
  93.           if (feeUtxos.length === 0) {
  94.             console.warn('Insufficient satoshis balance!');
  95.             return;
  96.           }
  97.  
  98.           const count = await getTokenMinterCount(
  99.             this.configService,
  100.             token.tokenId,
  101.           );
  102.  
  103.           const maxTry = count < MAX_RETRY_COUNT ? count : MAX_RETRY_COUNT;
  104.  
  105.           if (count == 0 && index >= maxTry) {
  106.             console.error('No available minter UTXO found!');
  107.             return;
  108.           }
  109.  
  110.           const offset = getRandomInt(count - 1);
  111.           const minter = await getTokenMinter(
  112.             this.configService,
  113.             this.walletService,
  114.             token,
  115.             offset,
  116.           );
  117.  
  118.           if (minter == null) {
  119.             continue;
  120.           }
  121.  
  122.           if (isOpenMinter(token.info.minterMd5)) {
  123.             const minterState = minter.state.data;
  124.             if (minterState.isPremined && amount > scaledInfo.limit) {
  125.               console.error('The number of minted tokens exceeds the limit!');
  126.               return;
  127.             }
  128.  
  129.             const limit = scaledInfo.limit;
  130.  
  131.             if (minter.state.data.remainingSupply < limit) {
  132.               console.warn(
  133.                 `small limit of ${unScaleByDecimals(limit, token.info.decimals)} in the minter UTXO!`,
  134.               );
  135.               log(`retry to mint token [${token.info.symbol}] ...`);
  136.               continue;
  137.             }
  138.  
  139.             if (!minter.state.data.isPremined && scaledInfo.premine > 0n) {
  140.               if (typeof amount === 'bigint') {
  141.                 if (amount !== scaledInfo.premine) {
  142.                   throw new Error(
  143.                     `first mint amount should equal to premine ${scaledInfo.premine}`,
  144.                   );
  145.                 }
  146.               } else {
  147.                 amount = scaledInfo.premine;
  148.               }
  149.             } else {
  150.               amount = amount || limit;
  151.               amount =
  152.                 amount > minter.state.data.remainingSupply
  153.                   ? minter.state.data.remainingSupply
  154.                   : amount;
  155.             }
  156.  
  157.             const mintTxIdOrErr = await openMint(
  158.               this.configService,
  159.               this.walletService,
  160.               this.spendService,
  161.               feeRate,
  162.               feeUtxos,
  163.               token,
  164.               2,
  165.               minter,
  166.               amount,
  167.             );
  168.  
  169.             if (mintTxIdOrErr instanceof Error) {
  170.               if (needRetry(mintTxIdOrErr)) {
  171.                 // throw these error, so the caller can handle it.
  172.                 log(`retry to mint token [${token.info.symbol}] ...`);
  173.                 await sleep(6);
  174.                 continue;
  175.               } else {
  176.                 logerror(
  177.                   `mint token [${token.info.symbol}] failed`,
  178.                   mintTxIdOrErr,
  179.                 );
  180.                 return;
  181.               }
  182.             }
  183.  
  184.             console.log(
  185.               `Minting ${unScaleByDecimals(amount, token.info.decimals)} ${token.info.symbol} tokens in txid: ${mintTxIdOrErr} ...`,
  186.             );
  187.             return;
  188.           } else {
  189.             throw new Error('unkown minter!');
  190.           }
  191.         }
  192.  
  193.         console.error(`mint token [${token.info.symbol}] failed`);
  194.       } else {
  195.         throw new Error('expect a ID option');
  196.       }
  197.     } catch (error) {
  198.       logerror('mint failed!', error);
  199.     }
  200.   }
  201.  
  202.   async merge(metadata: TokenMetadata, address: btc.Addres) {
  203.     const res = await getTokens(
  204.       this.configService,
  205.       this.spendService,
  206.       metadata,
  207.       address,
  208.     );
  209.  
  210.     if (res !== null) {
  211.       const { contracts: tokenContracts } = res;
  212.  
  213.       if (tokenContracts.length > 1) {
  214.         const cachedTxs: Map<string, btc.Transaction> = new Map();
  215.         console.info(`Start merging your [${metadata.info.symbol}] tokens ...`);
  216.  
  217.         const feeUtxos = await this.getFeeUTXOs(address);
  218.         const feeRate = await this.getFeeRate();
  219.         // eslint-disable-next-line @typescript-eslint/no-unused-vars
  220.         const [newTokens, newFeeUtxos, e] = await mergeTokens(
  221.           this.configService,
  222.           this.walletService,
  223.           this.spendService,
  224.           feeUtxos,
  225.           feeRate,
  226.           metadata,
  227.           tokenContracts,
  228.           address,
  229.           cachedTxs,
  230.         );
  231.  
  232.         if (e instanceof Error) {
  233.           logerror('merge token failed!', e);
  234.           return;
  235.         }
  236.  
  237.         const feeUtxo = pickLargeFeeUtxo(newFeeUtxos);
  238.  
  239.         if (newTokens.length > 1) {
  240.           const amountTobeMerge = calcTotalAmount(newTokens);
  241.           const result = await sendToken(
  242.             this.configService,
  243.             this.walletService,
  244.             feeUtxo,
  245.             feeRate,
  246.             metadata,
  247.             newTokens,
  248.             address,
  249.             address,
  250.             amountTobeMerge,
  251.             cachedTxs,
  252.           );
  253.           if (result) {
  254.             await broadcastMergeTokenTxs(
  255.               this.configService,
  256.               this.walletService,
  257.               this.spendService,
  258.               [result.commitTx, result.revealTx],
  259.             );
  260.  
  261.             console.info(
  262.               `Merging your [${metadata.info.symbol}] tokens in txid: ${result.revealTx.id} ...`,
  263.             );
  264.           }
  265.         }
  266.       }
  267.     }
  268.   }
  269.  
  270.   @Option({
  271.     flags: '-i, --id [tokenId]',
  272.     description: 'ID of the token',
  273.   })
  274.   parseId(val: string): string {
  275.     return val;
  276.   }
  277.  
  278.   @Option({
  279.     flags: '-m, --merge [merge]',
  280.     defaultValue: false,
  281.     description: 'merge token utxos when mint',
  282.   })
  283.   // eslint-disable-next-line @typescript-eslint/no-unused-vars
  284.   parseMerge(val: string): boolean {
  285.     return true;
  286.   }
  287.  
  288.   async getFeeUTXOs(address: btc.Address) {
  289.     let feeUtxos = await getUtxos(
  290.       this.configService,
  291.       this.walletService,
  292.       address,
  293.     );
  294.  
  295.     feeUtxos = feeUtxos.filter((utxo) => {
  296.       return this.spendService.isUnspent(utxo);
  297.     });
  298.  
  299.     if (feeUtxos.length === 0) {
  300.       console.warn('Insufficient satoshis balance!');
  301.       return [];
  302.     }
  303.     return feeUtxos;
  304.   }
  305. }
  306.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement