Skip to content

Commit

Permalink
[ts-sdk] Add transaction command shorthand (MystenLabs#9148)
Browse files Browse the repository at this point in the history
## Description 

This adds shorthand methods for all of the transaction builder commands.
This allows transactions to be written in a more intuitive way, but
still gives us the benefit of having a generic `add` method (which is
useful for external SDKs that want to construct commands, and also for
internal data modeling).

## Test Plan 

Rewrote to use it, and tests should pass.

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.

### Type of Change (Check all that apply)

- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
Jordan-Mysten authored Mar 12, 2023
1 parent 6129443 commit 2905b7d
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 297 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
getExecutionStatusType,
getExecutionStatusError,
Transaction,
Commands,
} from '@mysten/sui.js';
import { useWalletKit, ConnectButton } from '@mysten/wallet-kit';
import { useMutation } from '@tanstack/react-query';
Expand Down Expand Up @@ -74,21 +73,19 @@ export function ModuleFunction({
mutationFn: async ({ params, types }: TypeOf<typeof argsSchema>) => {
const tx = new Transaction();
tx.setGasBudget(2000);
tx.add(
Commands.MoveCall({
target: `${packageId}::${moduleName}::${functionName}`,
typeArguments: types ?? [],
arguments:
params?.map((param, i) =>
getPureSerializationType(
functionDetails.parameters[i],
param
)
? tx.pure(param)
: tx.object(param)
) ?? [],
})
);
tx.moveCall({
target: `${packageId}::${moduleName}::${functionName}`,
typeArguments: types ?? [],
arguments:
params?.map((param, i) =>
getPureSerializationType(
functionDetails.parameters[i],
param
)
? tx.pure(param)
: tx.object(param)
) ?? [],
});
const result = await signAndExecuteTransaction({
transaction: tx,
options: {
Expand Down
22 changes: 10 additions & 12 deletions apps/explorer/tests/utils/localnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,16 @@ export async function mint(address: string) {
const signer = new RawSigner(keypair, provider);

const tx = new Transaction();
tx.add(
Transaction.Commands.MoveCall({
target: '0x2::devnet_nft::mint',
arguments: [
tx.pure('Example NFT'),
tx.pure('An example NFT.'),
tx.pure(
'ipfs://bafkreibngqhl3gaa7daob4i2vccziay2jjlp435cf66vhono7nrvww53ty'
),
],
})
);
tx.moveCall({
target: '0x2::devnet_nft::mint',
arguments: [
tx.pure('Example NFT'),
tx.pure('An example NFT.'),
tx.pure(
'ipfs://bafkreibngqhl3gaa7daob4i2vccziay2jjlp435cf66vhono7nrvww53ty'
),
],
});
tx.setGasBudget(30000);

const result = await signer.signAndExecuteTransaction(tx, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,7 @@ export function TransferNFTForm({ objectId }: { objectId: string }) {
}
const tx = new Transaction();
tx.setGasBudget(DEFAULT_NFT_TRANSFER_GAS_FEE);
tx.add(
Transaction.Commands.TransferObjects(
[tx.object(objectId)],
tx.pure(to)
)
);
tx.transferObjects([tx.object(objectId)], tx.pure(to));
return signer.signAndExecuteTransaction(tx, {
showInput: true,
showEffects: true,
Expand Down
46 changes: 10 additions & 36 deletions apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,7 @@ function TransferCoinPage() {
tx.setGasBudget(formData.gasBudget);

if (formData.isPayAllSui && coinType === SUI_TYPE_ARG) {
tx.add(
Transaction.Commands.TransferObjects(
[tx.gas],
tx.pure(formData.to)
)
);
tx.transferObjects([tx.gas], tx.pure(formData.to));
tx.setGasPayment(
formData.coins
.filter((coin) => coin.coinType === coinType)
Expand All @@ -88,45 +83,24 @@ function TransferCoinPage() {
);

if (coinType === SUI_TYPE_ARG) {
const coin = tx.add(
Transaction.Commands.SplitCoin(
tx.gas,
tx.pure(bigIntAmount)
)
);
tx.add(
Transaction.Commands.TransferObjects(
[coin],
tx.pure(formData.to)
)
);
const coin = tx.splitCoin(tx.gas, tx.pure(bigIntAmount));
tx.transferObjects([coin], tx.pure(formData.to));
} else {
const primaryCoinInput = tx.object(
primaryCoin.coinObjectId
);
if (coins.length) {
// TODO: This could just merge a subset of coins that meet the balance requirements instead of all of them.
tx.add(
Transaction.Commands.MergeCoins(
primaryCoinInput,
coins.map((coin) =>
tx.object(coin.coinObjectId)
)
)
tx.mergeCoins(
primaryCoinInput,
coins.map((coin) => tx.object(coin.coinObjectId))
);
}
const coin = tx.add(
Transaction.Commands.SplitCoin(
primaryCoinInput,
tx.pure(bigIntAmount)
)
);
tx.add(
Transaction.Commands.TransferObjects(
[coin],
tx.pure(formData.to)
)
const coin = tx.splitCoin(
primaryCoinInput,
tx.pure(bigIntAmount)
);
tx.transferObjects([coin], tx.pure(formData.to));
}

return signer.signAndExecuteTransaction(tx, {
Expand Down
40 changes: 17 additions & 23 deletions apps/wallet/src/ui/app/redux/slices/sui-objects/Coin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,15 @@ export class Coin {
try {
const tx = new Transaction();
tx.setGasBudget(DEFAULT_GAS_BUDGET_FOR_STAKE);
const stakeCoin = tx.add(
Transaction.Commands.SplitCoin(tx.gas, tx.pure(amount))
);
tx.add(
Transaction.Commands.MoveCall({
target: '0x2::sui_system::request_add_stake',
arguments: [
tx.object(SUI_SYSTEM_STATE_OBJECT_ID),
stakeCoin,
tx.pure(validator),
],
})
);
const stakeCoin = tx.splitCoin(tx.gas, tx.pure(amount));
tx.moveCall({
target: '0x2::sui_system::request_add_stake',
arguments: [
tx.object(SUI_SYSTEM_STATE_OBJECT_ID),
stakeCoin,
tx.pure(validator),
],
});
return await signer.signAndExecuteTransaction(tx, {
showInput: true,
showEffects: true,
Expand All @@ -135,16 +131,14 @@ export class Coin {
try {
const tx = new Transaction();
tx.setGasBudget(DEFAULT_GAS_BUDGET_FOR_STAKE);
tx.add(
Transaction.Commands.MoveCall({
target: '0x2::sui_system::request_withdraw_stake',
arguments: [
tx.object(SUI_SYSTEM_STATE_OBJECT_ID),
tx.object(stake),
tx.object(stakedSuiId),
],
})
);
tx.moveCall({
target: '0x2::sui_system::request_withdraw_stake',
arguments: [
tx.object(SUI_SYSTEM_STATE_OBJECT_ID),
tx.object(stake),
tx.object(stakedSuiId),
],
});
return await signer.signAndExecuteTransaction(tx, {
showInput: true,
showEffects: true,
Expand Down
21 changes: 21 additions & 0 deletions sdk/typescript/src/builder/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,27 @@ export class Transaction {
return createTransactionResult(index - 1);
}

// Method shorthands:

splitCoin(...args: Parameters<(typeof Commands)['SplitCoin']>) {
return this.add(Commands.SplitCoin(...args));
}
mergeCoins(...args: Parameters<(typeof Commands)['MergeCoins']>) {
return this.add(Commands.MergeCoins(...args));
}
publish(...args: Parameters<(typeof Commands)['Publish']>) {
return this.add(Commands.Publish(...args));
}
moveCall(...args: Parameters<(typeof Commands)['MoveCall']>) {
return this.add(Commands.MoveCall(...args));
}
transferObjects(...args: Parameters<(typeof Commands)['TransferObjects']>) {
return this.add(Commands.TransferObjects(...args));
}
makeMoveVec(...args: Parameters<(typeof Commands)['MakeMoveVec']>) {
return this.add(Commands.MakeMoveVec(...args));
}

/**
* Serialize the transaction to a string so that it can be sent to a separate context.
* This is different from `build` in that it does not serialize to BCS bytes, and instead
Expand Down
42 changes: 20 additions & 22 deletions sdk/typescript/src/framework/sui-system-state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { Commands, Transaction } from '../builder';
import { Transaction } from '../builder';
import { Provider } from '../providers/provider';
import {
getObjectReference,
Expand Down Expand Up @@ -42,17 +42,16 @@ export class SuiSystemStateUtil {
): Promise<Transaction> {
// TODO: validate coin types and handle locked coins
const tx = new Transaction();
const coin = tx.add(Commands.SplitCoin(tx.gas, tx.pure(amount)));
tx.add(
Commands.MoveCall({
target: `${SUI_FRAMEWORK_ADDRESS}::${SUI_SYSTEM_MODULE_NAME}::${ADD_STAKE_FUN_NAME}`,
arguments: [
tx.object(SUI_SYSTEM_STATE_OBJECT_ID),
coin,
tx.pure(validatorAddress),
],
}),
);

const coin = tx.splitCoin(tx.gas, tx.pure(amount));
tx.moveCall({
target: `${SUI_FRAMEWORK_ADDRESS}::${SUI_SYSTEM_MODULE_NAME}::${ADD_STAKE_FUN_NAME}`,
arguments: [
tx.object(SUI_SYSTEM_STATE_OBJECT_ID),
coin,
tx.pure(validatorAddress),
],
});
const coinObjects = await provider.getObjectBatch(coins, {
showOwner: true,
});
Expand All @@ -73,16 +72,15 @@ export class SuiSystemStateUtil {
stakedCoinId: ObjectId,
): Promise<Transaction> {
const tx = new Transaction();
tx.add(
Commands.MoveCall({
target: `${SUI_FRAMEWORK_ADDRESS}::${SUI_SYSTEM_MODULE_NAME}::${WITHDRAW_STAKE_FUN_NAME}`,
arguments: [
tx.object(SUI_SYSTEM_STATE_OBJECT_ID),
tx.object(stake),
tx.object(stakedCoinId),
],
}),
);
tx.moveCall({
target: `${SUI_FRAMEWORK_ADDRESS}::${SUI_SYSTEM_MODULE_NAME}::${WITHDRAW_STAKE_FUN_NAME}`,
arguments: [
tx.object(SUI_SYSTEM_STATE_OBJECT_ID),
tx.object(stake),
tx.object(stakedCoinId),
],
});

return tx;
}
}
5 changes: 2 additions & 3 deletions sdk/typescript/test/e2e/coin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import { describe, it, expect, beforeAll } from 'vitest';
import {
Coin,
Commands,
normalizeSuiObjectId,
ObjectId,
SuiObjectInfo,
Expand All @@ -29,8 +28,8 @@ describe('Coin related API', () => {
tx.setGasBudget(DEFAULT_GAS_BUDGET);
const recieverInput = tx.pure(toolbox.address());
SPLIT_AMOUNTS.forEach((amount) => {
const coin = tx.add(Commands.SplitCoin(tx.gas, tx.pure(amount)));
tx.add(Commands.TransferObjects([coin], recieverInput));
const coin = tx.splitCoin(tx.gas, tx.pure(amount));
tx.transferObjects([coin], recieverInput);
});

// split coins into desired amount
Expand Down
32 changes: 14 additions & 18 deletions sdk/typescript/test/e2e/dev-inspect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect, beforeAll } from 'vitest';
import { RawSigner, Transaction, Commands } from '../../src';
import { RawSigner, Transaction } from '../../src';
import {
DEFAULT_GAS_BUDGET,
publishPackage,
Expand All @@ -24,8 +24,8 @@ describe('Test dev inspect', () => {
it.skip('Dev inspect split + transfer', async () => {
const tx = new Transaction();
tx.setGasBudget(DEFAULT_GAS_BUDGET);
const coin = tx.add(Commands.SplitCoin(tx.gas, tx.pure(10)));
tx.add(Commands.TransferObjects([coin], tx.pure(toolbox.address())));
const coin = tx.splitCoin(tx.gas, tx.pure(10));
tx.transferObjects([coin], tx.pure(toolbox.address()));
await validateDevInspectTransaction(toolbox.signer, tx, 'success');
});

Expand All @@ -34,30 +34,26 @@ describe('Test dev inspect', () => {

const tx = new Transaction();
tx.setGasBudget(DEFAULT_GAS_BUDGET);
const obj = tx.add(
Commands.MoveCall({
target: `${packageId}::serializer_tests::return_struct`,
typeArguments: ['0x2::coin::Coin<0x2::sui::SUI>'],
arguments: [tx.pure(coins[0].objectId)],
}),
);
const obj = tx.moveCall({
target: `${packageId}::serializer_tests::return_struct`,
typeArguments: ['0x2::coin::Coin<0x2::sui::SUI>'],
arguments: [tx.pure(coins[0].objectId)],
});

// TODO: Ideally dev inspect transactions wouldn't need this, but they do for now
tx.add(Commands.TransferObjects([obj], tx.pure(toolbox.address())));
tx.transferObjects([obj], tx.pure(toolbox.address()));

await validateDevInspectTransaction(toolbox.signer, tx, 'success');
});

it('Move Call that aborts', async () => {
const tx = new Transaction();
tx.setGasBudget(DEFAULT_GAS_BUDGET);
tx.add(
Commands.MoveCall({
target: `${packageId}::serializer_tests::test_abort`,
typeArguments: [],
arguments: [],
}),
);
tx.moveCall({
target: `${packageId}::serializer_tests::test_abort`,
typeArguments: [],
arguments: [],
});

await validateDevInspectTransaction(toolbox.signer, tx, 'failure');
});
Expand Down
Loading

0 comments on commit 2905b7d

Please sign in to comment.