Skip to content

Latest commit

 

History

History
179 lines (131 loc) · 7.07 KB

File metadata and controls

179 lines (131 loc) · 7.07 KB

Paying Transaction Fees with the Asset Conversion Pallet using Polkadot-JS

Introduction

The Asset Conversion Pallet allows us to use a Non-Native Asset to create a Liquidity Pool with a Native Asset or another Non-Native Asset (but this option is yet to be implemented on a System Chain). This in turn grants us the possibility of using that Non-Native Asset to pay for transaction fees via the ChargeAssetConversionTxPayment signed extension, as long as it has a Liquidity Pool against a Native Asset.

Here we aim to illustrate how to use the ChargeAssetConversionTxPayment signed extension to pay for the fees of a balances.transfer_keep_alive() call with a Non-Native Asset. For this, we will first create and mint the asset using the Assets Pallet, and then we'll create a Liquidity Pool against the Native Asset and add liquidity to it using the Asset Conversion Pallet.

Environment

For this example we will use polkadot-js. At the time of writing the ChargeAssetConversionTxPayment signed extension is not operational in the polkadot-js api and subsequently it's not available in the tools that use the api, such as txwrapper-core. This issue is patched in our example, but a more permanent fix is expected: Same as with the assetConversionApi.quotePriceExactTokensForTokens that expects XcmV3MultiLocation for the assets, while Asset Hub Westend only supports MultiLocation, problem patched by passing our own definition of assetConversionApi.quotePriceExactTokensForTokens at the time of creation of the ApiPromise.

We will also use zombienet to spawn the Westend Relay Chain and Westend Asset Hub nodes. For this we use the binaries built from the polkadot-sdk repo, with the consideration of building the polkadot binary with the flag --features=fast-runtime, in order to decrease the epoch time. We also need to build the three binaries for running the nodes as local.

Usage

To run this example first you need to have a zombienet running. For this, from the root directory run:

 ~ ./zombienet/<zombienet-binary-for-your-OS> -p native spawn ./zombienet/westend_network.toml 

Then, cd into the polkadot-js directory and run:

 ~ yarn example

And there you go, you can check the outputs for the different stages of the example.

Description

Setup

Asset Hub Rococo doesn't support XcmV3MultiLocation, so the default implementation of the assetConversionApi will fail, for that we have to inject a version which uses MultiLocation instead, so that we can use assetConversionApi.quotePriceExactTokensForTokens. This correction is injected as follows:

const api = await ApiPromise.create({
        provider: wsProvider,
        typesBundle: apiConfigRuntime,
    },
    );

With the modified version of assetConversionApi.quotePriceExactTokensForTokens constructed as follows:

const apiConfigRuntime = {
	spec: {
		statemine: {
			runtime: {
				AssetConversionApi: [
					{
						methods: {
							quote_price_exact_tokens_for_tokens: {
								description: 'Quote price: tokens for exact tokens',
								params: [
									{
										name: 'asset1',
										type: 'MultiLocation',
									},
									{
										name: 'asset2',
										type: 'MultiLocation',
									},
									{
										name: 'amount',
										type: 'u128',
									},
									{
										name: 'include_fee',
										type: 'bool',
									},
								],
								type: 'Option<(Balance)>',
							},
						},
						version: 1,
					},
				],
			},
		},
	},
};

Asset and Liquidity Pool Creation

After that, we proceed to create a batch of transactions in which we create the asset and set its metadata, as well as creating the liquidity pool and adding liquidity to it, minting liquidity pool tokens, after defining our Native and Custom Assets in the shape of MultiLocations:

const asset = {
	parents: 0,
	interior: {
		X2: [
			{ palletInstance: 50 },
			{ generalIndex: ASSET_ID },
		]
	}

};

const native = {
	parents: 1,
	interior: {
		Here: '',
	},
};

const setupTxs = [];
const create = api.tx.assets.create(ASSET_ID, alice.address, ASSET_MIN);
const setMetadata = api.tx.assets.setMetadata(ASSET_ID, ASSET_NAME, ASSET_TICKER, ASSET_DECIMALS);
const mint = api.tx.assets.mint(ASSET_ID, alice.address, 100000000);
const createPool = api.tx.assetConversion.createPool(native, asset);
const addLiquidity = api.tx.assetConversion.addLiquidity(native, asset, 1000000000000, 500000, 0, 0, alice.address);

setupTxs.push(create);
setupTxs.push(setMetadata);
setupTxs.push(mint);
setupTxs.push(createPool);
setupTxs.push(addLiquidity);

await api.tx.utility.batchAll(setupTxs).signAndSend(alice);

Here we can see when our Liqudity Pool was created:

And here when the liqudity was added and the liquidity pool tokens were issued:

We also want to estimate how much the fees will be for our transaction, for which we use paymentInfo():

const transferInfo = await api.tx.balances.transferKeepAlive(bob.address, 2000000).paymentInfo(alice);

Now we have the fee estimation, we can estimate the fee in the Non-Native Asset through the runtime api assetConversionApi.quotePriceExactTokensForTokens:

const convertedFee = await api.call.assetConversionApi.quotePriceExactTokensForTokens(native, asset, transferInfo.partialFee, true);

Transaction and fee payment

Now we can finally make our transfer and pay the fees with our Non-Native Asset. For this we just have to specify the MultiLocation of our Non-Native Asset as the assetId:

	const tx = await api.tx.balances
		.transferKeepAlive(bob.address, 2000000)
		.signAsync(alice, { assetId: asset });

	tx.send(alice)

To then send the transfer:

And here we can see when the Tx Fee was paid with our Custom Asset:

And when the swap was made for the payment:

And if we look closely, the amount paid is close to our estimation.

Conclusion

With this, we have succesfully gone through the whole process of creating and minting an asset, creating its own liquidity pool against the Native Asset, and using it to pay the fees of a transaction despite our Custom Asset not being sufficient. This grants more flexibility to the use of Custom Assets in environments where the Asset Conversion Pallet is implemented.

Thank you for your attention and we hope this example was useful.

NOTE: Some pieces of code have been omitted to keep this example at a reasonable length, but the full code can be seen in this repo.