Skip to content

Commit

Permalink
feat(sdk-core): Add support for pallet/method override ✨
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeldev5 authored and dudo50 committed Dec 29, 2024
1 parent 8987843 commit aa11c6b
Show file tree
Hide file tree
Showing 15 changed files with 290 additions and 34 deletions.
2 changes: 2 additions & 0 deletions apps/xcm-api/src/x-transfer/dto/XTransferDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ export const XTransferDtoSchema = z.object({
ahAddress: z.string().optional(),
currency: CurrencySchema,
xcmVersion: z.enum(versionValues).optional(),
pallet: z.string().optional(),
method: z.string().optional(),
});

export type XTransferDto = z.infer<typeof XTransferDtoSchema>;
110 changes: 110 additions & 0 deletions apps/xcm-api/src/x-transfer/x-transfer.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,54 @@ describe('XTransferService', () => {
service.generateXcmCall(xTransferDto, false, true),
).rejects.toThrow(BadRequestException);
});

it('should throw BadRequestException when pallet or method are not provided', async () => {
const xTransferDto: XTransferDto = {
from: 'Acala',
to: 'Basilisk',
address,
currency,
pallet: 'Balances',
};

await expect(service.generateXcmCall(xTransferDto)).rejects.toThrow(
BadRequestException,
);
});

it('should succeed when both pallet and method are provided', async () => {
const xTransferDto: XTransferDto = {
from: 'Acala',
to: 'Basilisk',
address,
currency,
pallet: 'Balances',
method: 'transfer',
};

const builderMock = {
from: vi.fn().mockReturnThis(),
to: vi.fn().mockReturnThis(),
currency: vi.fn().mockReturnThis(),
address: vi.fn().mockReturnThis(),
xcmVersion: vi.fn().mockReturnThis(),
addToBatch: vi.fn().mockReturnThis(),
customPallet: vi.fn().mockReturnThis(),
build: vi.fn().mockReturnValue(txHash),
};

vi.spyOn(paraspellSdk, 'Builder').mockReturnValue(
builderMock as unknown as ReturnType<typeof paraspellSdk.Builder>,
);

const result = await service.generateXcmCall(xTransferDto);

expect(result).toBe(txHash);
expect(builderMock.customPallet).toHaveBeenCalledWith(
'Balances',
'transfer',
);
});
});

describe('generateBatchXcmCall', () => {
Expand Down Expand Up @@ -786,5 +834,67 @@ describe('XTransferService', () => {
paraspellSdk.Version.V2,
);
});

it('should throw BadRequestException when pallet or method not provided', async () => {
const from: TNode = 'Acala';
const to: TNode = 'Basilisk';

const batchDto: BatchXTransferDto = {
transfers: [
{
from,
to,
address,
currency,
pallet: 'Balances',
},
],
};

await expect(service.generateBatchXcmCall(batchDto)).rejects.toThrow(
BadRequestException,
);
});

it('should succeed when pallet and method are provided', async () => {
const from: TNode = 'Acala';
const to: TNode = 'Basilisk';

const batchDto: BatchXTransferDto = {
transfers: [
{
from,
to,
address,
currency,
pallet: 'Balances',
method: 'transfer',
},
],
};

const builderMock = {
from: vi.fn().mockReturnThis(),
to: vi.fn().mockReturnThis(),
currency: vi.fn().mockReturnThis(),
address: vi.fn().mockReturnThis(),
addToBatch: vi.fn().mockReturnThis(),
xcmVersion: vi.fn().mockReturnThis(),
customPallet: vi.fn().mockReturnThis(),
buildBatch: vi.fn().mockResolvedValue('batch-transaction'),
};

vi.spyOn(paraspellSdk, 'Builder').mockReturnValue(
builderMock as unknown as ReturnType<typeof paraspellSdk.Builder>,
);

const result = await service.generateBatchXcmCall(batchDto);

expect(result).toBe(batchTransaction);
expect(builderMock.customPallet).toHaveBeenCalledWith(
'Balances',
'transfer',
);
});
});
});
33 changes: 32 additions & 1 deletion apps/xcm-api/src/x-transfer/x-transfer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@ import { Extrinsic } from '@paraspell/sdk-pjs';
@Injectable()
export class XTransferService {
async generateXcmCall(
{ from, to, address, ahAddress, currency, xcmVersion }: XTransferDto,
{
from,
to,
address,
ahAddress,
currency,
xcmVersion,
pallet,
method,
}: XTransferDto,
usePapi = false,
isDryRun = false,
) {
Expand Down Expand Up @@ -56,6 +65,10 @@ export class XTransferService {
);
}

if ((pallet && !method) || (!pallet && method)) {
throw new BadRequestException('Both pallet and method are required.');
}

const Sdk = usePapi
? await import('@paraspell/sdk')
: await import('@paraspell/sdk-pjs');
Expand All @@ -78,6 +91,10 @@ export class XTransferService {
finalBuilder = finalBuilder.xcmVersion(xcmVersion);
}

if (pallet && method) {
finalBuilder = finalBuilder.customPallet(pallet, method);
}

try {
const tx = await finalBuilder.build();

Expand Down Expand Up @@ -175,6 +192,13 @@ export class XTransferService {
throw new BadRequestException('Invalid wallet address.');
}

if (
(transfer.pallet && !transfer.method) ||
(!transfer.pallet && transfer.method)
) {
throw new BadRequestException('Both pallet and method are required.');
}

let finalBuilder:
| SdkPapiType.IUseKeepAliveFinalBuilder
| SdkType.IUseKeepAliveFinalBuilder;
Expand All @@ -189,6 +213,13 @@ export class XTransferService {
finalBuilder = finalBuilder.xcmVersion(transfer.xcmVersion);
}

if (transfer.pallet && transfer.method) {
finalBuilder = finalBuilder.customPallet(
transfer.pallet,
transfer.method,
);
}

builder = finalBuilder.addToBatch();
}

Expand Down
19 changes: 19 additions & 0 deletions apps/xcm-api/test/app.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,25 @@ describe('XCM API (e2e)', () => {
})
.expect(400);
});

