Skip to content

Commit

Permalink
feat: implement transfer all (#434)
Browse files Browse the repository at this point in the history
  • Loading branch information
marshacb authored Sep 23, 2024
1 parent ed3efdb commit 811eba8
Show file tree
Hide file tree
Showing 49 changed files with 605 additions and 287 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ interface TransferArgsOpts<T extends Format> {
* to a `transfer`.
*/
keepAlive?: boolean;
/**
* For creating local asset transfers, this will allow for a `transferAll` as opposed
* to a `transfer`.
*/
transferAll?: boolean;
/**
* Boolean to declare if this will transfer liquidity tokens.
* Default is false.
Expand Down
4 changes: 2 additions & 2 deletions src/AssetTransferApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { adjustedMockMoonriverNoXTokensParachainApi } from './testHelpers/adjust
import { adjustedMockRelayApiNoLimitedReserveTransferAssets } from './testHelpers/adjustedMockRelayApiNoLimitedReserveTransferAssets';
import { adjustedMockRelayApi } from './testHelpers/adjustedMockRelayApiV9420';
import { adjustedMockSystemApi } from './testHelpers/adjustedMockSystemApiV1004000';
import { adjustedMockSystemApiV1014000 } from './testHelpers/adjustedMockSystemApiV1014000';
import { adjustedMockSystemApiV1016000 } from './testHelpers/adjustedMockSystemApiV1016000';
import { mockSystemApi } from './testHelpers/mockSystemApi';
import { mockWeightInfo } from './testHelpers/mockWeightInfo';
import { AssetCallType, Direction, ResolvedCallInfo, UnsignedTransaction, XcmBaseArgs, XcmDirection } from './types';
Expand Down Expand Up @@ -50,7 +50,7 @@ const bifrostAssetsApi = new AssetTransferApi(adjustedMockBifrostParachainApi, '
const moonriverAssetsNoXTokensApi = new AssetTransferApi(adjustedMockMoonriverNoXTokensParachainApi, 'moonriver', 2, {
registryType: 'NPM',
});
const westmintAssetsApi = new AssetTransferApi(adjustedMockSystemApiV1014000, 'westmint', 4, {
const westmintAssetsApi = new AssetTransferApi(adjustedMockSystemApiV1016000, 'westmint', 4, {
registryType: 'NPM',
});

Expand Down
100 changes: 67 additions & 33 deletions src/AssetTransferApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import {
ConstructedFormat,
Direction,
Format,
LocalMethodName,
LocalTransferTypes,
LocalTxChainType,
LocalTxOpts,
Expand All @@ -82,6 +83,7 @@ import {
} from './types';
import { callExistsInRuntime } from './util/callExistsInRuntime';
import { deepEqual } from './util/deepEqual';
import { resolveMultiLocation } from './util/resolveMultiLocation';
import { sanitizeKeys } from './util/sanitizeKeys';
import { validateNumber } from './validate';

Expand Down Expand Up @@ -982,6 +984,8 @@ export class AssetTransferApi {
opts: LocalTxOpts,
) {
const { api, specName } = this;
const { isForeignAssetsTransfer, isLiquidTokenTransfer, keepAlive, transferAll } = opts;
const transferKeepAlive = keepAlive ? keepAlive : false;
let assetId = assetIds[0];
const amount = amounts[0];
const isValidNumber = validateNumber(assetId);
Expand All @@ -1003,7 +1007,15 @@ export class AssetTransferApi {
opts.isForeignAssetsTransfer,
);
}
const method = opts.keepAlive ? 'transferKeepAlive' : 'transfer';
let method: LocalMethodName;
if (transferAll) {
method = 'transferAll';
} else if (keepAlive) {
method = 'transferKeepAlive';
} else {
method = 'transfer';
}
let tx: SubmittableExtrinsic<'promise', ISubmittableResult> | undefined;

if (localTxChainType === LocalTxChainType.System) {
const localAssetType = await checkLocalSystemParachainInput(
Expand All @@ -1013,36 +1025,49 @@ export class AssetTransferApi {
this.specName,
this.registry,
declaredXcmVersion,
opts.isForeignAssetsTransfer,
opts.isLiquidTokenTransfer,
isForeignAssetsTransfer,
isLiquidTokenTransfer,
); // Throws an error when any of the inputs are incorrect.
let tx: SubmittableExtrinsic<'promise', ISubmittableResult> | undefined;
let palletMethod: LocalTransferTypes | undefined;

if (localAssetType === LocalTxType.Balances) {
tx =
method === 'transferKeepAlive'
? balances.transferKeepAlive(api, addr, amount)
: balances.transfer(api, addr, amount);
if (method === 'transferKeepAlive') {
tx = balances.transferKeepAlive(api, addr, amount);
} else if (method === 'transferAll') {
tx = balances.transferAll(api, addr, transferKeepAlive);
} else {
tx = balances.transfer(api, addr, amount);
}
palletMethod = `balances::${method}`;
} else if (localAssetType === LocalTxType.Assets) {
tx =
method === 'transferKeepAlive'
? assets.transferKeepAlive(api, addr, assetId, amount)
: assets.transfer(api, addr, assetId, amount);
if (method === 'transferKeepAlive') {
tx = assets.transferKeepAlive(api, addr, assetId, amount);
} else if (method === 'transferAll') {
tx = assets.transferAll(api, assetId, addr, transferKeepAlive);
} else {
tx = assets.transfer(api, addr, assetId, amount);
}
palletMethod = `assets::${method}`;
} else if (localAssetType === LocalTxType.PoolAssets) {
tx =
method === 'transferKeepAlive'
? poolAssets.transferKeepAlive(api, addr, assetId, amount)
: poolAssets.transfer(api, addr, assetId, amount);
if (method === 'transferKeepAlive') {
tx = poolAssets.transferKeepAlive(api, addr, assetId, amount);
} else if (method === 'transferAll') {
tx = poolAssets.transferAll(api, assetId, addr, transferKeepAlive);
} else {
tx = poolAssets.transfer(api, addr, assetId, amount);
}
palletMethod = `poolAssets::${method}`;
} else if (localAssetType === LocalTxType.ForeignAssets) {
const location = parseLocationStrToLocation(assetId);
tx =
method === 'transferKeepAlive'
? foreignAssets.transferKeepAlive(api, addr, location, amount)
: foreignAssets.transfer(api, addr, location, amount);
const foreignAssetsXcmVersion = 4;
const location = resolveMultiLocation(JSON.parse(assetId) as AnyJson, foreignAssetsXcmVersion);

if (method === 'transferKeepAlive') {
tx = foreignAssets.transferKeepAlive(api, addr, location, amount);
} else if (method === 'transferAll') {
tx = foreignAssets.transferAll(api, location, addr, transferKeepAlive);
} else {
tx = foreignAssets.transfer(api, addr, location, amount);
}
palletMethod = `foreignAssets::${method}`;
} else {
throw new BaseError(
Expand All @@ -1062,19 +1087,25 @@ export class AssetTransferApi {
*/
if (localAssetType === LocalTxType.Balances) {
const palletMethod: LocalTransferTypes = `balances::${method}`;
const tx =
method === 'transferKeepAlive'
? balances.transferKeepAlive(api, addr, amount)
: balances.transfer(api, addr, amount);
if (method === 'transferKeepAlive') {
tx = balances.transferKeepAlive(api, addr, amount);
} else if (method === 'transferAll') {
tx = balances.transferAll(api, addr, transferKeepAlive);
} else {
tx = balances.transfer(api, addr, amount);
}
return this.constructFormat(tx, 'local', null, palletMethod, destChainId, specName, {
...opts,
});
} else if (localAssetType === LocalTxType.Tokens) {
const palletMethod: LocalTransferTypes = `tokens::${method}`;
const tx =
method === 'transferKeepAlive'
? tokens.transferKeepAlive(api, addr, assetIds[0], amount)
: tokens.transfer(api, addr, assetIds[0], amount);
if (method === 'transferKeepAlive') {
tx = tokens.transferKeepAlive(api, addr, assetIds[0], amount);
} else if (method === 'transferAll') {
tx = tokens.transferAll(api, assetIds[0], addr, transferKeepAlive);
} else {
tx = tokens.transfer(api, addr, assetIds[0], amount);
}
return this.constructFormat(tx, 'local', null, palletMethod, destChainId, specName, {
...opts,
});
Expand All @@ -1090,10 +1121,13 @@ export class AssetTransferApi {
* By default local transaction on a relay chain will always be from the balances pallet
*/
const palletMethod: LocalTransferTypes = `balances::${method}`;
const tx =
method === 'transferKeepAlive'
? balances.transferKeepAlive(api, addr, amount)
: balances.transfer(api, addr, amount);
if (method === 'transferKeepAlive') {
tx = balances.transferKeepAlive(api, addr, amount);
} else if (method === 'transferAll') {
tx = balances.transferAll(api, addr, transferKeepAlive);
} else {
tx = balances.transfer(api, addr, amount);
}
return this.constructFormat(tx, 'local', null, palletMethod, destChainId, specName, {
...opts,
});
Expand Down
5 changes: 5 additions & 0 deletions src/config/disabledOpts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export const disabledOpts: DisabledOptions = {
chains: [],
error: (opt: string, chain: string) => callError(opt, chain),
},
transferAll: {
disabled: false,
chains: [],
error: (opt: string, chain: string) => callError(opt, chain),
},
transferLiquidToken: {
disabled: false,
chains: [],
Expand Down
1 change: 1 addition & 0 deletions src/createCalls/assets/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2023 Parity Technologies (UK) Ltd.

export { transfer } from './transfer';
export { transferAll } from './transferAll';
export { transferKeepAlive } from './transferKeepAlive';
2 changes: 1 addition & 1 deletion src/createCalls/assets/transfer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transfer } from './transfer';

describe('transfer', () => {
describe('assets::transfer', () => {
it('Should construct a valid transfer extrinsic', () => {
const res = transfer(
mockSystemApi,
Expand Down
16 changes: 16 additions & 0 deletions src/createCalls/assets/transferAll.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2024 Parity Technologies (UK) Ltd.

import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transferAll } from './transferAll';

describe('assets::transferAll', () => {
it('Should construct a valid assets pallet transferAll extrinsic', () => {
const res = transferAll(
mockSystemApi,
'1',
'0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b',
true,
);
expect(res.toHex()).toEqual('0x980432200400f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01');
});
});
14 changes: 14 additions & 0 deletions src/createCalls/assets/transferAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 Parity Technologies (UK) Ltd.

import type { ApiPromise } from '@polkadot/api';
import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import type { ISubmittableResult } from '@polkadot/types/types';

export const transferAll = (
api: ApiPromise,
assetId: string,
destAddr: string,
keepAlive: boolean,
): SubmittableExtrinsic<'promise', ISubmittableResult> => {
return api.tx.assets.transferAll(assetId, destAddr, keepAlive);
};
2 changes: 1 addition & 1 deletion src/createCalls/assets/transferKeepAlive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transferKeepAlive } from './transferKeepAlive';

describe('transfer', () => {
describe('assets::transfer', () => {
it('Should construct a valid transfer extrinsic', () => {
const res = transferKeepAlive(
mockSystemApi,
Expand Down
1 change: 1 addition & 0 deletions src/createCalls/balances/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2023 Parity Technologies (UK) Ltd.

export { transfer } from './transfer';
export { transferAll } from './transferAll';
export { transferKeepAlive } from './transferKeepAlive';
2 changes: 1 addition & 1 deletion src/createCalls/balances/transfer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transfer } from './transfer';

describe('transfer', () => {
describe('balances::transfer', () => {
it('Should construct a valid transfer extrinsic', () => {
const res = transfer(mockSystemApi, '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', '10000');
expect(res.toHex()).toEqual('0x98040a0000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b419c');
Expand Down
11 changes: 11 additions & 0 deletions src/createCalls/balances/transferAll.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2024 Parity Technologies (UK) Ltd.

import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transferAll } from './transferAll';

describe('balances::transferAll', () => {
it('Should construct a valid balances pallet transferAll extrinsic', () => {
const res = transferAll(mockSystemApi, '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', true);
expect(res.toHex()).toEqual('0x94040a0400f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01');
});
});
13 changes: 13 additions & 0 deletions src/createCalls/balances/transferAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2024 Parity Technologies (UK) Ltd.

import type { ApiPromise } from '@polkadot/api';
import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import type { ISubmittableResult } from '@polkadot/types/types';

export const transferAll = (
api: ApiPromise,
destAddr: string,
keepAlive: boolean,
): SubmittableExtrinsic<'promise', ISubmittableResult> => {
return api.tx.balances.transferAll(destAddr, keepAlive);
};
2 changes: 1 addition & 1 deletion src/createCalls/balances/transferKeepAlive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transferKeepAlive } from './transferKeepAlive';

describe('transfer', () => {
describe('balances::transfer', () => {
it('Should construct a valid transfer extrinsic', () => {
const res = transferKeepAlive(
mockSystemApi,
Expand Down
1 change: 1 addition & 0 deletions src/createCalls/foreignAssets/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2023 Parity Technologies (UK) Ltd.

export { transfer } from './transfer';
export { transferAll } from './transferAll';
export { transferKeepAlive } from './transferKeepAlive';
2 changes: 1 addition & 1 deletion src/createCalls/foreignAssets/transfer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { UnionXcmMultiLocation } from '../../createXcmTypes/types';
import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transfer } from './transfer';

describe('transfer', () => {
describe('foreignAssets::transfer', () => {
it('Should construct a valid foreignAsset transfer extrinsic', () => {
const foreignAsset = {
parents: 1,
Expand Down
33 changes: 33 additions & 0 deletions src/createCalls/foreignAssets/transferAll.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2024 Parity Technologies (UK) Ltd.

import { UnionXcmMultiLocation } from '../../createXcmTypes/types';
import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transferAll } from './transferAll';

describe('foreignAssets::transferAll', () => {
it('Should construct a valid foreignAssets pallet transferAll extrinsic', () => {
const foreignAsset = {
parents: 1,
interior: {
X2: [
{
Parachain: '2125',
},
{
GeneralIndex: '0',
},
],
},
} as UnionXcmMultiLocation;

const res = transferAll(
mockSystemApi,
foreignAsset,
'0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b',
true,
);
expect(res.toHex()).toEqual(
'0xb00435200102003521050000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01',
);
});
});
15 changes: 15 additions & 0 deletions src/createCalls/foreignAssets/transferAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2024 Parity Technologies (UK) Ltd.

import type { ApiPromise } from '@polkadot/api';
import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import type { ISubmittableResult } from '@polkadot/types/types';
import { UnionXcmMultiLocation } from 'src/createXcmTypes/types';

export const transferAll = (
api: ApiPromise,
assetId: UnionXcmMultiLocation,
destAddr: string,
keepAlive: boolean,
): SubmittableExtrinsic<'promise', ISubmittableResult> => {
return api.tx.foreignAssets.transferAll(assetId, destAddr, keepAlive);
};
2 changes: 1 addition & 1 deletion src/createCalls/foreignAssets/transferKeepAlive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { UnionXcmMultiLocation } from '../../createXcmTypes/types';
import { mockSystemApi } from '../../testHelpers/mockSystemApi';
import { transferKeepAlive } from './transferKeepAlive';

describe('transfer', () => {
describe('foreignAssets::transfer', () => {
it('Should construct a valid foreignAssets transferKeepAlive extrinsic', () => {
const foreignAssetMultiLocation = {
parents: 1,
Expand Down
1 change: 1 addition & 0 deletions src/createCalls/poolAssets/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2023 Parity Technologies (UK) Ltd.

export { transfer } from './transfer';
export { transferAll } from './transferAll';
export { transferKeepAlive } from './transferKeepAlive';
Loading

0 comments on commit 811eba8

Please sign in to comment.