Interact with X and P-Chain
This guide builds a P-Chain delegation — staking AVAX to an existing validator — from a dapp, using AvalancheJS to construct the transaction and Core to sign and broadcast it. The same shape applies to any X or P-Chain operation.
Work on Fuji first: enable testnet mode in Core and fund your account from the faucet.
1. Derive the user's P-Chain address
import { utils, secp256k1 } from '@avalabs/avalanchejs';
const { xp } = await provider.request({
method: 'avalanche_getAccountPubKey',
});
const pubKeyBytes = utils.hexToBuffer(xp);
const address = utils.formatBech32(
'fuji', // 'avax' on mainnet
secp256k1.publicKeyBytesToAddress(pubKeyBytes),
);
const pAddress = `P-${address}`;
avalanche_getAccountPubKey prompts the user once; the xp key derives
their X and P-Chain addresses.
2. Build the unsigned delegation
import { pvm, utils, Context, networkIDs } from '@avalabs/avalanchejs';
const AVAX_PUBLIC_URL = 'https://api.avax-test.network';
const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL);
const context = await Context.getContextFromURI(AVAX_PUBLIC_URL);
const { utxos } = await pvmApi.getUTXOs({ addresses: [pAddress] });
const startTime = BigInt(Math.floor(Date.now() / 1000) + 60);
const endTime = startTime + 86400n * 14n; // two weeks
const unsignedTx = pvm.newAddPermissionlessDelegatorTx(
context,
utxos,
[utils.bech32ToBytes(pAddress)],
'NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg', // validator to delegate to
networkIDs.PrimaryNetworkID.toString(),
startTime,
endTime,
BigInt(1e9), // 1 AVAX, in nAVAX
[utils.bech32ToBytes(pAddress)], // rewards address
);
const transactionHex = utils.bufferToHex(unsignedTx.toBytes());
3. Hand it to Core
const txId = await provider.request({
method: 'avalanche_sendTransaction',
params: { transactionHex, chainAlias: 'P' },
});
Core decodes the transaction and shows the user what it actually is — the
validator node, the staked amount, the end date — then signs and broadcasts.
The returned txId is visible on the
P-Chain explorer once accepted.
4. Confirm acceptance
const { status } = await pvmApi.getTxStatus({ txID: txId });
// 'Committed' means the delegation is live.
Variations
- X-Chain transfer: build with
avm.newExportTx/ AVM helpers and passchainAlias: 'X'. - Sign without broadcasting:
avalanche_signTransactionreturns the signed bytes for you to issue through your own node. - UTXO control: pass
utxosand index arrays explicitly — see the method reference.