Skip to content

Commit 0097d86

Browse files
Merge pull request #885 from ava-labs/convertSubnetTx
Implement ConvertSubnetTx
2 parents 7d08ce5 + d09a4e7 commit 0097d86

37 files changed

+1071
-118
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ PRIVATE_KEY=...
44
C_CHAIN_ADDRESS=0x...
55
X_CHAIN_ADDRESS=X-fuji...
66
P_CHAIN_ADDRESS=P-fuji...
7-
CORETH_ADDRESS=C-fuji...
7+
CORETH_ADDRESS=C-fuji...
8+
NODE_ID=NodeID-...
9+
BLS_PUBLIC_KEY=0x...
10+
BLS_SIGNATURE=0x...

examples/c-chain/export.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,15 @@ import { bech32ToBytes, hexToBuffer } from '../../src/utils';
44
import { getContextFromURI } from '../../src/vms/context';
55
import { newExportTxFromBaseFee } from '../../src/vms/evm';
66
import { evmapi } from '../chain_apis';
7-
8-
const C_CHAIN_ADDRESS = process.env.C_CHAIN_ADDRESS;
9-
const X_CHAIN_ADDRESS = process.env.X_CHAIN_ADDRESS;
10-
const PRIVATE_KEY = process.env.PRIVATE_KEY;
7+
import { getEnvVars } from '../utils/getEnvVars';
118

