import { ChainClientParams, Network } from '@d11k-ts/client' import { validatePhrase } from '@d11k-ts/crypto' import { InboundAddressResult, SwapAssetList } from '@d11k-ts/utils' import Arweave from 'arweave' import { getKeyFromMnemonic } from 'arweave-mnemonic-keys' import { ApiConfig } from 'arweave/node/lib/api' import Transaction, { Tag } from 'arweave/node/lib/transaction' import axios from 'axios' import ArweaveTxClient from './tx-client' import { ArTxDataResult, ArTxParams, ArTxs, ArTxsHistoryParams, GasfeeResult, TxStatusResponse } from './types' import { defaultArMainnetConfig, defaultArTestnetConfig } from './utils' export interface ArweaveChainClient { getAddress(): Promise mintArTokens(pubAddress: string): Promise getBalance(address: string): Promise getFees(rawTx: Transaction): GasfeeResult transfer({ recipient, amount }: ArTxParams): Promise getTransactionData(txHash: string): Promise getTransactionsHistory(params: ArTxsHistoryParams): Promise getTxStatus(txHash: string): Promise } export type ChainConfigParams = { config?: ApiConfig } class ArweaveClient extends ArweaveTxClient implements ArweaveChainClient { protected network: Network protected arweave: Arweave protected phrase = '' protected apiConfig: ApiConfig constructor({ phrase, network = Network.Mainnet, config = defaultArMainnetConfig, }: ChainClientParams & ChainConfigParams) { super() if (phrase) { if (!validatePhrase(phrase)) { throw new Error('Invalid phrase') } this.phrase = phrase } this.network = network this.apiConfig = config if (this.network === Network.Testnet || this.network === Network.Stagenet) { this.apiConfig = defaultArTestnetConfig } if (this.network === Network.DojTestnet && this.apiConfig === defaultArMainnetConfig) { throw Error(`'config' params can't be empty for 'dojtestnet'`) } this.arweave = Arweave.init(this.apiConfig) } async getAddress(): Promise { const keyPair = await getKeyFromMnemonic(this.phrase) const address = await this.arweave.wallets.jwkToAddress(keyPair) return address } /** testnet tokens in winston */ async mintArTokens(address: string) { const test_ar_amount = 2000000000000 // Mint balance in Arlocal for testing await this.arweave.api.get(`/mint/${address}/${test_ar_amount}`) await this.arweave.api.get('/mine') } async getBalance(address: string): Promise { /** Get balance */ const wnstBalance = await this.arweave.wallets.getBalance(address) /** Convert balance from Winston to Ar. (1 Ar = 10^12) */ const arBalance = this.arweave.ar.winstonToAr(wnstBalance) return Number(arBalance) } /** Calculate gasFee required for transaction */ getFees(rawTx: Transaction): GasfeeResult { /** Gas fee generated by default during 'createTransaction' */ const arw_gasFee = Number(rawTx.reward) return { slow: arw_gasFee, average: arw_gasFee, fast: arw_gasFee, } } /** Create transaction based on user inputs */ async createTransaction(recipient: string, amount: number, tag?: Tag): Promise { const pvtKey = await getKeyFromMnemonic(this.phrase) // const pubAddress = await this.arweave.wallets.jwkToAddress(pvtKey); // Create transaction const rawTx = await this.arweave.createTransaction( { target: recipient, // Receiver address quantity: this.arweave.ar.arToWinston(amount.toString()), // Amount to transfer in Ar tags: tag ? [tag] : [], }, pvtKey, ) return rawTx } /** Sign and Send the transaction */ async signAndSend(rawTx: Transaction): Promise { const pvtKey = await getKeyFromMnemonic(this.phrase) // const pubAddress = await this._arweave.wallets.jwkToAddress(pvtKey); /** Sign transaction and retrieve status */ await this.arweave.transactions.sign(rawTx, pvtKey) const status = await this.arweave.transactions.post(rawTx) await this.arweave.api.get('/mine') if (status.status !== 200) throw Error(`Transaction error or invalid`) return rawTx.id } async transfer({ recipient, amount }: ArTxParams): Promise { const rawTx = await this.createTransaction(recipient, amount) const txHash = await this.signAndSend(rawTx) return txHash } /** Get status data using transaction hash / id */ async getTxStatus(txHash: string): Promise { const statusData = await this.arweave.transactions.getStatus(txHash) return statusData } async dummyTx(recipient: string, amount: number): Promise { const tag = new Tag(this.arweave.utils.stringToB64Url('memo'), this.arweave.utils.stringToB64Url(`NOOP:NOVAULT`)) const rawTx = await this.createTransaction(recipient, amount, tag) const txHash = await this.signAndSend(rawTx) return txHash } async getTransactionData(txHash: string): Promise { const txData = await this.getTxData(this.arweave, txHash) return txData } async getTransactionsHistory(params: ArTxsHistoryParams): Promise { const txs = await this.getTxsHistory(this.arweave, params.address, params.limit ? params.limit : 100) const txsResult = { total: txs.inner.length, txs, } return txsResult } async getInboundObject(): Promise { const response = await axios.get('https://api-test.h4s.dojima.network/hermeschain/inbound_addresses') if (response.status !== 200) { throw new Error(`Unable to retrieve inbound addresses. Dojima gateway responded with status ${response.status}.`) } const data: Array = response.data const inboundObj = data.find((res) => res.chain === 'AR') as InboundAddressResult return inboundObj } async getArweaveInboundAddress(): Promise { const inboundObj = await this.getInboundObject() return inboundObj.address } async getDefaultLiquidityPoolGasFee(): Promise { const inboundObj = await this.getInboundObject() /** Convert from Winston to Ar. (1 Ar = 10^12) */ const arGasFee = this.arweave.ar.winstonToAr(inboundObj.gas_rate) return Number(arGasFee) } async addLiquidityPool(amount: number, inboundAddress: string, dojAddress?: string): Promise { const tag = dojAddress ? new Tag('memo', `ADD:AR.AR:${dojAddress}`) : new Tag('memo', `ADD:AR.AR`) const rawTx = await this.createTransaction(inboundAddress, amount, tag) const txHash = await this.signAndSend(rawTx) return txHash } async swap(amount: number, token: SwapAssetList, inboundAddress: string, recipient: string): Promise { const tag = new Tag('memo', `SWAP:${token}:${recipient}`) const rawTx = await this.createTransaction(inboundAddress, amount, tag) const txHash = await this.signAndSend(rawTx) return txHash } } export { ArweaveClient }