Skip to content

Commit

Permalink
fix(sdk): Properly disconnect auto-created API client ✅
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeldev5 committed Nov 19, 2024
1 parent fc947be commit f5f4a17
Show file tree
Hide file tree
Showing 24 changed files with 481 additions and 229 deletions.
14 changes: 8 additions & 6 deletions apps/playground/src/components/XcmTransfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ const XcmTransfer = () => {

const signer = await getSigner();

try {
const Sdk =
apiType === "PAPI"
? await import("@paraspell/sdk/papi")
: await import("@paraspell/sdk");
const Sdk =
apiType === "PAPI"
? await import("@paraspell/sdk/papi")
: await import("@paraspell/sdk");

const api = await Sdk.createApiInstanceForNode(from);
const api = await Sdk.createApiInstanceForNode(from);

try {
let tx: Extrinsic | TPapiTransaction;
if (useApi) {
tx = await getTxFromApi(
Expand Down Expand Up @@ -178,6 +178,8 @@ const XcmTransfer = () => {
}
} finally {
setLoading(false);
if ("disconnect" in api) await api.disconnect();
else api.destroy();
}
};

Expand Down
2 changes: 2 additions & 0 deletions packages/sdk/src/api/IPolkadotApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ export interface IPolkadotApi<TApi, TRes> {
getFromStorage(key: string): Promise<string>
clone(): IPolkadotApi<TApi, TRes>
createApiForNode(node: TNodeWithRelayChains): Promise<IPolkadotApi<TApi, TRes>>
setDisconnectAllowed(allowed: boolean): void
disconnect(): Promise<void>
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ vi.mock('./buildClaimAssetsInput', () => ({
describe('claimAssets', () => {
const apiMock = {
init: vi.fn(),
callTxMethod: vi.fn()
callTxMethod: vi.fn(),
disconnect: vi.fn()
} as unknown as IPolkadotApi<ApiPromise, Extrinsic>

const nodeMock = 'Acala'
Expand Down
34 changes: 20 additions & 14 deletions packages/sdk/src/pallets/assets/asset-claim/assetClaim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { TPallet } from '../../../types'
import { type TSerializedApiCall } from '../../../types'
import { type TAssetClaimOptions } from '../../../types/TAssetClaim'
import { isRelayChain } from '../../../utils'
import { isPjsClient } from '../../../utils/isPjsClient'
import { buildClaimAssetsInput } from './buildClaimAssetsInput'

export const claimAssets = async <TApi, TRes>(
Expand All @@ -11,23 +12,28 @@ export const claimAssets = async <TApi, TRes>(

await api.init(node)

const args = buildClaimAssetsInput<TApi, TRes>(options)
try {
const args = buildClaimAssetsInput<TApi, TRes>(options)

const module: TPallet = isRelayChain(node) ? 'XcmPallet' : 'PolkadotXcm'
const module: TPallet = isRelayChain(node) ? 'XcmPallet' : 'PolkadotXcm'

const call = {
module,
section: 'claim_assets',
parameters: args
}
const call = {
module,
section: 'claim_assets',
parameters: args
}

if (serializedApiCallEnabled === true) {
return {
...call,
// Keep compatible with the old SerializedCall type
parameters: Object.values(args)
if (serializedApiCallEnabled === true) {
return {
...call,
parameters: Object.values(args)
}
}
}

return api.callTxMethod(call)
return api.callTxMethod(call)
} finally {
if (isPjsClient(api)) {
await api.disconnect()
}
}
}
21 changes: 11 additions & 10 deletions packages/sdk/src/pallets/assets/balance/getAssetBalance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { createApiInstanceForNode } from '../../../utils'
import { getNativeAssetSymbol } from '../assets'
import { getAssetBalance } from './getAssetBalance'
import type { ApiPromise } from '@polkadot/api'
import { getBalanceNative } from './getBalanceNative'
import { getBalanceForeign } from './getBalanceForeign'
import { getBalanceNativeInternal } from './getBalanceNative'
import { getBalanceForeignInternal } from './getBalanceForeign'
import type { IPolkadotApi } from '../../../api/IPolkadotApi'
import type { Extrinsic } from '../../../pjs/types'

Expand All @@ -17,19 +17,20 @@ vi.mock('../assets', () => ({
}))

vi.mock('./getBalanceNative', () => ({
getBalanceNative: vi.fn()
getBalanceNativeInternal: vi.fn()
}))

vi.mock('./getBalanceForeign', () => ({
getBalanceForeign: vi.fn()
getBalanceForeignInternal: vi.fn()
}))

describe('getAssetBalance', () => {
let apiMock: IPolkadotApi<ApiPromise, Extrinsic>

beforeEach(() => {
apiMock = {
init: vi.fn()
init: vi.fn(),
disconnect: vi.fn()
} as unknown as IPolkadotApi<ApiPromise, Extrinsic>
vi.mocked(createApiInstanceForNode).mockResolvedValue(apiMock)
})
Expand All @@ -39,23 +40,23 @@ describe('getAssetBalance', () => {
const node = 'Polkadot'
const currency = { symbol: 'DOT' }
vi.mocked(getNativeAssetSymbol).mockReturnValue('DOT')
vi.mocked(getBalanceNative).mockResolvedValue(BigInt(1000))
vi.mocked(getBalanceNativeInternal).mockResolvedValue(BigInt(1000))

const result = await getAssetBalance({ api: apiMock, address: account, node, currency })
expect(result).toEqual(BigInt(1000))
expect(getBalanceNative).toHaveBeenCalledWith({ address: account, node, api: apiMock })
expect(getBalanceNativeInternal).toHaveBeenCalledWith({ address: account, node, api: apiMock })
})

it('returns the foreign asset balance when the currency symbol does not match the native symbol', async () => {
const account = '0x456'
const node = 'Kusama'
const currency = { symbol: 'KSM' }
vi.mocked(getNativeAssetSymbol).mockReturnValue('DOT')
vi.mocked(getBalanceForeign).mockResolvedValue(BigInt(200))
vi.mocked(getBalanceForeignInternal).mockResolvedValue(BigInt(200))

const result = await getAssetBalance({ api: apiMock, address: account, node, currency })
expect(result).toEqual(BigInt(200))
expect(getBalanceForeign).toHaveBeenCalledWith({
expect(getBalanceForeignInternal).toHaveBeenCalledWith({
address: account,
node,
currency,
Expand All @@ -68,7 +69,7 @@ describe('getAssetBalance', () => {
const node = 'Kusama'
const currency = { symbol: 'XYZ' }
vi.mocked(getNativeAssetSymbol).mockReturnValue('DOT')
vi.mocked(getBalanceForeign).mockResolvedValue(BigInt(0))
vi.mocked(getBalanceForeignInternal).mockResolvedValue(BigInt(0))

const result = await getAssetBalance({ api: apiMock, address: account, node, currency })
expect(result).toEqual(BigInt(0))
Expand Down
22 changes: 17 additions & 5 deletions packages/sdk/src/pallets/assets/balance/getAssetBalance.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { TNodePolkadotKusama } from '../../../types'
import { getNativeAssetSymbol } from '../assets'
import { getBalanceNative } from './getBalanceNative'
import { getBalanceForeign } from './getBalanceForeign'
import { getBalanceNativeInternal } from './getBalanceNative'
import { getBalanceForeignInternal } from './getBalanceForeign'
import type { TGetAssetBalanceOptions } from '../../../types/TBalance'

export const getAssetBalance = async <TApi, TRes>({
export const getAssetBalanceInternal = async <TApi, TRes>({
address,
node,
currency,
Expand All @@ -14,16 +14,28 @@ export const getAssetBalance = async <TApi, TRes>({

const isNativeSymbol =
'symbol' in currency ? getNativeAssetSymbol(node) === currency.symbol : false

return isNativeSymbol
? await getBalanceNative({
? await getBalanceNativeInternal({
address,
node,
api
})
: ((await getBalanceForeign({
: ((await getBalanceForeignInternal({
address,
node: node as TNodePolkadotKusama,
api,
currency
})) ?? BigInt(0))
}

export const getAssetBalance = async <TApi, TRes>(
options: TGetAssetBalanceOptions<TApi, TRes>
): Promise<bigint> => {
const { api } = options
try {
return await getAssetBalanceInternal(options)
} finally {
await api.disconnect()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ describe('getBalanceForeign', () => {
const mockApi = {
init: vi.fn(),
getBalanceForeignXTokens: vi.fn(),
getBalanceForeign: vi.fn()
getBalanceForeign: vi.fn(),
disconnect: vi.fn()
} as unknown as IPolkadotApi<ApiPromise, Extrinsic>

beforeEach(() => {
Expand Down
22 changes: 18 additions & 4 deletions packages/sdk/src/pallets/assets/balance/getBalanceForeign.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { getDefaultPallet } from '../../pallets'
import { getAssetBySymbolOrId } from '../getAssetBySymbolOrId'
import { getBalanceForeignPolkadotXcm } from './getBalanceForeignPolkadotXcm'
import { getBalanceForeignXTokens } from './getBalanceForeignXTokens'
import type { TGetBalanceForeignOptions } from '../../../types/TBalance'
import { InvalidCurrencyError } from '../../../errors'
import { getBalanceForeignXTokens } from './getBalanceForeignXTokens'

export const getBalanceForeign = async <TApi, TRes>({
export const getBalanceForeignInternal = async <TApi, TRes>({
address,
node,
currency,
Expand All @@ -21,10 +21,24 @@ export const getBalanceForeign = async <TApi, TRes>({
throw new InvalidCurrencyError(`Asset ${JSON.stringify(currency)} not found on ${node}`)
}

if (getDefaultPallet(node) === 'XTokens') {
const defaultPallet = getDefaultPallet(node)

if (defaultPallet === 'XTokens') {
return await getBalanceForeignXTokens(api, node, address, asset)
} else if (getDefaultPallet(node) === 'PolkadotXcm') {
} else if (defaultPallet === 'PolkadotXcm') {
return await getBalanceForeignPolkadotXcm(api, node, address, asset)
}

throw new Error('Unsupported pallet')
}

export const getBalanceForeign = async <TApi, TRes>(
options: TGetBalanceForeignOptions<TApi, TRes>
): Promise<bigint> => {
const { api } = options
try {
return await getBalanceForeignInternal(options)
} finally {
await api.disconnect()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,15 @@ vi.mock('../../../utils', () => ({
describe('getBalanceNative', () => {
const apiMock = {
init: vi.fn(),
getBalanceNative: vi.fn()
getBalanceNative: vi.fn(),
disconnect: vi.fn()
} as unknown as IPolkadotApi<ApiPromise, Extrinsic>

it('returns the correct balance when API is provided', async () => {
const address = '0x123'
const node = 'Polkadot'
const apiMock = {
init: vi.fn(),
getBalanceNative: vi.fn().mockResolvedValue(BigInt(1000))
} as unknown as IPolkadotApi<ApiPromise, Extrinsic>

vi.spyOn(apiMock, 'getBalanceNative').mockResolvedValue(BigInt(1000))

const balance = await getBalanceNative({
address,
Expand Down
13 changes: 12 additions & 1 deletion packages/sdk/src/pallets/assets/balance/getBalanceNative.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import type { TGetBalanceNativeOptions } from '../../../types/TBalance'

export const getBalanceNative = async <TApi, TRes>({
export const getBalanceNativeInternal = async <TApi, TRes>({
address,
node,
api
}: TGetBalanceNativeOptions<TApi, TRes>): Promise<bigint> => {
await api.init(node)
return await api.getBalanceNative(address)
}

export const getBalanceNative = async <TApi, TRes>(
options: TGetBalanceNativeOptions<TApi, TRes>
): Promise<bigint> => {
const { api } = options
try {
return await getBalanceNativeInternal(options)
} finally {
await api.disconnect()
}
}
12 changes: 7 additions & 5 deletions packages/sdk/src/pallets/assets/getExistentialDeposit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import {
getMaxNativeTransferableAmount
} from './getExistentialDeposit'
import * as edsMapJson from '../../maps/existential-deposits.json'
import { getBalanceNative } from './balance/getBalanceNative'
import { getBalanceNativeInternal } from './balance/getBalanceNative'
import type { TNodeDotKsmWithRelayChains } from '../../types'
import type { IPolkadotApi } from '../../api/IPolkadotApi'
import type { ApiPromise } from '@polkadot/api'
import type { Extrinsic } from '../../pjs/types'

vi.mock('./balance/getBalanceNative', () => ({
getBalanceNative: vi.fn()
getBalanceNativeInternal: vi.fn()
}))

describe('Existential Deposit and Transferable Amounts', () => {
const apiMock = {} as unknown as IPolkadotApi<ApiPromise, Extrinsic>
const apiMock = {
disconnect: vi.fn()
} as unknown as IPolkadotApi<ApiPromise, Extrinsic>
const mockPalletsMap = edsMapJson as { [key: string]: string }
const mockNode: TNodeDotKsmWithRelayChains = 'Polkadot'
const mockAddress = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'
Expand All @@ -36,7 +38,7 @@ describe('Existential Deposit and Transferable Amounts', () => {

it('should return the correct maximum native transferable amount', async () => {
const mockBalance = BigInt(1000000000000)
vi.mocked(getBalanceNative).mockResolvedValue(mockBalance)
vi.mocked(getBalanceNativeInternal).mockResolvedValue(mockBalance)

const ed = getExistentialDeposit(mockNode)
const expectedMaxTransferableAmount = mockBalance - ed - ed / BigInt(10)
Expand All @@ -50,7 +52,7 @@ describe('Existential Deposit and Transferable Amounts', () => {

it('should return 0 for maximum native transferable amount if balance is too low', async () => {
const mockBalance = BigInt(5000)
vi.mocked(getBalanceNative).mockResolvedValue(mockBalance)
vi.mocked(getBalanceNativeInternal).mockResolvedValue(mockBalance)

const result = await getMaxNativeTransferableAmount(apiMock, mockAddress, mockNode)

Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/pallets/assets/getExistentialDeposit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type TNodeDotKsmWithRelayChains, type TEdJsonMap } from '../../types'
import * as edsMapJson from '../../maps/existential-deposits.json' assert { type: 'json' }
import { getBalanceNative } from './balance/getBalanceNative'
import { getBalanceNativeInternal } from './balance/getBalanceNative'
import type { IPolkadotApi } from '../../api/IPolkadotApi'
const palletsMap = edsMapJson as TEdJsonMap

Expand All @@ -19,7 +19,7 @@ export const getMaxNativeTransferableAmount = async <TApi, TRes>(
node: TNodeDotKsmWithRelayChains
): Promise<bigint> => {
const ed = getExistentialDeposit(node)
const nativeBalance = await getBalanceNative({
const nativeBalance = await getBalanceNativeInternal({
address,
node,
api
Expand Down
Loading

0 comments on commit f5f4a17

Please sign in to comment.