it(`Generate XCM call - Parachain to relaychain override pallet and method - ${xTransferUrl}`, async () => {
const from: TNode = 'AssetHubKusama';
const currency = {
symbol: 'KSM',
amount,
};
return request(app.getHttpServer())
.post(xTransferUrl)
.send({
from,
to: 'Kusama',
currency,
address,
pallet: 'Balances',
method: 'transfer',
})
.expect(500);
});
});

describe('Router controller', () => {
Expand Down
18 changes: 17 additions & 1 deletion packages/sdk-core/src/builder/Builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export class GeneralBuilder<TApi, TRes>
private _ahAddress?: string
private _destApi: IPolkadotApi<TApi, TRes>
private _version?: Version
private _pallet?: string
private _method?: string

constructor(
private readonly batchManager: BatchTransactionManager<TApi, TRes>,
Expand Down Expand Up @@ -135,6 +137,18 @@ export class GeneralBuilder<TApi, TRes>
return this
}

/**
* Sets a custom pallet for the transaction.
*
* @param palletName - The name of the custom pallet to be used.
* @returns An instance of the Builder.
*/
customPallet(pallet: string, method: string): this {
this._pallet = pallet
this._method = method
return this
}

private createOptions(): TSendOptions<TApi, TRes> {
return {
api: this.api,
Expand All @@ -145,7 +159,9 @@ export class GeneralBuilder<TApi, TRes>
paraIdTo: this._paraIdTo,
destApiForKeepAlive: this._destApi,
version: this._version,
ahAddress: this._ahAddress
ahAddress: this._ahAddress,
pallet: this._pallet,
method: this._method
}
}

Expand Down
35 changes: 26 additions & 9 deletions packages/sdk-core/src/nodes/ParachainNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import type {
TXTokensTransferOptions,
TRelayToParaOverrides,
TAmount,
TRelayToParaOptions
TRelayToParaOptions,
TPallet
} from '../types'
import { Version, Parents } from '../types'
import { generateAddressPayload, getFees, isRelayChain } from '../utils'
Expand Down Expand Up @@ -92,8 +93,18 @@ abstract class ParachainNode<TApi, TRes> {
}

async transfer(options: TSendInternalOptions<TApi, TRes>): Promise<TRes> {
const { api, asset, address, destination, paraIdTo, overriddenAsset, version, ahAddress } =
options
const {
api,
asset,
address,
destination,
paraIdTo,
overriddenAsset,
version,
ahAddress,
pallet,
method
} = options
const isRelayDestination = !isTMultiLocation(destination) && isRelayChain(destination)
const scenario: TScenario = isRelayDestination ? 'ParaToRelay' : 'ParaToPara'
const paraId =
Expand Down Expand Up @@ -128,7 +139,9 @@ abstract class ParachainNode<TApi, TRes> {
scenario,
paraIdTo: paraId,
destination,
overriddenAsset
overriddenAsset,
pallet,
method
}

if (shouldUseMultiasset) {
Expand All @@ -144,7 +157,9 @@ abstract class ParachainNode<TApi, TRes> {
paraId,
origin: this.node,
destination,
overriddenAsset
overriddenAsset,
pallet,
method
})
} else if (supportsPolkadotXCM(this)) {
return this.transferPolkadotXCM({
Expand Down Expand Up @@ -172,7 +187,9 @@ abstract class ParachainNode<TApi, TRes> {
paraIdTo: paraId,
overriddenAsset,
version,
ahAddress
ahAddress,
pallet,
method
})
} else {
throw new NoXCMSupportImplementedError(this._node)
Expand All @@ -184,11 +201,11 @@ abstract class ParachainNode<TApi, TRes> {
}

transferRelayToPara(options: TRelayToParaOptions<TApi, TRes>): TSerializedApiCall {
const { version = Version.V3 } = options
const { version = Version.V3, pallet, method } = options
const { section, includeFee } = this.getRelayToParaOverrides()
return {
module: 'XcmPallet',
section,
module: (pallet as TPallet) ?? 'XcmPallet',
section: method ?? section,
parameters: constructRelayToParaParameters(options, version, { includeFee })
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { DEFAULT_FEE_ASSET } from '../../constants'
import { isTMultiLocation } from '../xcmPallet/utils'
import type { TPolkadotXcmSection, TSerializedApiCall } from '../../types'
import type { TPallet, TPolkadotXcmSection, TSerializedApiCall } from '../../types'
import { type TPolkadotXCMTransferOptions } from '../../types'

class PolkadotXCMTransferImpl {
Expand All @@ -12,7 +12,9 @@ class PolkadotXCMTransferImpl {
header,
addressSelection,
currencySelection,
overriddenAsset
overriddenAsset,
pallet,
method
}: TPolkadotXCMTransferOptions<TApi, TRes>,
section: TPolkadotXcmSection,
fees: 'Unlimited' | { Limited: string } | undefined = undefined
Expand All @@ -23,8 +25,8 @@ class PolkadotXCMTransferImpl {
: overriddenAsset.findIndex(asset => asset.isFeeAsset)

const call: TSerializedApiCall = {
module: 'PolkadotXcm',
section,
module: (pallet as TPallet) ?? 'PolkadotXcm',
section: method ?? section,
parameters: {
dest: header,
beneficiary: addressSelection,
Expand Down
Loading

0 comments on commit aa11c6b

Please sign in to comment.