Skip to main content

Automating Testnet and Futurenet reset data

Overview

Stellar operates two primary testing environments: the Testnet and the Futurenet. These networks allow developers to experiment with Stellar features without risking real assets. Periodically, these networks are reset to ensure they remain clean and manageable.

What is the Testnet and Futurenet reset?

Testnet and Futurenet are reset periodically to the genesis ledger to declutter the network, remove spam, reduce the time needed to catch up on the latest ledger, and help maintain the system. These resets take place approximately quarterly. Resets clear all ledger entries (accounts, trustlines, offers, smart contract data, etc.), transactions, and historical data from Stellar Core, Horizon, and the Soroban RPC, which is why developers should not rely on the persistence of accounts or the state of any balances when using Testnet or Futurenet.

You can check current reset dates here.

Why resets are important

  1. Clean Slate: Regular resets ensure that both Testnet and Futurenet provide a clean environment for testing. This helps in avoiding complications arising from old data or configurations.
  2. Performance: Over time, test environments can accumulate a lot of data, which can slow down performance. Resets help in maintaining optimal performance.
  3. Protocol Updates: Introducing new features or protocol changes often requires a reset to ensure compatibility and stability.
  4. Development Cycles: Aligning with development cycles allows developers to plan their testing phases and ensures they have a reliable environment for their work.

Data automation on Testnet and Futurenet

Automating blockchain state on Stellar's Testnet and Futurenet can streamline development workflows, ensuring that you can consistently test and validate your applications in these environments.

Code walkthrough

Prerequisites:

Code

import * as StellarSdk from "@stellar/stellar-sdk";
import fs from "fs";

//GLOBAL VARIABLES

// const networkRPC = "USE EITHER FUTERNET OR TESTNET RPC"
const networkRPC = "https://soroban-testnet.stellar.org";
// Example
// FOR FUTERENET - https://rpc-futurenet.stellar.org
// FOR TESTNET - https://soroban-testnet.stellar.org

//Initiate Server

const server = new StellarSdk.SorobanRpc.Server(networkRPC);

// const networkURL = "USE EITHER FUTERNET OR TESTNET URL"
const networkURL = "https://friendbot.stellar.org";
// Example
// FOR FUTERENET - https://friendbot-futurenet.stellar.org
// FOR TESTNET - https://friendbot.stellar.org

// const networkParaphrase = "USE EITHER FUTERNET OR TESTNET Paraphrase"
const networkPassPhrase = StellarSdk.Networks.TESTNET;
// Example
// FOR FUTERENET - StellarSdk.Networks.FUTURENET
// FOR TESTNET - StellarSdk.Networks.TESTNET

// This Functions Submits a transactions and then polls for its status until a timeout is reached.
async function yeetTx(feedback, message) {
try {
if (feedback.status !== "PENDING") {
throw feedback;
}

let status;
let attempts = 0;
while (attempts++ < 20) {
// A maximum of 20 polls of transaction status
const tmpStatus = await server.getTransaction(feedback.hash);
switch (tmpStatus.status) {
case "FAILED":
throw tmpStatus;
case "NOT_FOUND":
await sleep(1000); // Wait for one minute
continue;
case "SUCCESS":
status = tmpStatus;
console.log(`${message}: Transaction succeeded`, status);
break;
}
if (status) break;
}
if (attempts >= 20 || !status) {
throw new Error(
`Failed to find transaction ${feedback.hash} after ${attempts} attempts.`,
);
}
return status;
} catch (error) {
console.error("Transaction failed:", error);
throw error;
}
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

//Create an Account
async function createAccount(networkURL, SecretKey) {
if (SecretKey === undefined || null) {
try {
// Generate a keypair
const pair = StellarSdk.Keypair.random();
// Fund the new account using Friendbot
const response = await fetch(
`${networkURL}?addr=${encodeURIComponent(pair.publicKey())}`,
);
const responseJSON = await response.json();
console.log("Account created:", responseJSON);

return pair;
} catch (error) {
console.error("Error creating account:", error);
}
} else {
try {
const pair = StellarSdk.Keypair.fromSecret(SecretKey);
console.log("Account Restored:", pair);
return pair;
} catch (error) {
console.error("Error restoring account:", error);
}
}
}

// Issues an Asset
async function issueAsset(issuerKeys, receivingKeys, customAsset) {
try {
// First, the receiving account must trust the asset
const receiver = await server.getAccount(receivingKeys.publicKey());
let transaction = new StellarSdk.TransactionBuilder(receiver, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: networkPassPhrase,
})
.addOperation(
StellarSdk.Operation.changeTrust({
asset: customAsset,
limit: "100000",
}),
)
// setTimeout is required for a transaction
.setTimeout(100)
.build();
transaction.sign(receivingKeys);
const result = await server.sendTransaction(transaction);
await yeetTx(result, `Receiver Trusting ${customAsset.code} Asset......`);

// Second, the issuing account actually sends a payment using the asset
const issuer = await server.getAccount(issuerKeys.publicKey());
transaction = new StellarSdk.TransactionBuilder(issuer, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: networkPassPhrase,
})
.addOperation(
StellarSdk.Operation.payment({
destination: receivingKeys.publicKey(),
asset: customAsset,
amount: "1000", // change to desired amount you want to pay
}),
)
// setTimeout is required for a transaction
.setTimeout(100)
.build();
transaction.sign(issuerKeys);
const resultFeeback = await server.sendTransaction(transaction);
await yeetTx(
resultFeeback,
`Issuer Payment using ${
customAsset.code
} to ${receivingKeys.publicKey()}`,
);
} catch (e) {
console.error("An error occurred while issuing assets:", e);
}
}

