Skip to content

Commit

Permalink
[tto-sdks] Add typescript SDK support and plumb out into json-rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
tzakian committed Jul 15, 2023
1 parent 7e5c518 commit 0be1557
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 13 deletions.
28 changes: 17 additions & 11 deletions crates/sui-json-rpc-types/src/sui_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1530,13 +1530,13 @@ impl SuiCallArg {
initial_shared_version,
mutable,
}),
// TODO(tzakian)[tto]
CallArg::Object(ObjectArg::Receiving(_obj_ref)) => std::todo!(
"Implement SuiCallArg::try_from for CallArg::Object(ObjectArg::Receiving(obj_ref))"
),
// {
// SuiCallArg::Object(SuiObjectArg::Receiving(SuiObjectRef::from(obj_ref)))
// }
CallArg::Object(ObjectArg::Receiving((object_id, version, digest))) => {
SuiCallArg::Object(SuiObjectArg::Receiving {
object_id,
version,
digest,
})
}
})
}

Expand All @@ -1550,7 +1550,8 @@ impl SuiCallArg {
pub fn object(&self) -> Option<&ObjectID> {
match self {
SuiCallArg::Object(SuiObjectArg::SharedObject { object_id, .. })
| SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject { object_id, .. }) => {
| SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject { object_id, .. })
| SuiCallArg::Object(SuiObjectArg::Receiving{ object_id, ..}) => {
Some(object_id)
}
_ => None,
Expand Down Expand Up @@ -1601,10 +1602,15 @@ pub enum SuiObjectArg {
initial_shared_version: SequenceNumber,
mutable: bool,
},
// TODO(tzakian)[tto]: uncomment this when we are ready to expose this to the RPC interface.
// A reference to a Move object that's going to be received in the transaction.
// #[serde(rename_all = "camelCase")]
// Receiving(SuiObjectRef),
#[serde(rename_all = "camelCase")]
Receiving {
object_id: ObjectID,
#[schemars(with = "AsSequenceNumber")]
#[serde_as(as = "AsSequenceNumber")]
version: SequenceNumber,
digest: ObjectDigest,
},
}

#[serde_as]
Expand Down
17 changes: 17 additions & 0 deletions sdk/typescript/src/builder/Inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const ObjectArg = union([
mutable: boolean(),
}),
}),
object({ Receiving: SuiObjectRef }),
]);

export const PureCallArg = object({ Pure: array(integer()) });
Expand Down Expand Up @@ -60,6 +61,17 @@ export const Inputs = {
},
};
},
ReceivingRef({ objectId, digest, version }: SuiObjectRef): ObjectCallArg {
return {
Object: {
Receiving: {
digest,
version,
objectId: normalizeSuiAddress(objectId),
},
},
};
},
};

export function getIdFromCallArg(arg: ObjectId | ObjectCallArg) {
Expand All @@ -69,6 +81,11 @@ export function getIdFromCallArg(arg: ObjectId | ObjectCallArg) {
if ('ImmOrOwned' in arg.Object) {
return normalizeSuiAddress(arg.Object.ImmOrOwned.objectId);
}

if ('Receiving' in arg.Object) {
return normalizeSuiAddress(arg.Object.Receiving.objectId);
}

return normalizeSuiAddress(arg.Object.Shared.objectId);
}

Expand Down
34 changes: 34 additions & 0 deletions sdk/typescript/src/builder/TransactionBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ function createTransactionResult(index: number): TransactionResult {
}) as TransactionResult;
}

function isReceivingType(normalizedType: SuiMoveNormalizedType): boolean {
const tag = extractStructTag(normalizedType);
if (tag) {
return (
tag.Struct.address === '0x2' &&
tag.Struct.module === 'transfer' &&
tag.Struct.name === 'Receiving'
);
}
return false;
}

