PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-algo/src

Просмотр файла: algo.ts

/**
 * @prettier
 */
import * as _ from 'lodash';
import { SeedValidator } from './seedValidator';
import { coins, CoinFamily } from '@bitgo/statics';
import * as AlgoLib from './lib';
import {
  AddressCoinSpecific,
  BaseBroadcastTransactionOptions,
  BaseBroadcastTransactionResult,
  BaseCoin,
  BitGoBase,
  InvalidAddressError,
  InvalidKey,
  KeyIndices,
  KeyPair,
  ParsedTransaction,
  ParseTransactionOptions,
  SignedTransaction,
  SignTransactionOptions as BaseSignTransactionOptions,
  TokenManagementType,
  TransactionExplanation,
  TransactionRecipient,
  TransactionType,
  UnexpectedAddressError,
  VerifyAddressOptions,
  VerifyTransactionOptions,
  NotSupported,
  MultisigType,
  multisigTypes,
} from '@bitgo/sdk-core';
import stellar from 'stellar-sdk';
import BigNumber from 'bignumber.js';
import Utils from './lib/utils';
import { TxData } from './lib/ifaces';
import * as algosdk from 'algosdk';
import {
  MAINNET_GENESIS_HASH,
  MAINNET_GENESIS_ID,
  TESTNET_GENESIS_HASH,
  TESTNET_GENESIS_ID,
} from './lib/transactionBuilder';
import { Buffer } from 'buffer';

const SUPPORTED_ADDRESS_VERSION = 1;
const MSIG_THRESHOLD = 2; // m in m-of-n

export interface AlgoAddressCoinSpecifics extends AddressCoinSpecific {
  rootAddress: string;
  bitgoKey: string;
  bitgoPubKey?: string;
  addressVersion: number;
  threshold: number;
}

export interface VerifyAlgoAddressOptions extends VerifyAddressOptions {
  chain: number;
  index: number;
  coin: string;
  wallet: string;
  coinSpecific: AlgoAddressCoinSpecifics;
}

export interface AlgoTransactionExplanation extends TransactionExplanation {
  memo?: string;
  type?: string | number;
  voteKey?: string;
  selectionKey?: string;
  voteFirst?: number;
  voteLast?: number;
  voteKeyDilution?: number;
  tokenId?: number;
  operations?: TransactionOperation[];
}

export interface TransactionOperation {
  type: string;
  coin: string;
}

export interface SignTransactionOptions extends BaseSignTransactionOptions {
  txPrebuild: TransactionPrebuild;
  prv: string;
}

export interface TransactionPrebuild {
  txHex: string;
  halfSigned?: {
    txHex: string;
  };
  txInfo: {
    from: string;
    to: string;
    amount: string;
    fee: number;
    firstRound: number;
    lastRound: number;
    genesisID: string;
    genesisHash: string;
    note?: string;
  };
  keys: string[];
  addressVersion: number;
}

export interface FullySignedTransaction {
  txHex: string;
}

export interface HalfSignedTransaction {
  halfSigned: {
    txHex: string;
  };
}

export interface TransactionFee {
  fee: string;
}
export interface ExplainTransactionOptions {
  txHex?: string;
  halfSigned?: {
    txHex: string;
  };
  publicKeys?: string[];
  feeInfo: TransactionFee;
}

interface NodeParams {
  token: string;
  baseServer: string;
  port: number;
}

export interface VerifiedTransactionParameters {
  txHex: string;
  addressVersion: number;
  signers: string[];
  prv: string;
  isHalfSigned: boolean;
  numberSigners: number;
}

export interface RecoveryOptions {
  backupKey: string;
  userKey: string;
  rootAddress: string;
  recoveryDestination: string;
  bitgoKey: string;
  walletPassphrase?: string;
  fee: number;
  firstRound?: number;
  note?: string;
  nodeParams: NodeParams;
}

interface RecoveryInfo {
  id: string;
  tx: string;
  coin: string;
  fee: number;
  firstRound: number;
  lastRound: number;
  genesisId: string;
  genesisHash: string;
  note?: string;
}

export interface OfflineVaultTxInfo {
  txHex: string;
  userKey: string;
  backupKey: string;
  bitgoKey: string;
  type?: string;
  address: string;
  coin: string;
  feeInfo: number;
  amount: string;
  firstRound: number;
  lastRound: number;
  genesisId: string;
  genesisHash: string;
  note?: string;
  addressVersion: number;
  keys: string[];
}

