End-to-End Examples
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.js/client/';
import type { SuiObjectRef, SuiObjectResponse } from '@mysten/sui.js/client/';
import { TransactionBlock } from '@mysten/sui.js/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 txb = new TransactionBlock();
const coinToPay = await client.getObject({ id: coinObjectId });
const newcoins1 = txb.splitCoins(txb.gas, [txb.pure(300000000)]);
// const newcoins2 = txb.splitCoins(txb.gas, [txb.pure(300000000)]);
txb.transferObjects(
[
newcoins1,
// newcoins2
],
txb.pure(getKeyPair(yourAddressSecretKey).getPublicKey().toSuiAddress()),
);
txb.setGasPayment([toSuiObjectRef(coinToPay)]);
txb.setGasBudget(100000000);
await client
.signAndExecuteTransactionBlock({
signer: getKeyPair(yourAddressSecretKey),
transactionBlock: txb,
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 txb: TransactionBlockWithLambda;
for (let i = 0; i < 10; i++) {
txb = new TransactionBlockWithLambda(
() => createPaymentTxb('<recipient-address>'), // Use a test user address to receive the txbs
);
promises.push(
eshandler.execute(
txb,
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 mintNFTTxb
function that
creates and returns the transaction block of type TransactionBlockWithLambda
.
function mintNFTTxb(
env: EnvironmentVariables,
adminKeypair: Ed25519Keypair,
): TransactionBlockWithLambda {
const txbLambda = (adminCapId: string) => {
const txb = new TransactionBlock();
const hero = txb.moveCall({
arguments: [
txb.object(adminCapId),
txb.pure('zed'),
txb.pure('gold'),
txb.pure(3),
txb.pure('ipfs://example.com/'),
],
target: `${env.NFT_APP_PACKAGE_ID}::hero_nft::mint_hero`,
});
txb.transferObjects([hero], txb.pure(adminKeypair.getPublicKey().toSuiAddress()));
txb.setGasBudget(10000000);
return txb;
};
return new TransactionBlockWithLambda(txbLambda, ['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 txb: TransactionBlockWithLambda;
for (let i = 0; i < NUMBER_OF_TRANSACTION_TO_EXECUTE; i++) {
txb = mintNFTTxb(env, adminKeypair);
promises.push(
eshandler.execute(txb, client, new IncludeAdminCapStrategy('<your-nft-package-id>')),
);
}
const results = await Promise.allSettled(promises);
results.forEach((result) => {
if (result.status === 'rejected') {
console.error(result.reason);
}
});