Skip to content

Commit

Permalink
Merge pull request #115 from dappforce/subsocial-moonbeam
Browse files Browse the repository at this point in the history
Add xtransfer from subsocial to moonbeam
  • Loading branch information
olehmell authored Dec 5, 2023
2 parents 2012959 + 8042ae9 commit 33c2a31
Show file tree
Hide file tree
Showing 11 changed files with 603 additions and 182 deletions.
18 changes: 11 additions & 7 deletions src/components/transfer/TokenSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,30 @@ const generateAddTokenOption =
tokenMap: Record<string, SearchableSelectOption>,
filterCrossChainBridgeable?: boolean
) =>
(tokenName: string, tokenId?: TokenData['tokenId']) => {
(tokenName: string, tokenId?: TokenData['tokenId'], isEvmWrappedToken?: boolean) => {
const network = showNetwork ? networkName : ''
const token = isEvmWrappedToken ? tokenName : tokenName

const tokenData: TokenData = {
tokenId: showNetwork ? tokenId : undefined,
token: tokenName,
token,
network: showNetwork ? networkId : '',
}


const encodedTokenData = tokenSelectorEncoder.encode(tokenData)


if (tokenMap[encodedTokenData]) return
if (filterCrossChainBridgeable && !isTokenBridgeable(tokenName)) return
if (filterCrossChainBridgeable && !isTokenBridgeable(token)) return

const filterNetwork = showNetwork ? networkName : ''
tokenMap[encodedTokenData] = {
value: encodedTokenData,
label: (
<TokenOption
networkImage={showNetwork ? `/images/${icon}` : undefined}
token={tokenName}
token={token}
network={network}
/>
),
Expand Down Expand Up @@ -130,15 +135,14 @@ export default function TokenSelector ({
if (tokenSymbol === nativeTokenSymbol) return
const currency = asset.currency
if (!currency) return
const tokenId = currency
addTokenOption(tokenSymbol, { id: tokenId })
const tokenId = currency.id || currency
addTokenOption(tokenSymbol, { id: tokenId }, tokenSymbol)
})
}
)
return Object.values(tokenMap)
}, [ chainInfo, showNetwork, myAddress ])


return (
<div
{...props}
Expand Down
10 changes: 8 additions & 2 deletions src/components/transfer/TransferForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ export type TransferFormProps = Omit<FormProps, 'form' | 'children'> & {
) => JSX.Element
}

export const DEFAULT_TOKEN = { network: 'polkadot', token: 'DOT' }
export const DEFAULT_TOKEN = {
network: 'polkadot',
token: 'DOT',
}
type SelectedTokenChainData = TokenData & {
dest?: string
}
Expand Down Expand Up @@ -182,7 +185,9 @@ export default function TransferForm ({
[transferFormField('source')]: undefined,
[transferFormField('dest')]: undefined,
})
setSelectedToken({ token: tokenSelectorEncoder.decode(token).token })
setSelectedToken({
token: tokenSelectorEncoder.decode(token).token,
})
}