//Create Liquidity Pool
async function createLiquidityPool(accountKeypair, nativeAsset, customAsset) {
try {
const account = await server.getAccount(accountKeypair.publicKey());

// Create the liquidity pool
const poolIdAsset = new StellarSdk.LiquidityPoolAsset(
nativeAsset,
customAsset,
StellarSdk.LiquidityPoolFeeV18,
);
const poolId = poolIdAsset.toString().split(":")[1]; // To Get the Pool ID

const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: networkPassPhrase,
})
.addOperation(
StellarSdk.Operation.changeTrust({
asset: poolIdAsset,
limit: "100000", // Set an appropriate limit
}),
)
.addOperation(
StellarSdk.Operation.liquidityPoolDeposit({
liquidityPoolId: poolId,
maxAmountA: "1000", // Amount of asset A to deposit
maxAmountB: "500", // Amount of asset B to deposit
minPrice: "0.5", // Minimum price ratio
maxPrice: "2.0", // Maximum price ratio
}),
)
.setTimeout(30)
.build();
transaction.sign(accountKeypair);
const resultFeeback = await server.sendTransaction(transaction);

await yeetTx(
resultFeeback,
`Creating Liquidity Pool for ${nativeAsset.code} and ${customAsset.code} `,
);
} catch (error) {
console.error("Error creating liquidity pool:", error);
throw error;
}
}
//Deploy and Invoke Contract
async function deployAndInvokeContract(deployer, contractWasmFilePath, salt) {
try {
// Step 1: Upload WASM
const bytecode = fs.readFileSync(contractWasmFilePath);
const account = await server.getAccount(deployer.publicKey());

const uploadTransaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: networkPassPhrase,
})
.addOperation(StellarSdk.Operation.uploadContractWasm({ wasm: bytecode }))
.setTimeout(30)
.build();

const uploadTx = await server.prepareTransaction(uploadTransaction);
uploadTx.sign(deployer);

console.log("Submitting WASM upload transaction...");
let uploadResponse = await server.sendTransaction(uploadTx);
const result = await yeetTx(uploadResponse, `WASM upload transaction...`);
const wasmHash = result.returnValue._value;
const saltBuffer = Buffer.from(salt, "hex");

const deployerAddress = new StellarSdk.Address(deployer.publicKey());

// Deploy the Contract
const createContractTransaction = new StellarSdk.TransactionBuilder(
account,
{
fee: StellarSdk.BASE_FEE,
networkPassphrase: networkPassPhrase,
},
)
.addOperation(
StellarSdk.Operation.createCustomContract({
address: deployerAddress,
wasmHash: wasmHash,
salt: saltBuffer,
}),
)
.setTimeout(30)
.build();

