Skip to main content

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 pass chainAlias: 'X'.
  • Sign without broadcasting: avalanche_signTransaction returns the signed bytes for you to issue through your own node.
  • UTXO control: pass utxos and index arrays explicitly — see the method reference.