import { bech32 } from "bech32" ; import BigNumber from "bignumber.js" ; import elliptic from "elliptic" ; import { assertType , ChainTransaction , DepositChain , ErrorWithCode , InputChainTransaction , OutputType , RenJSError , RenNetwork , RenNetworkString , utils , } from "@renproject/utils" ; import { AccAddress , Key , RawKey , SimplePublicKey , } from "@terra-money/terra.js" ; import { TerraDev } from "./api/terraDev" ; import { isTerraNetworkConfig , TerraNetworkConfig } from "./api/types" ; export const TerraMainnet = { selector : "Terra" , chainId : "columbus-5" , nativeAsset : { name : "Luna" , symbol : "LUNA" , decimals : 18 , } , averageConfirmationTime : 6 , explorer : "https://finder.terra.money/columbus" , apiUrl : "https://fcd.terra.dev" , } ; export const TerraTestnet = { selector : "Terra" , chainId : "bombay-12" , nativeAsset : { name : "Luna" , symbol : "LUNA" , decimals : 18 , } , averageConfirmationTime : 6 , explorer : "https://finder.terra.money/bombay" , apiUrl : "https://bombay-fcd.terra.dev" , } ; export const TerraConfigMap = { [ RenNetwork . Mainnet ] : TerraMainnet , [ RenNetwork . Testnet ] : TerraTestnet , [ RenNetwork . Devnet ] : TerraTestnet , } ; export interface TerraReleasePayload { chain : string ; address : string ; } /**
 * Terra implements the LockChain interface for Terra (https://terra.money)
 * and it's asset LUNA.
 */ export class Terra implements DepositChain < { chain : string ; } , TerraReleasePayload > { public static chain = "Terra" ; public chain = Terra . chain ; public name = Terra . chain ; public static configMap = TerraConfigMap ; public configMap = TerraConfigMap ; public network : TerraNetworkConfig ; public api : TerraDev ; public static assets = { LUNA : "LUNA" , } ; public assets = Terra . assets ; public validateAddress ( address ) { assertType < string > ( "string" , { address : address } ) ; return AccAddress . validate ( address ) ; } public validateTransaction ( transaction ) { return ( utils . isHex ( typeof transaction === "string" ? transaction : transaction . txidFormatted , { length : 32 } ) && utils . fromBase64 ( transaction . txid ) . length === 32 ) ; } public addressExplorerLink ( address ) { return new URL ( `/account/${ address }` , this . network . explorer ) . href ; } public transactionExplorerLink ( transaction ) { return new URL ( `/tx/${ String ( transaction . txidFormatted ) }` , this . network . explorer ) . href ; } public formattedTransactionHash ( tx ) { return utils . fromBase64 ( tx . txid ) . toString ( "hex" ) . toUpperCase ( ) ; } public constructor ( { network , } ) { const networkConfig = isTerraNetworkConfig ( network ) ? network : TerraConfigMap [ network ] ; if ( ! networkConfig ) { if ( typeof network === "string" ) { throw new Error ( `Invalid RenVM network ${ network }.` ) ; } else { throw new Error ( `Invalid Terra network config.` ) ; } } this . network = networkConfig ; this . chain = this . network . selector ; this . api = new TerraDev ( this . network ) ; } public isLockAsset ( asset ) { return this . assets [ asset ] !== undefined ; } /**
     * See [[LockChain.isLockAsset]].
     */ public isDepositAsset ( asset ) { return this . isLockAsset ( asset ) ; } private _assertAssetIsSupported ( asset ) { if ( ! this . isLockAsset ( asset ) ) { throw new Error ( `Unsupported asset ${ asset }.` ) ; } } /**
     * See [[LockChain.assetDecimals]].
     */ public assetDecimals ( asset ) { switch ( asset ) { case Terra . assets . LUNA : return 6 ; } throw new Error ( `Unsupported asset ${ String ( asset ) }.` ) ; } public getBalance = async ( asset , address ) : Promise < BigNumber > => { this . _assertAssetIsSupported ( asset ) ; if ( ! this . validateAddress ( address ) ) { throw new Error ( `Invalid address ${ address }.` ) ; } return new BigNumber ( 0 ) ; } ; /**
     * See [[LockChain.getDeposits]].
     */ public async watchForDeposits ( asset , fromPayload , address , onInput , _removeInput , listenerCancelled ) { this . _assertAssetIsSupported ( asset ) ; if ( fromPayload . chain !== this . chain ) { throw new Error ( `Invalid payload for chain ${ fromPayload . chain } instead of ${ this . chain }.` ) ; } while ( true ) { if ( listenerCancelled ( ) ) { return ; } const txs = await this . api . fetchDeposits ( address ) ; await Promise . all ( txs . map ( async ( tx ) => onInput ( { chain : this . chain , txid : utils . toURLBase64 ( Buffer . from ( tx . hash , "hex" ) ) , txidFormatted : tx . hash . toUpperCase ( ) , txindex : "0" , asset , amount : tx . amount , } ) ) ) ; } } /**
     * See [[LockChain.transactionConfidence]].
     */ public async transactionConfidence ( transaction ) { if ( ! this . network ) { throw new Error ( `${ this . name } object not initialized.` ) ; } return await this . api . fetchConfirmations ( transaction . txidFormatted ) ; } /**
     * See [[LockChain.getGatewayAddress]].
     */ public createGatewayAddress ( asset , fromPayload , shardPublicKey , gHash ) { this . _assertAssetIsSupported ( asset ) ; if ( fromPayload . chain !== this . chain ) { throw new Error ( `Invalid payload for chain ${ fromPayload . chain } instead of ${ this . chain }.` ) ; } const ec = new elliptic . ec ( "secp256k1" ) ; const renVMPublicKey = ec . keyFromPublic ( shardPublicKey ) ; const gHashKey = ec . keyFromPrivate ( gHash ) ; const derivedPublicKey = ec . keyFromPublic ( renVMPublicKey . getPublic ( ) . add ( gHashKey . getPublic ( ) ) as unknown as elliptic . ec . KeyPair ) ; const newCompressedPublicKey = Buffer . from ( derivedPublicKey . getPublic ( ) . encodeCompressed ( ) ) ; const address = new ( Key as { new ( publicKey ) : Key ; } ) ( new SimplePublicKey ( newCompressedPublicKey . toString ( "base64" ) ) ) ; return address . accAddress ; } public getOutputPayload ( asset , _type , toPayload ) { this . _assertAssetIsSupported ( asset ) ; return { to : toPayload . address , toBytes : Buffer . from ( bech32 . fromWords ( bech32 . decode ( toPayload . address ) . words ) ) , payload : Buffer . from ( [ ] ) , } ; } category public Address ( address ) { assertType < string > ( "string" , { address } ) ; if ( ! this . validateAddress ( address ) ) { throw ErrorWithCode . from ( new Error ( `Invalid ${ this . chain } address: ${ String ( address ) }` ) , RenJSError . PARAMETER_ERROR ) ; } return { chain : this . chain , address , } ; } category public GatewayAddress ( ) { return { chain : this . chain , } ; } }