const createContractTx = await server.prepareTransaction(
createContractTransaction,
);
createContractTx.sign(deployer);
let createContractResponse = await server.sendTransaction(createContractTx);
const returnContractResponse = await yeetTx(
createContractResponse,
`Contract Deployed...`,
);
const contractBuffer = returnContractResponse.returnValue?._value?._value;
const contractId = StellarSdk.StrKey.encodeContract(contractBuffer);
const contract = new StellarSdk.Contract(contractId);

// Invoke Contract
const invokeContractTransaction = new StellarSdk.TransactionBuilder(
account,
{
fee: StellarSdk.BASE_FEE,
networkPassphrase: networkPassPhrase,
},
)
.addOperation(
contract.call("hello", StellarSdk.xdr.ScVal.scvSymbol("World")),
)
.setTimeout(30)
.build();

const invokeContractTx = await server.prepareTransaction(
invokeContractTransaction,
);
invokeContractTx.sign(deployer);
let invokeContractResponse = await server.sendTransaction(invokeContractTx);
const returnInvokeContractResponse = await yeetTx(
invokeContractResponse,
`Invoke Contract.`,
);

const functionValues = returnInvokeContractResponse.returnValue._value
.map((item) => item._value)
.filter(Boolean);

return {
contractId: contractId,
returnValues: functionValues,
};
} catch (error) {
console.error("Error in contract deployment and invocation:", error);
throw error;
}
}

async function automateSetup() {
try {
//Check Network Status
console.log("Checking network health...");
const health = await server.getHealth();
console.log("Network health:", health);

// Flexible Account Configuration
const secretkey =
"SBGGNMUPVF2SDN4KZOJQFVFX7VDR4Q4NK3FMEFRQC64D3UBMFELKF5GC"; // This is an example of a user's secret key

const accountOne = await createAccount(networkURL, secretkey);
const accountTwo = await createAccount(networkURL);

console.log("Issuing an Asset...");
// Issue assets to these accounts
const customAsset = new StellarSdk.Asset("Boya", accountOne.publicKey());
await issueAsset(accountOne, accountTwo, customAsset);

// Create liquidity pool
console.log("Creating liquidity pool...");
const nativeAsset = StellarSdk.Asset.native();
await createLiquidityPool(accountTwo, nativeAsset, customAsset);

// Deploy a contract
console.log("Deploying contract...");
// Ensure you have the contract Wasm file compiled and saved in the specified path.
// Adjust this path as necessary
const contractWasmFilePath =
"./target/wasm32-unknown-unknown/release/hello_world.wasm"; // This is Wasmfile for an Hello World Contract.
const salt =
"2a875e49a81e4e17858abb2d3eb8a7722a657849e08a779eaf06ac85eade15a9"; // KodeSage,The First Principle Engineer was here Fam

const ContractData = await deployAndInvokeContract(
accountOne,
contractWasmFilePath,
salt,
);

console.log("Contract ID:", ContractData.contractId);
console.log("Return Values:", ContractData.returnValues.join(", "));
} catch (error) {
console.error("An error occurred:", error);
}
}

automateSetup();

The code defines several asynchronous functions:

  1. createAccount(networkURL):

Generates a new Stellar keypair (public and secret keys). Uses FriendBot to fund the new account on the test network. Returns the created keypair.

  1. issueAsset(issuerKeys, receivingKeys, customAsset):

Sets up a trust line for the receiving account to accept the custom asset, Issues the custom asset from the issuer account to the receiving account.

  1. createLiquidityPool(accountKeypair, nativeAsset, customAsset):

Creates a liquidity pool asset, Sets up a trust line for the pool and Deposits initial liquidity into the pool.

  1. deployAndInvokeContract(deployer, contractWasmFilePath, salt):

Uploads the contract's webAssembly (wasm) code, creates and deploys the contract on the network, invokes the contract function and returns the contract ID and function return values

  1. automateSetup():

Initializes the Stellar server connection, Creates two accounts, Issues a custom asset, Creates a liquidity pool, Deploys a smart contract and returns the contract ID and function values.

Helper functions

sleep(ms): A utility function to introduce delays in asynchronous operations.

yeetTx(feedback, message): provides more detailed logging of transaction results.

Conclusion

Automating the setup of data on the Stellar Testnet and Futurenet can significantly enhance your development workflow, ensuring that you can quickly return to testing after a network reset. By following the above steps and using the provided code samples, you can streamline your processes and maintain consistency across resets.