TypeScript SDK
Owned Object Pool
End-to-End Examples

End-to-End Examples

⚠️

Sui Owned Object Pools (SuiOOP) will likely be replaced by ParallelTransactionExecutor from @mysten/sui/transactions

Sui Owned Object Pools (SuiOOP) is a beta library. Enhancements and changes are likely during development.

These examples demonstrate some possible use cases for the Sui Owned Object Pool library.

Use case 1: Parallel coin transfers service—Multiple Coins

Assume that you have a service that needs to make payments of size 50000000 MIST to multiple recipients in parallel.

Before creating an ExecutorServiceHandler instance that executes each incoming transaction, you first need to have a set of coins to use for the coin transferring and to pay for the gas of each transaction.

ExecutorServiceHandler creates worker pools that handle the execution of the transactions. The maximum number of worker pools the handler can create is tightly coupled with the number of your account's coins.

Here is the code that creates the coins by splitting a single coin 20 times and transferring the new coins to your address:

import { SuiClient } from '@mysten/sui/client/';
import type { SuiObjectRef, SuiObjectResponse } from '@mysten/sui/client/';
import { Transaction } from '@mysten/sui/transactions';
 
const client = new SuiClient({
	url: 'https://fullnode.testnet.sui.io:443',
});
 
const objectId: string = '<your-coin-object-id>'; // A
const yourAddressSecretKey: string = '<your-address-secret-key>';
 
const numberOfCoinsToCreate = 5;
/// Splits a specific coin and then transfer the new coins to the same address.
for (let i = 0; i < numberOfCoinsToCreate; i++) {
	await splitCoinAndTransferToSelf(client, objectId, yourAddressSecretKey);
}
 
async function splitCoinAndTransferToSelf(
	client: SuiClient,
	coinObjectId: string,
	yourAddressSecretKey: string,
) {
	const tx = new Transaction();
	const coinToPay = await client.getObject({ id: coinObjectId });
	const newcoins1 = tx.splitCoins(tx.gas, [tx.pure(300000000)]);
	// const newcoins2 = tx.splitCoins(tx.gas, [tx.pure(300000000)]);
	tx.transferObjects(
		[
			newcoins1,
			// newcoins2
		],
		tx.pure(getKeyPair(yourAddressSecretKey).getPublicKey().toSuiAddress()),
	);
	tx.setGasPayment([toSuiObjectRef(coinToPay)]);
	tx.setGasBudget(100000000);
	await client
		.signAndExecuteTransaction({
			signer: getKeyPair(yourAddressSecretKey),
			transaction: tx,
			requestType: 'WaitForLocalExecution',
			options: {
				showEffects: true,
			},
		})
		.then((txRes) => {
			const status = txRes.effects?.status?.status;
			if (status !== 'success') {
				throw new Error(`Could not split coin! ${txRes.effects?.status?.error}`);
			}
			return txRes;
		})
		.catch((err) => {
			throw new Error(`Error thrown: Could not split coin!: ${err}`);
		});
}

Now that you have the coins, you can create the ExecutorServiceHandler instance and execute the transactions.

The library provides the DefaultSplitStrategy as a parameter to the ExecutorServiceHandler constructor setting the minimumBalance equal to 300000000, meaning that the worker pool that is created needs to have a set of coins that the sum of their balances is greater than or equal to this.

import { DefaultSplitStrategy, ExecutorServiceHandler } from 'suioop';
 
// Setup the executor service
const eshandler = await ExecutorServiceHandler.initialize(adminKeypair, client);
// Define the number of transactions to execute
const promises = [];
let tx: TransactionBlockWithLambda;
for (let i = 0; i < 10; i++) {
	tx = new TransactionBlockWithLambda(
		() => createPaymenttx('<recipient-address>'), // Use a test user address to receive the txs
	);
	promises.push(
		eshandler.execute(
			tx,
			client,
			new DefaultSplitStrategy(300000000), // Each pool contains coins with a total balance of 300000000 MIST
		),
	);
}
 
// Collect the promise results
const results = await Promise.allSettled(promises);

Providing a SplitStrategy is optional. If you don't provide one, the DefaultSplitStrategy is used, which also has a default minimum balance. For demonstration purposes, the minimum pool balance here is 300000000.

Use case 2: mint and transfer NFTs concurrently using multiple AdminCaps

Assume that you have a service that needs to mint and transfer NFTs concurrently to multiple recipients. Similar to the previous example, you first need to have a set of coins that are used to pay for the gas of each transaction. In addition, you need to have a set of AdminCaps to mint the NFTs.

In addition to the coin creation performed earlier, you need to create multiple AdminCaps for your account.

To do this, you can either use the createAdminCap function in your smart contract, or generate multiple admin caps on package publishing. You can test this by using the move_examples/nft_app example (opens in a new tab). Notice how multiple AdminCaps are created in genesis.move (opens in a new tab):

// ...
// Generate 20 AdminCaps, for parallelization of transactions
let i = 0;
while (i <= 20) {
  // Transfer AdminCap to sender
  transfer::public_transfer(AdminCap {
  id: object::new(ctx)
  }, sender(ctx));
  i = i + 1;
}
// ...

Now that you have the coins and the admin caps, you can define the mintNFTtx function that creates and returns the transaction of type TransactionBlockWithLambda.

function mintNFTtx(
	env: EnvironmentVariables,
	adminKeypair: Ed25519Keypair,
): TransactionBlockWithLambda {
	const txLambda = (adminCapId: string) => {
		const tx = new Transaction();
		const hero = tx.moveCall({
			arguments: [
				tx.object(adminCapId),
				tx.pure('zed'),
				tx.pure('gold'),
				tx.pure(3),
				tx.pure('ipfs://example.com/'),
			],
			target: `${env.NFT_APP_PACKAGE_ID}::hero_nft::mint_hero`,
		});
 
		tx.transferObjects([hero], tx.pure(adminKeypair.getPublicKey().toSuiAddress()));
		tx.setGasBudget(10000000);
		return tx;
	};
	return new TransactionBlockWithLambda(txLambda, ['AdminCap']);
}

Having done the preparatory steps above, it's time to create the ExecutorServiceHandler instance and execute the transactions.

const eshandler = await ExecutorServiceHandler.initialize(adminKeypair, client);
const promises: Promise<SuiTransactionBlockResponse>[] = [];
let tx: TransactionBlockWithLambda;
for (let i = 0; i < NUMBER_OF_TRANSACTION_TO_EXECUTE; i++) {
	tx = mintNFTtx(env, adminKeypair);
	promises.push(
		eshandler.execute(tx, client, new IncludeAdminCapStrategy('<your-nft-package-id>')),
	);
}
 
const results = await Promise.allSettled(promises);
results.forEach((result) => {
	if (result.status === 'rejected') {
		console.error(result.reason);
	}
});