function expectProvider(options: BuildOptions): JsonRpcProvider | SuiClient {
if (!options.provider) {
throw new Error(
Expand Down Expand Up @@ -273,6 +285,18 @@ export class TransactionBlock {
return inserted ?? this.#input('object', value);
}

/**
* Add a new object input to the transaction.
*/
receiving(value: ObjectId | ObjectCallArg) {
const id = getIdFromCallArg(value);
// deduplicate
const inserted = this.#blockData.inputs.find(
(i) => i.type === 'object' && id === getIdFromCallArg(i.value),
);
return inserted ?? this.#input('object', value);
}

/**
* Add a new object input to the transaction using the fully-resolved object reference.
* If you only have an object ID, use `builder.object(id)` instead.
Expand All @@ -281,6 +305,14 @@ export class TransactionBlock {
return this.object(Inputs.ObjectRef(...args));
}

/**
* Add a new receiving input to the transaction using the fully-resolved object reference.
* If you only have an object ID, use `builder.receiving(id)` instead.
*/
receivingRef(...args: Parameters<(typeof Inputs)['ReceivingRef']>) {
return this.receiving(Inputs.ReceivingRef(...args));
}

/**
* Add a new shared object input to the transaction using the fully-resolved shared object reference.
* If you only have an object ID, use `builder.object(id)` instead.
Expand Down Expand Up @@ -670,6 +702,8 @@ export class TransactionBlock {
initialSharedVersion,
mutable,
});
} else if (normalizedType && isReceivingType(normalizedType)) {
input.value = Inputs.ReceivingRef(getObjectReference(object)!);
} else {
input.value = Inputs.ObjectRef(getObjectReference(object)!);
}
Expand Down
26 changes: 26 additions & 0 deletions sdk/typescript/src/builder/__tests__/Transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ it('can construct and serialize an empty tranaction', () => {
expect(() => tx.serialize()).not.toThrow();
});

it('can construct a receiving transaction argument', () => {
const tx = new TransactionBlock();
tx.receiving(Inputs.ReceivingRef(ref()));
expect(() => tx.serialize()).not.toThrow();
});

it('can be serialized and deserialized to the same values', () => {
const tx = new TransactionBlock();
tx.add(Transactions.SplitCoins(tx.gas, [tx.pure(100)]));
Expand Down Expand Up @@ -108,6 +114,26 @@ describe('offline build', () => {
await tx.build();
});

it('uses a receiving argument', async () => {
const tx = setup();
tx.receiving(Inputs.ObjectRef(ref()));
const coin = tx.add(Transactions.SplitCoins(tx.gas, [tx.pure(100)]));
tx.add(Transactions.MergeCoins(tx.gas, [coin, tx.object(Inputs.ObjectRef(ref()))]));
tx.add(
Transactions.MoveCall({
target: '0x2::devnet_nft::mint',
typeArguments: [],
arguments: [tx.object(Inputs.ObjectRef(ref())), tx.receiving(Inputs.ReceivingRef(ref()))],
}),
);

const bytes = await tx.build();
const tx2 = TransactionBlock.from(bytes);
const bytes2 = await tx2.build();

expect(bytes).toEqual(bytes2);
});

it('builds a more complex interaction', async () => {
const tx = setup();
const coin = tx.add(Transactions.SplitCoins(tx.gas, [tx.pure(100)]));
Expand Down
6 changes: 5 additions & 1 deletion sdk/typescript/src/types/sui-bcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export type SharedObjectRef = {
/**
* An object argument.
*/
export type ObjectArg = { ImmOrOwned: SuiObjectRef } | { Shared: SharedObjectRef };
export type ObjectArg =
| { ImmOrOwned: SuiObjectRef }
| { Shared: SharedObjectRef }
| { Receiving: SuiObjectRef };

/**
* A pure argument.
Expand Down Expand Up @@ -129,6 +132,7 @@ const BCS_SPEC: TypeSchema = {
ObjectArg: {
ImmOrOwned: 'SuiObjectRef',
Shared: 'SharedObjectRef',
Receiving: 'SuiObjectRef',
},
CallArg: {
Pure: [VECTOR, BCS.U8],
Expand Down
7 changes: 7 additions & 0 deletions sdk/typescript/src/types/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ export const SuiCallArg = union([
initialSharedVersion: SequenceNumber,
mutable: boolean(),
}),
object({
type: literal('object'),
objectType: literal('receiving'),
objectId: ObjectId,
version: SequenceNumber,
digest: ObjectDigest,
}),
]);
export type SuiCallArg = Infer<typeof SuiCallArg>;

Expand Down
2 changes: 1 addition & 1 deletion sdk/typescript/src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
// This file is generated by genversion.mjs. Do not edit it directly.

export const PACKAGE_VERSION = '0.37.1';
export const TARGETED_RPC_VERSION = '1.5.0';
export const TARGETED_RPC_VERSION = '1.6.0';
9 changes: 9 additions & 0 deletions sdk/typescript/test/e2e/data/tto/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "tto"
version = "0.0.1"

[dependencies]
Sui = { local = "../../../../../../crates/sui-framework/packages/sui-framework" }

[addresses]
tto = "0x0"
44 changes: 44 additions & 0 deletions sdk/typescript/test/e2e/data/tto/sources/tto1.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module tto::tto {
use sui::object::{Self, UID};
use sui::tx_context::{Self, TxContext};
use sui::transfer::{Self, Receiving};

struct A has key, store {
id: UID,
}

struct B has key, store {
id: UID,
}

public fun start(ctx: &mut TxContext) {
let a = A { id: object::new(ctx) };
let a_address = object::id_address(&a);
let b = B { id: object::new(ctx) };
transfer::public_transfer(a, tx_context::sender(ctx));
transfer::public_transfer(b, a_address);
}

public entry fun receiver(parent: &mut A, x: Receiving<B>) {
let b = transfer::receive(&mut parent.id, x);
transfer::public_transfer(b, @tto);
}

public entry fun deleter(parent: &mut A, x: Receiving<B>) {
let B { id } = transfer::receive(&mut parent.id, x);
object::delete(id);
}

public fun return_(parent: &mut A, x: Receiving<B>): B {
transfer::receive(&mut parent.id, x)
}

public entry fun delete_(b: B) {
let B { id } = b;
object::delete(id);
}

public fun invalid_call_immut_ref(_parent: &mut A, _x: &Receiving<B>) { }
public fun invalid_call_mut_ref(_parent: &mut A, _x: &mut Receiving<B>) { }
public fun dropper(_parent: &mut A, _x: Receiving<B>) { }
}
Loading

0 comments on commit 0be1557

Please sign in to comment.