export interface BroadcastTransactionOptions extends BaseBroadcastTransactionOptions {
  nodeParams: NodeParams;
}

export class Algo extends BaseCoin {
  readonly ENABLE_TOKEN: TokenManagementType = 'enabletoken';
  readonly DISABLE_TOKEN: TokenManagementType = 'disabletoken';

  constructor(bitgo: BitGoBase) {
    super(bitgo);
  }

  static createInstance(bitgo: BitGoBase): BaseCoin {
    return new Algo(bitgo);
  }

  getChain(): string {
    return 'algo';
  }

  getBaseChain(): string {
    return 'algo';
  }

  getFamily(): string {
    return 'algo';
  }

  getFullName(): string {
    return 'Algorand';
  }

  getBaseFactor(): number | string {
    return 1e6;
  }

  /**
   * Flag for sending value of 0
   * @returns {boolean} True if okay to send 0 value, false otherwise
   */
  valuelessTransferAllowed(): boolean {
    return true;
  }

  /**
   * Algorand supports account consolidations. These are transfers from the receive addresses
   * to the main address.
   */
  allowsAccountConsolidations(): boolean {
    return true;
  }

  /** inheritdoc */
  deriveKeyWithSeed(): { derivationPath: string; key: string } {
    throw new NotSupported('method deriveKeyWithSeed not supported for eddsa curve');
  }

  /** inheritdoc */
  generateKeyPair(seed?: Buffer): KeyPair {
    const keyPair = seed ? new AlgoLib.KeyPair({ seed }) : new AlgoLib.KeyPair();
    const keys = keyPair.getKeys();
    if (!keys.prv) {
      throw new Error('Missing prv in key generation.');
    }

    return {
      pub: keyPair.getAddress(),
      prv: AlgoLib.algoUtils.encodeSeed(Buffer.from(keyPair.getSigningKey())),
    };
  }

  /** inheritdoc */
  generateRootKeyPair(seed?: Buffer): KeyPair {
    const keyPair = seed ? new AlgoLib.KeyPair({ seed }) : new AlgoLib.KeyPair();
    const keys = keyPair.getKeys();
    if (!keys.prv) {
      throw new Error('Missing prv in key generation.');
    }
    return { prv: keys.prv + keys.pub, pub: keys.pub };
  }

  /**
   * Return boolean indicating whether input is valid public key for the coin.
   *
   * @param {String} pub the pub to be checked
   * @returns {Boolean} is it valid?
   */
  isValidPub(pub: string): boolean {
    return AlgoLib.algoUtils.isValidAddress(pub) || AlgoLib.algoUtils.isValidPublicKey(pub);
  }

  /**
   * Return boolean indicating whether input is valid seed for the coin
   * In Algorand, when the private key is encoded as base32 string only the first 32 bytes are taken,
   * so the encoded value is actually the seed
   *
   * @param {String} prv the prv to be checked
   * @returns {Boolean} is it valid?
   */
  isValidPrv(prv: string): boolean {
    return AlgoLib.algoUtils.isValidSeed(prv) || AlgoLib.algoUtils.isValidPrivateKey(prv);
  }

  /**
   * Return boolean indicating whether input is valid public key for the coin
   *
   * @param {String} address the pub to be checked
   * @returns {Boolean} is it valid?
   */
  isValidAddress(address: string): boolean {
    return AlgoLib.algoUtils.isValidAddress(address);
  }

  /**
   * Sign message with private key
   *
   * @param key
   * @param message
   */
  async signMessage(key: KeyPair, message: string | Buffer): Promise<Buffer> {
    const algoKeypair = new AlgoLib.KeyPair({ prv: key.prv });
    if (Buffer.isBuffer(message)) {
      message = message.toString('base64');
    }
    return Buffer.from(algoKeypair.signMessage(message));
  }

  /**
   * Specifies what key we will need for signing` - Algorand needs the backup, bitgo pubs.
   */
  keyIdsForSigning(): number[] {
    return [KeyIndices.USER, KeyIndices.BACKUP, KeyIndices.BITGO];
  }

  getTokenNameById(tokenId: number | string): string {
    const tokenNames = coins.filter((coin) => coin.family === 'algo' && coin.isToken).map(({ name }) => name!);
    return tokenNames.find((tokenName) => tokenName.split('-')[1] === `${tokenId}`) || 'AlgoToken unknown';
  }

