diff --git a/.gitignore b/.gitignore index 1162f24..214d05a 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ build/ # Optional npm cache directory .npm .vscode +.yarn # Optional eslint cache .eslintcache diff --git a/src/connections/networks/index.ts b/src/connections/networks/index.ts index 5f73f7e..531efe9 100644 --- a/src/connections/networks/index.ts +++ b/src/connections/networks/index.ts @@ -9,14 +9,15 @@ export const subsocial = { icon: 'subsocial-parachain.svg', paraId: 2100, ipfs: 'https://ipfs.subsocial.network', - offchain: 'https://api.subsocial.network' + offchain: 'https://api.subsocial.network', + isTransferable: true } export const standalones: Networks = { kusama: { name: 'Kusama', ...resolveOnfinalityUrl('kusama'), - wsNode: 'wss://kusama-rpc.polkadot.io', + // wsNode: 'wss://kusama-rpc.polkadot.io', icon: 'kusama.svg', isMixedConnection: true, isTransferable: true @@ -24,7 +25,7 @@ export const standalones: Networks = { polkadot: { name: 'Polkadot', ...resolveOnfinalityUrl('polkadot'), - wsNode: 'wss://rpc.polkadot.io', + // wsNode: 'wss://rpc.polkadot.io', icon: 'polkadot.svg', isMixedConnection: true, isTransferable: true diff --git a/src/connections/networks/utils.ts b/src/connections/networks/utils.ts index fcf9a7c..2548442 100644 --- a/src/connections/networks/utils.ts +++ b/src/connections/networks/utils.ts @@ -2,8 +2,8 @@ import { ONFINALITY_API_KEY } from '../../constant' export const resolveOnfinalityUrl = (chainName: string) => { return { - // node: `wss://${chainName}.api.onfinality.io/ws?apikey=${ONFINALITY_API_KEY}`, - node: `https://${chainName}.api.onfinality.io/rpc?apikey=${ONFINALITY_API_KEY}`, + node: `wss://${chainName}.api.onfinality.io/ws?apikey=${ONFINALITY_API_KEY}`, + // node: `https://${chainName}.api.onfinality.io/rpc?apikey=${ONFINALITY_API_KEY}`, wsNode: `wss://${chainName}.api.onfinality.io/ws?apikey=${ONFINALITY_API_KEY}` } } \ No newline at end of file diff --git a/src/services/fees/custom/SubsocialAdapter.ts b/src/services/fees/custom/SubsocialAdapter.ts new file mode 100644 index 0000000..3b1dad3 --- /dev/null +++ b/src/services/fees/custom/SubsocialAdapter.ts @@ -0,0 +1,190 @@ +import { Storage } from '@acala-network/sdk/utils/storage' +import { AnyApi, FixedPointNumber as FN } from '@acala-network/sdk-core' +import { combineLatest, map, Observable } from 'rxjs' + +import { SubmittableExtrinsic } from '@polkadot/api/types' +import { DeriveBalancesAll } from '@polkadot/api-derive/balances/types' +import { ISubmittableResult } from '@polkadot/types/types' +import { + createPolkadotXCMAccount, + createPolkadotXCMAsset, + createPolkadotXCMDest, + createRouteConfigs, + validateAddress +} from '@polkawallet/bridge/utils' +import { BalanceData, BasicToken, ChainId, chains, TransferParams } from '@polkawallet/bridge' +import { BalanceAdapter, BalanceAdapterConfigs } from '@polkawallet/bridge/balance-adapter' +import { ApiNotFound, InvalidAddress, TokenNotFound } from '@polkawallet/bridge/errors' +import { BaseCrossChainAdapter } from '@polkawallet/bridge/base-chain-adapter' + +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore +// @ts-ignore +chains.subsocial = { + id: 'subsocial', + display: 'Subsocial', + type: 'substrate', + icon: 'https://sub.id/images/subsocial.svg', + paraChainId: 2100, + ss58Prefix: 28 +} +export const subsocialRouteConfigs = createRouteConfigs('subsocial' as any, [ + { + to: 'hydradx', + token: 'SUB', + xcm: { + fee: { token: 'SUB', amount: '63199000' }, + }, + }, +]) + +export const subsocialTokensConfig: Record = { + SUB: { name: 'SUB', symbol: 'SUB', decimals: 10, ed: '100000000' }, +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +const createBalanceStorages = (api: AnyApi) => { + return { + balances: (address: string) => + Storage.create({ + api, + path: 'derive.balances.all', + params: [ address ], + }), + } +} + +class SubsocialBalanceAdapter extends BalanceAdapter { + private storages: ReturnType + + constructor ({ api, chain, tokens }: BalanceAdapterConfigs) { + super({ api, chain, tokens }) + this.storages = createBalanceStorages(api) + } + + public subscribeBalance ( + token: string, + address: string + ): Observable { + const storage = this.storages.balances(address) + + if (token !== this.nativeToken) { + throw new TokenNotFound(token) + } + + return storage.observable.pipe( + map((data) => ({ + free: FN.fromInner(data.freeBalance.toString(), this.decimals), + locked: FN.fromInner(data.lockedBalance.toString(), this.decimals), + reserved: FN.fromInner(data.reservedBalance.toString(), this.decimals), + available: FN.fromInner( + data.availableBalance.toString(), + this.decimals + ), + })) + ) + } +} + +class SubsocialBaseAdapter extends BaseCrossChainAdapter { + private balanceAdapter?: SubsocialBalanceAdapter + + public async init (api: AnyApi) { + this.api = api + + await api.isReady + + const chain = this.chain.id as ChainId + + this.balanceAdapter = new SubsocialBalanceAdapter({ + chain, + api, + tokens: subsocialTokensConfig, + }) + } + + public subscribeTokenBalance ( + token: string, + address: string + ): Observable { + if (!this.balanceAdapter) { + throw new ApiNotFound(this.chain.id) + } + + return this.balanceAdapter.subscribeBalance(token, address) + } + + public subscribeMaxInput ( + token: string, + address: string, + to: ChainId + ): Observable { + if (!this.balanceAdapter) { + throw new ApiNotFound(this.chain.id) + } + + return combineLatest({ + txFee: this.estimateTxFee({ + amount: FN.ZERO, + to, + token, + address, + signer: address, + }), + balance: this.balanceAdapter + .subscribeBalance(token, address) + .pipe(map((i) => i.available)), + }).pipe( + map(({ balance, txFee }) => { + const tokenMeta = this.balanceAdapter?.getToken(token) + const feeFactor = 1.2 + const fee = FN.fromInner(txFee, tokenMeta?.decimals).mul( + new FN(feeFactor) + ) + + // always minus ed + return balance + .minus(fee) + .minus(FN.fromInner(tokenMeta?.ed || '0', tokenMeta?.decimals)) + }) + ) + } + + public createTx ( + params: TransferParams + ): + | SubmittableExtrinsic<'promise', ISubmittableResult> + | SubmittableExtrinsic<'rxjs', ISubmittableResult> { + if (!this.api) throw new ApiNotFound(this.chain.id) + + const { address, amount, to, token } = params + + if (!validateAddress(address)) throw new InvalidAddress(address) + + const toChain = chains[to] + + if (token !== this.balanceAdapter?.nativeToken) { + throw new TokenNotFound(token) + } + + const accountId = this.api?.createType('AccountId32', address).toHex() + const paraChainId = toChain.paraChainId + const rawAmount = amount.toChainData() + + return this.api?.tx.polkadotXcm.limitedReserveTransferAssets( + createPolkadotXCMDest(this.api, paraChainId), + createPolkadotXCMAccount(this.api, accountId), + createPolkadotXCMAsset(this.api, rawAmount, 'NATIVE'), + 0, + this.getDestWeight(token, to)?.toString() as any + ) + } +} + +export class SubsocialAdapter extends SubsocialBaseAdapter { + constructor () { + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore + super(chains.subsocial, subsocialRouteConfigs, subsocialTokensConfig) + } +} + diff --git a/src/services/fees/transfer.ts b/src/services/fees/transfer.ts index ada54c4..364d6b7 100644 --- a/src/services/fees/transfer.ts +++ b/src/services/fees/transfer.ts @@ -73,6 +73,7 @@ async function getCrossChainTransferFee ({ from, token, to }: GetTransferFeePara try { const adapter = await getCrossChainAdapter(from, node) if (!adapter) return amount + amount = await firstValueFrom(adapter.estimateTxFee({ to: to as ChainId, address: dummyAccount, diff --git a/src/services/fees/utils.ts b/src/services/fees/utils.ts index d5ff89e..7717178 100644 --- a/src/services/fees/utils.ts +++ b/src/services/fees/utils.ts @@ -20,6 +20,7 @@ import { StatemineAdapter } from '@polkawallet/bridge/adapters/statemint' import { ZeitgeistAdapter } from '@polkawallet/bridge/adapters/zeitgeist' import { BaseCrossChainAdapter } from '@polkawallet/bridge/base-chain-adapter' import { firstValueFrom } from 'rxjs' +import { SubsocialAdapter } from './custom/SubsocialAdapter' const transferAdapters: Record = { polkadot: { @@ -105,6 +106,9 @@ const transferAdapters: Record