129
const main = async () => {
13-
if (!C_CHAIN_ADDRESS || !X_CHAIN_ADDRESS || !PRIVATE_KEY) {
14-
throw new Error('Missing environment variable(s).');
15-
}
10+
const { AVAX_PUBLIC_URL, C_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS } =
11+
getEnvVars();
1612

17-
const provider = new JsonRpcProvider(
18-
process.env.AVAX_PUBLIC_URL + '/ext/bc/C/rpc',
19-
);
13+
const provider = new JsonRpcProvider(AVAX_PUBLIC_URL + '/ext/bc/C/rpc');
2014

21-
const context = await getContextFromURI(process.env.AVAX_PUBLIC_URL);
15+
const context = await getContextFromURI(AVAX_PUBLIC_URL);
2216
const txCount = await provider.getTransactionCount(C_CHAIN_ADDRESS);
2317
const baseFee = await evmapi.getBaseFee();
2418
const xAddressBytes = bech32ToBytes(X_CHAIN_ADDRESS);

examples/c-chain/import.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,14 @@ import { getContextFromURI } from '../../src/vms/context';
44
import { newImportTxFromBaseFee } from '../../src/vms/evm';
55
import { evmapi } from '../chain_apis';
66
import { getChainIdFromContext } from '../utils/getChainIdFromContext';
7-
8-
const C_CHAIN_ADDRESS = process.env.C_CHAIN_ADDRESS;
9-
const CORETH_ADDRESS = process.env.CORETH_ADDRESS;
10-
const PRIVATE_KEY = process.env.PRIVATE_KEY;
7+
import { getEnvVars } from '../utils/getEnvVars';
118

129
const main = async (sourceChain: 'X' | 'P') => {
13-
if (!C_CHAIN_ADDRESS || !CORETH_ADDRESS || !PRIVATE_KEY) {
14-
throw new Error('Missing environment variable(s).');
15-
}
10+
const { AVAX_PUBLIC_URL, C_CHAIN_ADDRESS, PRIVATE_KEY, CORETH_ADDRESS } =
11+
getEnvVars();
1612

1713
const baseFee = await evmapi.getBaseFee();
18-
const context = await getContextFromURI(process.env.AVAX_PUBLIC_URL);
14+
const context = await getContextFromURI(AVAX_PUBLIC_URL);
1915

2016
const { utxos } = await evmapi.getUTXOs({
2117
sourceChain,

examples/generate-keys.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
1-
import { secp256k1, utils } from '../src';
1+
import { info, secp256k1, utils } from '../src';
22
import { Address } from 'micro-eth-signer';
3+
import { getEnvVars } from './utils/getEnvVars';
4+
5+
type NodeIdResponse = {
6+
nodeID: string;
7+
nodePOP: { publicKey: string; proofOfPossession: string };
8+
};
39

410
/**
511
* Generate a new private/public key pair and console log out the needed environment variables
612
* needed to run the examples. Please these values in a `.env` file.
713
*/
814
const main = async () => {
15+
const { AVAX_PUBLIC_URL } = getEnvVars();
16+
917
const privateKey = secp256k1.randomPrivateKey();
1018
const publicKey = secp256k1.getPublicKey(privateKey);
11-
1219
const hrp = 'custom';
1320
const address = utils.formatBech32(
1421
hrp,
1522
secp256k1.publicKeyBytesToAddress(publicKey),
1623
);
1724

25+
const infoApi = new info.InfoApi(AVAX_PUBLIC_URL);
26+
27+
const nodeIdResponse = (await infoApi.getNodeId()) as NodeIdResponse;
28+
1829
console.log('Copy the below values to your `.env` file:');
1930
console.log('------------------------------------------\n');
2031

@@ -26,6 +37,13 @@ const main = async () => {
2637
console.log('X_CHAIN_ADDRESS=', `"X-${address}"`);
2738
console.log('C_CHAIN_ADDRESS=', `"${Address.fromPublicKey(publicKey)}"`);
2839
console.log('CORETH_ADDRESS=', `"C-${address}"`);
40+
41+
console.log('NodeId=', `"${nodeIdResponse.nodeID}"`);
42+
console.log('BLS_PUBLIC_KEY=', `"${nodeIdResponse.nodePOP.publicKey}"`);
43+
console.log(
44+
'BLS_SIGNATURE=',
45+
`"${nodeIdResponse.nodePOP.proofOfPossession}"`,
46+
);
2947
};
3048

3149
main();

examples/p-chain/base.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@ import { bech32ToBytes, hexToBuffer } from '../../src/utils';
44
import { getContextFromURI } from '../../src/vms/context';
55
import { newBaseTx } from '../../src/vms/pvm';
66
import { pvmapi } from '../chain_apis';
7-
8-
const P_CHAIN_ADDRESS = process.env.P_CHAIN_ADDRESS;
9-
const PRIVATE_KEY = process.env.PRIVATE_KEY;
7+
import { getEnvVars } from '../utils/getEnvVars';
108

119
const main = async () => {
12-
if (!P_CHAIN_ADDRESS || !PRIVATE_KEY) {
13-
throw new Error('Missing environment variable(s).');
14-
}
10+
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars();
1511

1612
const { utxos } = await pvmapi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] });
17-
const context = await getContextFromURI(process.env.AVAX_PUBLIC_URL);
13+
const context = await getContextFromURI(AVAX_PUBLIC_URL);
1814

1915
const tx = newBaseTx(context, [bech32ToBytes(P_CHAIN_ADDRESS)], utxos, [
2016
TransferableOutput.fromNative(context.avaxAssetID, BigInt(0.1 * 1e9), [

examples/p-chain/delegate.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@ import { bech32ToBytes, hexToBuffer } from '../../src/utils';
44
import { getContextFromURI } from '../../src/vms/context';
55
import { PVMApi, newAddPermissionlessDelegatorTx } from '../../src/vms/pvm';
66
import { pvmapi } from '../chain_apis';
7-
8-
const P_CHAIN_ADDRESS = process.env.P_CHAIN_ADDRESS;
9-
const PRIVATE_KEY = process.env.PRIVATE_KEY;
7+
import { getEnvVars } from '../utils/getEnvVars';
108

119
const main = async () => {
12-
if (!P_CHAIN_ADDRESS || !PRIVATE_KEY) {
13-
throw new Error('Missing environment variable(s).');
14-
}
10+
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars();
1511

1612
const { utxos } = await pvmapi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] });
17-
const context = await getContextFromURI(process.env.AVAX_PUBLIC_URL);
13+
const context = await getContextFromURI(AVAX_PUBLIC_URL);
1814
const startTime = await new PVMApi().getTimestamp();
1915
const startDate = new Date(startTime.timestamp);
2016
const start = BigInt(startDate.getTime() / 1000);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { addTxSignatures, pvm, utils } from '../../../src';
2+
import { setupEtnaExample } from './utils/etna-helper';
3+
import { ConvertSubnetValidator } from '../../../src/serializable/fxs/pvm/convertSubnetValidator';
4+
import { ProofOfPossession } from '../../../src/serializable/pvm';
5+
import { PChainOwner } from '../../../src/serializable/fxs/pvm/pChainOwner';
6+
import { getEnvVars } from '../../utils/getEnvVars';
7+
8+
const AMOUNT_TO_VALIDATE_AVAX: number = 1;
9+
const BALANCE_AVAX: number = 1;
10+
11+
/**
12+
* Converts a subnet to permissionless subnet.
13+
*
14+
* **Note** A subnet must be created (createSubnetTx) and a chain must be created (createChainTx)
15+
* before a subnet can be converted from permissioned to permissionless.
16+
* @param BLS_PUBLIC_KEY BLS key from info.getNodeID on AVAX_PUBLIC_URL
17+
* @param BLS_SIGNATURE BLS signature from info.getNodeID on AVAX_PUBLIC_URL
18+
* @param NODE_ID the ID of the node from info.getNodeID on AVAX_PUBLIC_URL.
19+
* @param chainId the ID of the chain that is created via `createChainTx`.
20+
* @param subnetId the ID of the subnet that is created via `createSubnetTx`.
21+
* @returns The resulting transaction's ID.
22+
*/
23+
const convertSubnetTxExmaple = async () => {
24+
const {
25+
AVAX_PUBLIC_URL,
26+
P_CHAIN_ADDRESS,
27+
PRIVATE_KEY,
28+
NODE_ID,
29+
BLS_PUBLIC_KEY,
30+
BLS_SIGNATURE,
31+
} = getEnvVars();
32+
const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL);
33+
34+
const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] });
35+
36+
const testPAddr = utils.bech32ToBytes(P_CHAIN_ADDRESS);
37+
38+
const pChainOwner = PChainOwner.fromNative([testPAddr], 1);
39+
40+
const publicKey = utils.hexToBuffer(BLS_PUBLIC_KEY);
41+
42+
const signature = utils.hexToBuffer(BLS_SIGNATURE);
43+
44+
const signer = new ProofOfPossession(publicKey, signature);
45+
46+
const validator = ConvertSubnetValidator.fromNative(
47+
NODE_ID,
48+
BigInt(AMOUNT_TO_VALIDATE_AVAX * 1e9),
49+
BigInt(BALANCE_AVAX * 1e9),
50+
signer,
51+
pChainOwner,
52+
pChainOwner,
53+
);
54+
55+
const tx = pvm.e.newConvertSubnetTx(
56+
{
57+
feeState,
58+
fromAddressesBytes: [testPAddr],
59+
subnetId: '', // subnetId from createSubnetTx
60+
utxos,
61+
chainId: '', // chainId from createChainTx
62+
validators: [validator],
63+
subnetAuth: [0],
64+
address: testPAddr,
65+
},
66+
context,
67+
);
68+
69+
await addTxSignatures({
70+
unsignedTx: tx,
71+
privateKeys: [utils.hexToBuffer(PRIVATE_KEY)],
72+
});
73+
74+
return pvmApi.issueSignedTx(tx.getSignedTx());
75+
};
76+
77+
convertSubnetTxExmaple().then(console.log);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { addTxSignatures, pvm, utils } from '../../../src';
2+
import { setupEtnaExample } from './utils/etna-helper';
3+
import { testGenesisData } from '../../../src/fixtures/transactions';
4+
import { getEnvVars } from '../../utils/getEnvVars';
5+
6+
/**
7+
* Create a new chain on the P-Chain.
8+
*
9+
* **Note** A subnet must be created (createSubnetTx) before a chain can be created.
10+
* @param vmId the platform chain's vmID can be found in the `InfoApi.getVMs` response.
11+
* @param subnetId the ID of the subnet that is created via `createSubnetTx`.
12+
* @param chainName the name of the chain. Can be any string.
13+
* @returns The resulting transaction's ID.
14+
*/
15+
const createChainTxExample = async () => {
16+
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars();
17+
const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL);
18+
19+
const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] });
20+
21+
const testPAddr = utils.bech32ToBytes(P_CHAIN_ADDRESS);
22+
23+
const vmId = 'rWhpuQPF1kb72esV2momhMuTYGkEb1oL29pt2EBXWmSy4kxnT'; // platform vmId
24+
const subnetId = ''; // subnetId from createSubnetTx
25+
26+
const tx = pvm.e.newCreateChainTx(
27+
{
28+
feeState,
29+
fromAddressesBytes: [testPAddr],
30+
utxos,
31+
chainName: 'test chain',
32+
subnetAuth: [0],
33+
subnetId,
34+
vmId,
35+
fxIds: [],
36+
genesisData: testGenesisData,
37+
},
38+
context,
39+
);
40+
41+
await addTxSignatures({
42+
unsignedTx: tx,
43+
privateKeys: [utils.hexToBuffer(PRIVATE_KEY)],
44+
});
45+
46+
return pvmApi.issueSignedTx(tx.getSignedTx());
47+
};
48+
49+
createChainTxExample().then(console.log);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { addTxSignatures, pvm, utils } from '../../../src';
2+
import { getEnvVars } from '../../utils/getEnvVars';
3+
import { setupEtnaExample } from './utils/etna-helper';
4+
5+
const createSubnetTxExample = async () => {
6+
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars();
7+
8+
const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL);
9+
10+
const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] });
11+
12+
const testPAddr = utils.bech32ToBytes(P_CHAIN_ADDRESS);
13+
14+
const tx = pvm.e.newCreateSubnetTx(
15+
{
16+
feeState,
17+
fromAddressesBytes: [testPAddr],
18+
utxos,
19+
subnetOwners: [testPAddr],
20+
},
21+
context,
22+
);
23+
24+
await addTxSignatures({
25+
unsignedTx: tx,
26+
privateKeys: [utils.hexToBuffer(PRIVATE_KEY)],
27+
});
28+
29+
return pvmApi.issueSignedTx(tx.getSignedTx());
30+
};
31+
32+
createSubnetTxExample().then(console.log);

examples/p-chain/etna/utils/etna-helper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Context, Info, pvm } from '../../../../src';
1+
import { Context, info, pvm } from '../../../../src';
22
import type { FeeState } from '../../../../src/vms/pvm';
33

44
export const setupEtnaExample = async (
@@ -12,9 +12,9 @@ export const setupEtnaExample = async (
1212
const pvmApi = new pvm.PVMApi(uri);
1313
const feeState = await pvmApi.getFeeState();
1414

15-
const info = new Info(uri);
15+
const infoApi = new info.InfoApi(uri);
1616

17-
const { etnaTime } = await info.getUpgradesInfo();
17+
const { etnaTime } = await infoApi.getUpgradesInfo();
1818

1919
const etnaDateTime = new Date(etnaTime);
2020
const now = new Date();

0 commit comments

Comments
 (0)