  /**
   * Explain/parse transaction
   * @param params
   */
  async explainTransaction(params: ExplainTransactionOptions): Promise<AlgoTransactionExplanation | undefined> {
    const txHex = params.txHex || (params.halfSigned && params.halfSigned.txHex);
    if (!txHex || !params.feeInfo) {
      throw new Error('missing explain tx parameters');
    }

    const factory = this.getBuilder();

    const txBuilder = factory.from(txHex);
    const tx = await txBuilder.build();
    const txJson = tx.toJson();

    if (tx.type === TransactionType.Send) {
      const outputs: TransactionRecipient[] = [
        {
          address: txJson.to,
          amount: txJson.amount,
          memo: txJson.note,
        },
      ];
      const operations: TransactionOperation[] = [];

      const isTokenTx = this.isTokenTx(txJson.type);
      if (isTokenTx) {
        const type = AlgoLib.algoUtils.getTokenTxType(txJson.amount, txJson.from, txJson.to, txJson.closeRemainderTo);
        operations.push({
          type: type,
          coin: this.getTokenNameById(txJson.tokenId),
        });
      }

      const displayOrder = [
        'id',
        'outputAmount',
        'changeAmount',
        'outputs',
        'changeOutputs',
        'fee',
        'memo',
        'type',
        'operations',
      ];

      const explanationResult: AlgoTransactionExplanation = {
        displayOrder,
        id: txJson.id,
        outputAmount: txJson.amount.toString(),
        changeAmount: '0',
        outputs,
        changeOutputs: [],
        fee: txJson.fee,
        memo: txJson.note,
        type: tx.type.toString(),
        operations,
      };

      if (txJson.tokenId) {
        explanationResult.tokenId = txJson.tokenId;
      }

      return explanationResult;
    }

    if (tx.type === TransactionType.WalletInitialization) {
      const displayOrder = [
        'id',
        'fee',
        'memo',
        'type',
        'voteKey',
        'selectionKey',
        'voteFirst',
        'voteLast',
        'voteKeyDilution',
      ];

      return {
        displayOrder,
        id: txJson.id,
        outputAmount: '0',
        changeAmount: '0',
        outputs: [],
        changeOutputs: [],
        fee: txJson.fee,
        memo: txJson.note,
        type: tx.type,
        voteKey: txJson.voteKey,
        selectionKey: txJson.selectionKey,
        voteFirst: txJson.voteFirst,
        voteLast: txJson.voteLast,
        voteKeyDilution: txJson.voteKeyDilution,
      };
    }
  }

  /**
   * returns if a tx is a token tx
   * @param type {string} - tx type
   * @returns true if it's a token tx
   */
  isTokenTx(type: string): boolean {
    return type === 'axfer';
  }

  /**
   * Check if a seed is a valid stellar seed
   *
   * @param {String} seed the seed to check
   * @returns {Boolean} true if the input is a Stellar seed
   */
  isStellarSeed(seed: string): boolean {
    return SeedValidator.isValidEd25519SeedForCoin(seed, CoinFamily.XLM);
  }

  /**
   * Convert a stellar seed to an algo seed
   *
   * @param {String} seed the seed to convert
   * @returns {Boolean | null} seed in algo encoding
   */
  convertFromStellarSeed(seed: string): string | null {
    // assume this is a trust custodial seed if its a valid ed25519 prv
    if (!this.isStellarSeed(seed) || SeedValidator.hasCompetingSeedFormats(seed)) {
      return null;
    }

    if (SeedValidator.isValidEd25519SeedForCoin(seed, CoinFamily.XLM)) {
      return AlgoLib.algoUtils.convertFromStellarSeed(seed);
    }

    return null;
  }

  verifySignTransactionParams(params: SignTransactionOptions): VerifiedTransactionParameters {
    const prv = params.prv;
    const addressVersion = params.txPrebuild.addressVersion;
    let isHalfSigned = false;

    // it's possible this tx was already signed - take the halfSigned
    // txHex if it is
    let txHex = params.txPrebuild.txHex;
    if (params.txPrebuild.halfSigned) {
      isHalfSigned = true;
      txHex = params.txPrebuild.halfSigned.txHex;
    }

    if (_.isUndefined(txHex)) {
      throw new Error('missing txPrebuild parameter');
    }

    if (!_.isString(txHex)) {
      throw new Error(`txPrebuild must be an object, got type ${typeof txHex}`);
    }

    if (_.isUndefined(prv)) {
      throw new Error('missing prv parameter to sign transaction');
    }

    if (!_.isString(prv)) {
      throw new Error(`prv must be a string, got type ${typeof prv}`);
    }

    if (!_.has(params.txPrebuild, 'keys')) {
      throw new Error('missing public keys parameter to sign transaction');
    }

    if (!_.isNumber(addressVersion)) {
      throw new Error('missing addressVersion parameter to sign transaction');
    }

    const signers = params.txPrebuild.keys.map((key) => {
      // if we are receiving addresses do not try to convert them
      if (!AlgoLib.algoUtils.isValidAddress(key)) {
        return AlgoLib.algoUtils.publicKeyToAlgoAddress(AlgoLib.algoUtils.toUint8Array(key));
      }
      return key;
    });
    // TODO(https://bitgoinc.atlassian.net/browse/STLX-6067): fix the number of signers using
    // should be similar to other coins implementation
    // If we have a number with digits to eliminate them without taking any rounding criteria.
    const numberSigners = Math.trunc(signers.length / 2) + 1;
    return { txHex, addressVersion, signers, prv, isHalfSigned, numberSigners };
  }