const getExtendedTransferData = (): ExtendedTransferFormData => {
Expand Down Expand Up @@ -342,6 +347,7 @@ export default function TransferForm ({
{() => {
if (!crossChain) return null
const fee = getCrossChainFee(form)

if (!fee.balance) return null
return (
<CrossChainFee
Expand Down
3 changes: 2 additions & 1 deletion src/components/transfer/configs/cross-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { InterlayAdapter, KintsugiAdapter } from '@polkawallet/bridge/adapters/i
import { KicoAdapter } from '@polkawallet/bridge/adapters/kico'
import { PichiuAdapter } from '@polkawallet/bridge/adapters/kylin'
import { CalamariAdapter } from '@polkawallet/bridge/adapters/manta'
import { MoonbeamAdapter, MoonriverAdapter } from '@polkawallet/bridge/adapters/moonbeam'
import { /* MoonbeamAdapter, */ MoonriverAdapter } from '@polkawallet/bridge/adapters/moonbeam'
import { KhalaAdapter } from '@polkawallet/bridge/adapters/phala'
import { PolkadotAdapter, KusamaAdapter } from '@polkawallet/bridge/adapters/polkadot'
import { StatemineAdapter } from '@polkawallet/bridge/adapters/statemint'
Expand All @@ -25,6 +25,7 @@ import { TuringAdapter } from '@polkawallet/bridge/adapters/oak'
import { HeikoAdapter, ParallelAdapter } from '@polkawallet/bridge/adapters/parallel'
import { ZeitgeistAdapter } from '@polkawallet/bridge/adapters/zeitgeist'
import { SubsocialAdapter, subsocialTokensConfig } from './custom/SubsocialAdapter'
import { MoonbeamAdapter } from './custom/MoonbeamAdapter'

// Add SUB for Hydra DX
hydraRouteConfigs.push({
Expand Down
205 changes: 205 additions & 0 deletions src/components/transfer/configs/custom/MoonbeamAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
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 { createRouteConfigs, validateAddress } from '@polkawallet/bridge/utils'
import {
BalanceData,
ChainId,
chains,
ExtendedToken,
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'
import { getDestAccountInfo } from './utils/destination-utils'

export const moonbeamRouteConfigs = createRouteConfigs('moonbeam', [
{
to: 'subsocial' as any,
token: 'xcSUB',
xcm: {
fee: { token: 'SUB', amount: '1000000000' },
},
},
])

const moonbeamTokensConfig: Record<string, ExtendedToken> = {
SUB: {
name: 'SUB',
symbol: 'SUB',
decimals: 10,
ed: '100000000000',
toRaw: () => ({
ForeignAsset: '89994634370519791027168048838578580624',
}),
},
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const createBalanceStorages = (api: AnyApi) => {
return {
balances: (address: string) =>
Storage.create<DeriveBalancesAll>({
api,
path: 'derive.balances.all',
params: [ address ],
}),
}
}

class MoonbeamBalanceAdapter extends BalanceAdapter {
private storages: ReturnType<typeof createBalanceStorages>

constructor ({ api, chain, tokens }: BalanceAdapterConfigs) {
super({ api, chain, tokens })
this.storages = createBalanceStorages(api)
}

public subscribeBalance (
token: string,
address: string
): Observable<BalanceData> {
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 MoonbeamBaseAdapter extends BaseCrossChainAdapter {
private balanceAdapter?: MoonbeamBalanceAdapter

public async init (api: AnyApi) {
this.api = api

await api.isReady

const chain = this.chain.id as ChainId

this.balanceAdapter = new MoonbeamBalanceAdapter({
chain,
api,
tokens: moonbeamTokensConfig,
})
}

public subscribeTokenBalance (
token: string,
address: string
): Observable<BalanceData> {
if (!this.balanceAdapter) {
throw new ApiNotFound(this.chain.id)
}

return this.balanceAdapter.subscribeBalance(token, address)
}

public subscribeMaxInput (
token: string,
address: string,
to: ChainId
): Observable<FN> {
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 === undefined) {
throw new ApiNotFound(this.chain.id)
}

const { address, amount, to, token } = params

const { accountId, accountType, addrType } = getDestAccountInfo(
address,
token,
this.api,
to
)

const tokenData = moonbeamTokensConfig[token.replace('xc', '')]

if (!validateAddress(address, addrType)) throw new InvalidAddress(address)

const toChain = chains[to]

return this.api.tx.xTokens.transfer(
tokenData.toRaw(),
amount.toChainData(),
{
V3: {
parents: 1,
interior: {
X2: [
{ Parachain: toChain.paraChainId },
{ [accountType]: { id: accountId, network: undefined } },
],
},
},
} as any,
'Unlimited'
)
}
}

export class MoonbeamAdapter extends MoonbeamBaseAdapter {
constructor () {
super(chains.moonbeam, moonbeamRouteConfigs, moonbeamTokensConfig)
}
}
Loading

0 comments on commit 33c2a31

Please sign in to comment.