Skip to content

Commit 9a43da2

Browse files
authored
feat: XCM native transfer tool call (#58)
+ Add XCM native token transfer Relay <-> Parachain + Fix bug transfer tool call
1 parent c96b3af commit 9a43da2

File tree

31 files changed

+1627
-994
lines changed

31 files changed

+1627
-994
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ jobs:
1717
run: |
1818
echo COREPACK_INTEGRITY_KEYS="$(curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}')" >> $GITHUB_ENV
1919
- run: corepack enable
20+
- name: Install pnpm
21+
run: corepack prepare pnpm@10.6.5 --activate
2022
- uses: actions/setup-node@v4
2123
with:
2224
node-version: 22

examples/telegram-bot/src/TelegramBot.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,14 @@ export class TelegramBot {
5656
const checkBalance = this.agent.getNativeBalanceTool();
5757
// Transfer native tokens to a recipient address on a specific chain.
5858
const transferNative = this.agent.transferNativeTool();
59+
// Transfer native tokens to a recipient address on a specific chain via xcm.
60+
// xcm_transfer_native_asset
61+
const xcmTransferNativeAsset = this.agent.xcmTransferNativeTool();
5962

6063
setupHandlers(this.bot, this.llm, {
6164
checkBalance: checkBalance,
6265
transferNative: transferNative,
66+
xcmTransferNativeAsset: xcmTransferNativeAsset,
6367
});
6468

6569
console.log("Bot initialization complete");

examples/telegram-bot/src/handlers.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,46 @@ const SYSTEM_PROMPT = `I am a Telegram bot powered by PolkadotAgentKit. I can as
99
- Checking proxies (e.g., "check proxies on westend" or "check proxies")
1010
- Transfer tokens through XCM (e.g., "transfer 1 WND to 5CSox4ZSN4SGLKUG9NYPtfVK9sByXLtxP4hmoF4UgkM4jgDJ from west to westend_asset_hub ")
1111
12+
IMPORTANT: When users mention chain names, I must convert them to the correct parameter values using this mapping:
13+
14+
| User Input | Real Param (USE THIS IN TOOL CALLS) |
15+
|------------|-------------------------------------|
16+
| Westend | west |
17+
| Westend Asset Hub | west_asset_hub |
18+
| Polkadot | polkadot |
19+
| Kusama | kusama |
20+
21+
CHAIN NAME CONVERSION RULES:
22+
- Always use the "Real Param" values when calling tools
23+
- "Westend" → "west"
24+
- "Westend Asset Hub" → "west_asset_hub"
25+
- "Polkadot" → "polkadot"
26+
- "Kusama" → "kusama"
27+
28+
For XCM transfers, when the user says:
29+
"transfer X WND to [address] from [source_chain_user_input] to [dest_chain_user_input]"
30+
31+
I must:
32+
1. Convert source chain user input to real param (e.g., "Westend" → "west")
33+
2. Convert destination chain user input to real param (e.g., "Westend Asset Hub" → "west_asset_hub")
34+
3. Use these converted values in the tool call parameters
35+
36+
Example:
37+
User: "transfer 0.1 WND to 5D7jcv6aYbhbYGVY8k65oemM6FVNoyBfoVkuJ5cbFvbefftr from Westend to Westend Asset Hub"
38+
Tool call should use: sourceChain: "west", destChain: "west_asset_hub"
39+
1240
When transferring tokens, please provide:
1341
1. The amount of tokens to transfer (e.g., 1)
1442
2. The address to receive the tokens (e.g., 5CSox4ZSN4SGLKUG9NYPtfVK9sByXLtxP4hmoF4UgkM4jgDJ)
15-
3. The name of the destination chain (e.g., westend, westend_asset_hub)
16-
43+
3. The name of the destination chain (convert to real param)
1744
18-
Suggested syntax: "transfer [amount] token to [chain name] to [address]"
45+
When transferring tokens through XCM, please provide:
46+
1. The amount of tokens to transfer (e.g., 1)
47+
2. The address to receive the tokens (e.g., 5CSox4ZSN4SGLKUG9NYPtfVK9sByXLtxP4hmoF4UgkM4jgDJ)
48+
3. The name of the source chain (convert to real param)
49+
4. The name of the destination chain (convert to real param)
1950
20-
When checking proxies, you can specify the chain (e.g., "check proxies on westend") or
21-
not specify a chain (the first chain will be used by default)
51+
When checking proxies, you can specify the chain (convert to real param) or not specify a chain (the first chain will be used by default)
2252
2353
Please provide instructions, and I will assist you!`;
2454

@@ -34,6 +64,7 @@ export function setupHandlers(
3464
'- Transferring native tokens (e.g., "transfer 1 token to westend_asset_hub to 5CSox4ZSN4SGLKUG9NYPtfVK9sByXLtxP4hmoF4UgkM4jgDJ")\n' +
3565
'- Checking balance (e.g., "check balance on west/polkadot/kusama")\n' +
3666
'- Checking proxies (e.g., "check proxies on westend" or "check proxies")\n' +
67+
'- Transfer tokens through XCM (e.g., "transfer 1 WND to 5CSox4ZSN4SGLKUG9NYPtfVK9sByXLtxP4hmoF4UgkM4jgDJ from west to west_asset_hub ")\n' +
3768
"Try asking something!",
3869
);
3970
});
@@ -60,6 +91,7 @@ export function setupHandlers(
6091
return;
6192
}
6293
const response = JSON.parse(toolMessage.content || "{}");
94+
console.log("response", response);
6395
if (response.error) {
6496
await ctx.reply(`Error: ${response.message}`);
6597
} else {

packages/common/src/api/api.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import {
1616
import { type ClientOptions, getClient } from "../clients/client"
1717

1818
export type LightClients = ClientOptions["lightClients"]
19+
1920
type ApiBase<Id extends ChainId> = Id extends KnownChainId
20-
? TypedApi<Descriptors<Id>>
21+
? // @ts-ignore
22+
TypedApi<Descriptors<Id>>
2123
: TypedApi<ChainDefinition>
2224

2325
export type Api<Id extends ChainId> = ApiBase<Id> & {
@@ -50,6 +52,7 @@ export const getApiInner = async <Id extends ChainId>(
5052
const client = await getClient(chainId, chains, { lightClients })
5153
if (!client) throw new Error(`Could not create client for chain ${chainId}`)
5254

55+
// @ts-ignore
5356
const api = client.getTypedApi(descriptors ?? polkadot) as Api<Id>
5457

5558
api.chainId = chainId as Id

packages/common/src/chains/supported-chains.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ export const polkadotChain = createChain({
1212
id: "polkadot",
1313
name: "Polkadot",
1414
specName: "polkadot",
15-
wsUrls: ["wss://polkadot-rpc.dwellir.com"],
15+
wsUrls: ["wss://polkadot-rpc.n.dwellir.com"],
1616
relay: "polkadot",
1717
type: "relay",
18-
chainId: null,
18+
chainId: 0,
1919
blockExplorerUrl: "https://polkadot.subscan.io",
2020
prefix: 0,
2121
decimals: 10,
@@ -43,7 +43,7 @@ export const westendChain = createChain({
4343
wsUrls: ["wss://westend-rpc.polkadot.io"],
4444
relay: "west",
4545
type: "relay",
46-
chainId: null,
46+
chainId: 0,
4747
blockExplorerUrl: "https://westend.subscan.io",
4848
xcmExtrinsic: "limited_teleport_assets",
4949
prefix: 42, // default

packages/common/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@ export * from "./chains"
33
export * from "./clients"
44
export * from "./types"
55
export * from "./utils"
6-
export * from "./utils"
Lines changed: 12 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,18 @@
1-
import { ed25519 } from "@noble/curves/ed25519"
2-
import type { PolkadotSigner } from "polkadot-api/signer"
3-
import { getPolkadotSigner } from "polkadot-api/signer"
4-
export type Hex = Uint8Array | string
5-
6-
import * as ss58 from "@subsquid/ss58"
7-
8-
export function buildAccountSigner(): PolkadotSigner {
9-
const seed = getPrivateKey(process.env.PRIVATE_KEY) as string
10-
const signer = getPolkadotSigner(ed25519.getPublicKey(seed), "Ed25519", input =>
11-
ed25519.sign(input, seed)
12-
)
13-
return signer
14-
}
15-
16-
export function buildAccountDelegateProxySigner(): PolkadotSigner {
17-
const seed = getPrivateKey(process.env.DELEGATE_PRIVATE_KEY) as string
18-
const signer = getPolkadotSigner(ed25519.getPublicKey(seed), "Ed25519", input =>
19-
ed25519.sign(input, seed)
20-
)
21-
return signer
22-
}
23-
24-
export function getPrivateKey(account?: string) {
25-
return account || process.env.PRIVATE_KEY
26-
}
27-
28-
export function publicKeyOf(seed?: string) {
29-
let privateKey: Hex | undefined = seed || getPrivateKey()
30-
if (!privateKey) {
31-
privateKey = ed25519.utils.randomPrivateKey()
32-
}
33-
return ed25519.getPublicKey(privateKey)
34-
}
35-
36-
/**
37-
* Check if a value is a hex string
38-
* @param value - the value to check
39-
**/
40-
export function isHex(value: unknown): value is string {
41-
return typeof value === "string" && value.length % 2 === 0 && /^0x[\da-f]*$/i.test(value)
42-
}
43-
44-
/**
45-
* Decode an ss58 address from the value
46-
* @param address - the address to decode
47-
**/
48-
export function addressOf(address: Uint8Array): string {
49-
const value = address
50-
if (!value) {
51-
return ""
52-
}
53-
return ss58.codec("polkadot").encode(value)
54-
}
55-
56-
export function addressOfSubstrate(address: Uint8Array): string {
57-
const value = address
58-
if (!value) {
59-
return ""
60-
}
61-
return ss58.codec("substrate").encode(value)
62-
}
63-
641
export interface BalanceInfo {
652
balance: bigint
663
decimals: number
674
symbol: string
685
}
696

70-
// export function toMultiAddress(address: string): MultiAddress {
71-
// return MultiAddress.Id(address);
72-
// }
7+
export interface AccountData {
8+
nonce: number
9+
consumers: number
10+
providers: number
11+
sufficients: number
12+
data: {
13+
free: bigint
14+
reserved: bigint
15+
frozen: bigint
16+
flags: bigint
17+
}
18+
}

packages/core/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,15 @@
3535
"test:watch": "vitest"
3636
},
3737
"dependencies": {
38+
"@paraspell/sdk": "^10.5.2",
3839
"@polkadot-agent-kit/common": "workspace:*",
40+
"@polkadot/util-crypto": "^13.5.1",
3941
"@subsquid/ss58": "^2.0.2",
40-
"polkadot-api": "^1.9.13"
42+
"polkadot-api": "^1.9.13",
43+
"rxjs": "^7.8.2",
44+
"@substrate/asset-transfer-api": "^0.7.2",
45+
"@polkadot/api": "^16.2.2",
46+
"@polkadot/keyring": "^13.5.1"
4147
},
4248
"devDependencies": {
4349
"@babel/plugin-syntax-import-attributes": "^7.26.0",

packages/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// Polkadot Core exports
22
export * from "./api"
33
export * from "./pallets/assets"
4+
export * from "./pallets/xcm"
5+
export * from "./types"
46
export * from "./utils"

packages/core/src/pallets/assets/getNativeBalance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Api, BalanceInfo, KnownChainId } from "@polkadot-agent-kit/common"
1+
import type { AccountData, Api, BalanceInfo, KnownChainId } from "@polkadot-agent-kit/common"
22
import { getAllSupportedChains, getChainById } from "@polkadot-agent-kit/common"
33

44
/**
@@ -11,7 +11,7 @@ export const getNativeBalance = async (
1111
api: Api<KnownChainId>,
1212
address: string
1313
): Promise<BalanceInfo> => {
14-
const balance = await api.query.System.Account.getValue(address)
14+
const balance = (await api.query.System.Account.getValue(address)) as AccountData
1515
const chain = getChainById(api.chainId, getAllSupportedChains())
1616

1717
return {

0 commit comments

Comments
 (0)