  /**
   * Assemble keychain and half-sign prebuilt transaction
   *
   * @param params
   * @param params.txPrebuild {TransactionPrebuild} prebuild object returned by platform
   * @param params.prv {String} user prv
   * @returns {Promise<SignedTransaction>}
   */
  async signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
    const { txHex, signers, prv, isHalfSigned, numberSigners } = this.verifySignTransactionParams(params);
    const factory = this.getBuilder();
    const txBuilder = factory.from(txHex);
    txBuilder.numberOfRequiredSigners(numberSigners);
    txBuilder.sign({ key: prv });
    txBuilder.setSigners(signers);
    const transaction = await txBuilder.build();
    if (!transaction) {
      throw new Error('Invalid transaction');
    }
    const signedTxHex = Buffer.from(transaction.toBroadcastFormat()).toString('base64');
    if (numberSigners === 1) {
      return { txHex: signedTxHex };
    } else if (isHalfSigned) {
      return { txHex: signedTxHex };
    } else {
      return { halfSigned: { txHex: signedTxHex } };
    }
  }

  async parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
    return {};
  }

  /**
   * Check if address can be used to send funds.
   *
   * @param params.address address to validate
   * @param params.keychains public keys to generate the wallet
   */
  async isWalletAddress(params: VerifyAlgoAddressOptions): Promise<boolean> {
    const {
      address,
      keychains,
      coinSpecific: { bitgoPubKey },
    } = params;

    if (!this.isValidAddress(address)) {
      throw new InvalidAddressError(`invalid address: ${address}`);
    }

    if (!keychains) {
      throw new Error('missing required param keychains');
    }

    const effectiveKeychain = bitgoPubKey ? keychains.slice(0, -1).concat([{ pub: bitgoPubKey }]) : keychains;
    const pubKeys = effectiveKeychain.map((key) => this.stellarAddressToAlgoAddress(key.pub));

    if (!pubKeys.every((pubKey) => this.isValidPub(pubKey))) {
      throw new InvalidKey('invalid public key');
    }

    const rootAddress = AlgoLib.algoUtils.multisigAddress(SUPPORTED_ADDRESS_VERSION, MSIG_THRESHOLD, pubKeys);

    return rootAddress === address;
  }

  async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
    return true;
  }

  decodeTx(txn: Buffer): unknown {
    return AlgoLib.algoUtils.decodeAlgoTxn(txn);
  }

  getAddressFromPublicKey(pubKey: Uint8Array): string {
    return AlgoLib.algoUtils.publicKeyToAlgoAddress(pubKey);
  }

  supportsDeriveKeyWithSeed(): boolean {
    return false;
  }

  /** {@inheritDoc } **/
  supportsMultisig(): boolean {
    return true;
  }

  /** inherited doc */
  getDefaultMultisigType(): MultisigType {
    return multisigTypes.onchain;
  }

  /**
   * Gets config for how token enablements work for this coin
   * @returns
   *    requiresTokenEnablement: True if tokens need to be enabled for this coin
   *    supportsMultipleTokenEnablements: True if multiple tokens can be enabled in one transaction
   */
  getTokenEnablementConfig() {
    return {
      requiresTokenEnablement: true,
      supportsMultipleTokenEnablements: false,
    };
  }

  /**
   * Gets the balance of the root address in base units of algo
   * Eg. If balance is 1 Algo, this returns 1*10^6
   * @param rootAddress
   * @param client
   */
  async getAccountBalance(rootAddress: string, client: algosdk.Algodv2): Promise<number> {
    const accountInformation = await client.accountInformation(rootAddress).do();
    // Extract the balance from the account information
    return accountInformation.amount;
  }

  /**
   * Returns the Algo client for the given token, baseServer and port
   * Used to interact with the Algo network
   */
  getClient(token: string, baseServer: string, port: number): algosdk.Algodv2 {
    return new algosdk.Algodv2(token, baseServer, port);
  }

  public async recover(params: RecoveryOptions): Promise<RecoveryInfo | OfflineVaultTxInfo> {
    const isUnsignedSweep = this.isValidPub(params.userKey) && this.isValidPub(params.backupKey);

    if (!params.nodeParams) {
      throw new Error('Please provide the details of an ALGO node to use for recovery');
    }

    // Validate the root address
    if (!this.isValidAddress(params.rootAddress)) {
      throw new Error('invalid rootAddress, got: ' + params.rootAddress);
    }

    // Validate the destination address
    if (!this.isValidAddress(params.recoveryDestination)) {
      throw new Error('invalid recoveryDestination, got: ' + params.recoveryDestination);
    }

    if (params.firstRound && new BigNumber(params.firstRound).isNegative()) {
      throw new Error('first round needs to be a positive value');
    }

    const genesisId = this.bitgo.getEnv() === 'prod' ? MAINNET_GENESIS_ID : TESTNET_GENESIS_ID;
    const genesisHash = this.bitgo.getEnv() === 'prod' ? MAINNET_GENESIS_HASH : TESTNET_GENESIS_HASH;

    Utils.validateBase64(genesisHash);

    if (!isUnsignedSweep && !params.walletPassphrase) {
      throw new Error('walletPassphrase is required for non-bitgo recovery');
    }

    const factory = new AlgoLib.TransactionBuilderFactory(coins.get('algo'));
    const txBuilder = factory.getTransferBuilder();

    let userPrv: string | undefined;
    let backupPrv: string | undefined;
    if (!isUnsignedSweep) {
      if (!params.bitgoKey) {
        throw new Error('bitgo public key from the keyCard is required for non-bitgo recovery');
      }
      try {
        userPrv = this.bitgo.decrypt({ input: params.userKey, password: params.walletPassphrase });
        backupPrv = this.bitgo.decrypt({ input: params.backupKey, password: params.walletPassphrase });
        const userKeyAddress = Utils.privateKeyToAlgoAddress(userPrv);
        const backupKeyAddress = Utils.privateKeyToAlgoAddress(backupPrv);
        txBuilder.numberOfRequiredSigners(2).setSigners([userKeyAddress, backupKeyAddress, params.bitgoKey]);
      } catch (e) {
        throw new Error(
          'unable to decrypt userKey or backupKey with the walletPassphrase provided, got error: ' + e.message
        );
      }
    }

    const client = this.getClient(params.nodeParams.token, params.nodeParams.baseServer, params.nodeParams.port);
    const nativeBalance = await this.getAccountBalance(params.rootAddress, client);

    // Algorand accounts require a min. balance of 1 ALGO
    const MIN_MICROALGOS_BALANCE = 100000;
    const spendableAmount = new BigNumber(nativeBalance).minus(params.fee).minus(MIN_MICROALGOS_BALANCE).toNumber();

    if (new BigNumber(spendableAmount).isZero() || new BigNumber(spendableAmount).isLessThanOrEqualTo(params.fee)) {
      throw new Error(
        'Insufficient balance to recover, got balance: ' +
          nativeBalance +
          ' fee: ' +
          params.fee +
          ' min account balance: ' +
          MIN_MICROALGOS_BALANCE
      );
    }

    let latestRound: number | undefined;
    if (!params.firstRound) {
      latestRound = await client
        .status()
        .do()
        .then((status) => status['last-round']);
    }

    const firstRound = !params.firstRound ? latestRound : params.firstRound;
    if (!firstRound) {
      throw new Error('Unable to fetch the latest round from the node. Please provide the firstRound or try again.');
    }
    const LAST_ROUND_BUFFER = 1000;
    const lastRound = firstRound + LAST_ROUND_BUFFER;

    txBuilder
      .fee({ fee: params.fee.toString() })
      .isFlatFee(true)
      .sender({
        address: params.rootAddress,
      })
      .to({
        address: params.recoveryDestination,
      })
      .amount(spendableAmount)
      .genesisId(genesisId)
      .genesisHash(genesisHash)
      .firstRound(new BigNumber(firstRound).toNumber())
      .lastRound(new BigNumber(lastRound).toNumber());

    if (params.note) {
      const note = new Uint8Array(Buffer.from(params.note, 'utf-8'));
      txBuilder.note(note);
    }

    // Cold wallet, offline vault
    if (isUnsignedSweep) {
      const tx = await txBuilder.build();
      const txJson = tx.toJson() as TxData;

      return {
        txHex: Buffer.from(tx.toBroadcastFormat()).toString('hex'),
        type: txJson.type,
        userKey: params.userKey,
        backupKey: params.backupKey,
        bitgoKey: params.bitgoKey,
        address: params.rootAddress,
        coin: this.getChain(),
        feeInfo: txJson.fee,
        amount: txJson.amount ?? nativeBalance.toString(),
        firstRound: txJson.firstRound,
        lastRound: txJson.lastRound,
        genesisId: genesisId,
        genesisHash: genesisHash,
        note: txJson.note ? Buffer.from(txJson.note.buffer).toString('utf-8') : undefined,
        keys: [params.userKey, params.backupKey, params.bitgoKey],
        addressVersion: 1,
      };
    }

    // Non-bitgo Recovery (Hot wallets)
    txBuilder.sign({ key: userPrv });
    txBuilder.sign({ key: backupPrv });

    const tx = await txBuilder.build();
    const txJson = tx.toJson() as TxData;

    return {
      tx: Buffer.from(tx.toBroadcastFormat()).toString('base64'),
      id: txJson.id,
      coin: this.getChain(),
      fee: txJson.fee,
      firstRound: txJson.firstRound,
      lastRound: txJson.lastRound,
      genesisId: genesisId,
      genesisHash: genesisHash,
      note: txJson.note ? Buffer.from(txJson.note.buffer).toString('utf-8') : undefined,
    };
  }

  /**
   * Accepts a fully signed serialized base64 transaction and broadcasts it on the network.
   * Uses the external node provided by the client
   * @param serializedSignedTransaction
   * @param nodeParams
   */
  async broadcastTransaction({
    serializedSignedTransaction,
    nodeParams,
  }: BroadcastTransactionOptions): Promise<BaseBroadcastTransactionResult> {
    if (!nodeParams) {
      throw new Error('Please provide the details of the algorand node');
    }
    try {
      const txHex = Buffer.from(serializedSignedTransaction, 'base64').toString('hex');
      const algoTx = Utils.toUint8Array(txHex);
      const client = this.getClient(nodeParams.token, nodeParams.baseServer, nodeParams.port);

      return await client.sendRawTransaction(algoTx).do();
    } catch (e) {
      throw new Error('Failed to broadcast transaction, error: ' + e.message);
    }
  }

  /**
   * Stellar and Algorand both use keys on the ed25519 curve, but use different encodings.
   * As the HSM doesn't have explicit support to create Algorand addresses, we use the Stellar
   * keys and re-encode them to the Algorand encoding.
   *
   * This method should only be used when creating Algorand custodial wallets reusing Stellar keys.
   *
   * @param {string} addressOrPubKey a Stellar pubkey or Algorand address
   * @return {*}
   */
  private stellarAddressToAlgoAddress(addressOrPubKey: string): string {
    // we have an Algorand address
    if (this.isValidAddress(addressOrPubKey)) {
      return addressOrPubKey;
    }

    // we have a stellar key
    if (stellar.StrKey.isValidEd25519PublicKey(addressOrPubKey)) {
      const stellarPub = stellar.StrKey.decodeEd25519PublicKey(addressOrPubKey);
      const algoAddress = AlgoLib.algoUtils.encodeAddress(stellarPub);
      if (this.isValidAddress(algoAddress)) {
        return algoAddress;
      }
      throw new UnexpectedAddressError('Cannot convert Stellar address to an Algorand address via stellar pubkey.');
      // we have a root pubkey
    } else if (AlgoLib.algoUtils.isValidPublicKey(addressOrPubKey)) {
      const kp = new AlgoLib.KeyPair({ pub: addressOrPubKey });
      const algoAddress = kp.getAddress();
      if (this.isValidAddress(algoAddress)) {
        return algoAddress;
      }
      throw new UnexpectedAddressError('Invalid root pubkey.');
    }

    throw new UnexpectedAddressError('Neither an Algorand address, a stellar pubkey or a root public key.');
  }

  private getBuilder(): AlgoLib.TransactionBuilderFactory {
    return new AlgoLib.TransactionBuilderFactory(coins.get(this.getBaseChain()));
  }
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!