Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Kiosk SDK] Support kiosk_lock_rule and environments #12287

Merged
merged 12 commits into from
Jun 2, 2023
Merged
5 changes: 5 additions & 0 deletions .changeset/nervous-eels-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mysten/kiosk": minor
---

Support kiosk_lock_rule and environment support for rules package. Breaks `purchaseAndResolvePolicies` as it changes signature and return format.
4 changes: 2 additions & 2 deletions sdk/kiosk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

- c322a230da: Fix fetchKiosk consistency/naming, include locked state in items

## 1.0.0
## 0.1.0

### Major Changes
### Minor Changes

- 4ea96d909a: Kiosk SDK for managing, querying and interacting with Kiosk and TransferPolicy objects

Expand Down
27 changes: 20 additions & 7 deletions sdk/kiosk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ const item = {
price: "20000000000" // in MIST
}
}
const ownedKiosk = `0xMyKioskAddress`;
const ownedKioskCap = `0xMyKioskOwnerCap`;

const purchaseItem = async (item, kioskId) => {

// fetch the policy of the item (could be an array, if there's more than one transfer policy)
Expand All @@ -85,14 +88,24 @@ const purchaseItem = async (item, kioskId) => {
// initialize tx block.
const tx = new TransactionBlock();

// Purchases the item. Right now it also resolves a royalty rule, if one exists.
// There will be some additional work to support further rules & custom ones soon.
const purchasedItem = purchaseAndResolvePolicies(tx, item.type, item.listing, kioskId, item.objectId, policy[0]);
// Select the environment. Right now only `testnet` or `custom` is supported.
// For custom, you need to supply the `address` of the rules' package.
const environment = { env: 'testnet', address?: '' }

// now we need to decide what to do with the item
// ... e.g. place() // places the item to the user's kiosk.
// (NOT YET SUPPORTED BUT WORTH MENTIONING if the item has the `kiosk_lock` rule, the resolver will place it in the kiosk automatically.
// For now, to support this rule, we need to manually resolve the `kiosk_lock` rule and place it in our owned kiosk.)
// Both are required if you there is a `kiosk_lock_rule`.
// Optional otherwise. Function will throw an error if there's a kiosk_lock_rule and these are missing.
const extraParams = {
ownedKiosk,
ownedKioskCap
}
// Extra params. Optional, but required if the user tries to resolve a `kiosk_lock_rule`.
// Purchases the item. Supports `kiosk_lock_rule`, `royalty_rule` (accepts combination too).
const result = purchaseAndResolvePolicies(tx, item.type, item.listing, kioskId, item.objectId, policy[0], environment, extraParams);

// result = {item: <the_purchased_item>, canTransfer: true/false // depending on whether there was a kiosk lock rule }
// if the item didn't have a kiosk_lock_rule, we need to do something with it.
// for e..g place it in our own kiosk. (demonstrated below)
if(result.canTransfer) place(tx, item.type, ownedKiosk, ownedKioskCap , result.item);

// ...finally, sign PTB & execute it.

Expand Down
51 changes: 11 additions & 40 deletions sdk/kiosk/src/bcs.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,37 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { ObjectOwner, bcs } from '@mysten/sui.js';

/**
* The Kiosk object fields (for BCS queries).
*/
export type Kiosk = {
id: string;
profits: string;
owner: string;
itemCount: number;
allowExtensions: boolean;
};
import { bcs } from '@mysten/sui.js';
import {
KIOSK_PURCHASE_CAP,
KIOSK_TYPE,
TRANSFER_POLICY_CREATED_EVENT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if we had a function to create generic events of kind: transferPolicyCreated(type): 0x2:....::Created<${type}>. Wonder what you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@damirka Could you elaborate a bit on that?

TRANSFER_POLICY_TYPE,
} from './types';

// Register the `Kiosk` struct for faster queries.
bcs.registerStructType('0x2::kiosk::Kiosk', {
bcs.registerStructType(KIOSK_TYPE, {
id: 'address',
profits: 'u64',
owner: 'address',
itemCount: 'u32',
allowExtensions: 'bool',
});

/**
* PurchaseCap object fields (for BCS queries).
*/
export type PurchaseCap = {
id: string;
kioskId: string;
itemId: string;
minPrice: string;
};

// Register the `PurchaseCap` for faster queries.
bcs.registerStructType('0x2::kiosk::PurchaseCap', {
bcs.registerStructType(KIOSK_PURCHASE_CAP, {
id: 'address',
kioskId: 'address',
itemId: 'address',
minPrice: 'u64',
});

/** Event emitted when a TransferPolicy is created. */
export type TransferPolicyCreated = {
id: string;
};

// Register the `TransferPolicyCreated` event data.
bcs.registerStructType('0x2::transfer_policy::TransferPolicyCreated', {
bcs.registerStructType(TRANSFER_POLICY_CREATED_EVENT, {
id: 'address',
});

/** The `TransferPolicy` object */
export type TransferPolicy = {
id: string;
type: string;
balance: string;
rules: string[];
owner: ObjectOwner;
};

bcs.registerStructType('0x2::transfer_policy::TransferPolicy', {
bcs.registerStructType(TRANSFER_POLICY_TYPE, {
id: 'address',
balance: 'u64',
rules: ['vector', 'string'],
Expand Down
1 change: 1 addition & 0 deletions sdk/kiosk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './query/kiosk';
export * from './bcs';
export * from './utils';
export * from './query/transfer-policy';
export * from './types';
2 changes: 1 addition & 1 deletion sdk/kiosk/src/query/kiosk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
extractKioskData,
getKioskObject,
} from '../utils';
import { Kiosk } from '../bcs';
import { Kiosk } from '../types';

/**
* A dynamic field `Listing { ID, isExclusive }` attached to the Kiosk.
Expand Down
18 changes: 8 additions & 10 deletions sdk/kiosk/src/query/transfer-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// SPDX-License-Identifier: Apache-2.0

import { JsonRpcProvider } from '@mysten/sui.js';
import { TransferPolicy, bcs } from '../bcs';

/** Name of the event emitted when a TransferPolicy for T is created. */
export const TRANSFER_POLICY_CREATED_EVENT = `0x2::transfer_policy::TransferPolicyCreated`;
import { bcs } from '../bcs';
import {
TRANSFER_POLICY_CREATED_EVENT,
TRANSFER_POLICY_TYPE,
TransferPolicy,
} from '../types';

/**
* Searches the `TransferPolicy`-s for the given type. The seach is performed via
Expand Down Expand Up @@ -44,15 +46,11 @@ export async function queryTransferPolicy(
);
}

let parsed = bcs.de(
'0x2::transfer_policy::TransferPolicy',
policy.bcs.bcsBytes,
'base64',
);
let parsed = bcs.de(TRANSFER_POLICY_TYPE, policy.bcs.bcsBytes, 'base64');

return {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we could help provide initialSharedVersion for shared transfer policy. That could help build arguments for the purchase flow.

id: policy?.objectId,
type: `0x2::transfer_policy::TransferPolicy<${type}>`,
type: `${TRANSFER_POLICY_TYPE}<${type}>`,
owner: policy?.owner!,
rules: parsed.rules,
balance: parsed.balance,
Expand Down
84 changes: 56 additions & 28 deletions sdk/kiosk/src/tx/kiosk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import {
TransactionBlock,
} from '@mysten/sui.js';

import { ObjectArgument, getTypeWithoutPackageAddress, objArg } from '../utils';
import { getTypeWithoutPackageAddress, objArg } from '../utils';
import { KioskListing } from '../query/kiosk';
import { TransferPolicy } from '../bcs';
import { confirmRequest, resolveRoyaltyRule } from './transfer-policy';

/** The Kiosk module. */
export const KIOSK_MODULE = '0x2::kiosk';

/** The Kiosk type. */
export const KIOSK_TYPE = `${KIOSK_MODULE}::Kiosk`;

/** The Kiosk Owner Cap Type */
export const KIOSK_OWNER_CAP = `${KIOSK_MODULE}::KioskOwnerCap`;
import {
confirmRequest,
resolveKioskLockRule,
resolveRoyaltyRule,
} from './transfer-policy';
import {
KIOSK_MODULE,
KIOSK_TYPE,
ObjectArgument,
PurchaseAndResolvePoliciesResponse,
PurchaseOptionalParams,
RulesEnvironmentParam,
TransferPolicy,
} from '../types';

/**
* Create a new shared Kiosk and returns the [kiosk, kioskOwnerCap] tuple.
Expand Down Expand Up @@ -200,20 +203,16 @@ export function purchase(
tx: TransactionBlock,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we call txb to align with other places?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this sdk, we have it defined as tx everywhere!
Does other places refer to the @mysten/sui.js sdk?

itemType: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some argument grouping / ordering would help. Eg kioskId, itemId, itemType?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to follow the same ordering we had on the other ones. (itemType was always first)

kiosk: ObjectArgument,
itemId: SuiAddress,
item: ObjectArgument,
manolisliolios marked this conversation as resolved.
Show resolved Hide resolved
payment: ObjectArgument,
): [TransactionArgument, TransactionArgument] {
let [item, transferRequest] = tx.moveCall({
let [purchasedItem, transferRequest] = tx.moveCall({
target: `${KIOSK_MODULE}::purchase`,
typeArguments: [itemType],
arguments: [
objArg(tx, kiosk),
tx.pure(itemId, 'address'),
objArg(tx, payment),
],
arguments: [objArg(tx, kiosk), objArg(tx, item), objArg(tx, payment)],
manolisliolios marked this conversation as resolved.
Show resolved Hide resolved
});

return [item, transferRequest];
return [purchasedItem, transferRequest];
}

/**
Expand Down Expand Up @@ -335,35 +334,43 @@ export function returnValue(
* Completes the full purchase flow that includes:
* 1. Purchasing the item.
* 2. Resolving all the transfer policies (if any).
* 3. Returns the PurchasedItem OR places the item in the user's kiosk (if there's a kiosk lock policy).
* 3. Returns the item and whether the user can transfer it or not.
*
* If the item can be transferred, there's an extra transaction required by the user
* to transfer it to an address or place it in their kiosk.
*/
export function purchaseAndResolvePolicies(
tx: TransactionBlock,
itemType: string,
listing: KioskListing,
kioskId: string,
itemId: string,
kioskId: ObjectArgument,
item: ObjectArgument,
policy: TransferPolicy,
): TransactionArgument | null {
environment: RulesEnvironmentParam,
extraParams?: PurchaseOptionalParams,
): PurchaseAndResolvePoliciesResponse {
// if we don't pass the listing or the listing doens't have a price, return.
if (!listing || listing?.price === undefined) return null;
if (!listing || listing?.price === undefined)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just require price. Perhaps, instead we should require Coin... the reason for it is that developers might choose a custom coin and not use gas for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point for the price!
I like the Coin idea, I think though that this is a nice generic solution for 3-line purchase flows.
Maybe we should support more advanced use-case variations for ideas like this, by possibly extending extraParams in the future to support coin, etc.

throw new Error(`Listing not supplied.`);

// Split the coin for the amount of the listing.
const coin = tx.splitCoins(tx.gas, [tx.pure(listing.price)]);
const coin = tx.splitCoins(tx.gas, [tx.pure(listing.price, 'u64')]);

// initialize the purchase `kiosk::purchase`
const [purchasedItem, transferRequest] = purchase(
tx,
itemType,
kioskId,
itemId,
item,
coin,
);

// Start resolving rules.
// For now, we only support royalty rule.
// Will need some tweaking to make it function properly with the other
// ruleset.
let hasKioskLockRule = false;

for (let rule of policy.rules) {
const ruleWithoutAddr = getTypeWithoutPackageAddress(rule);

Expand All @@ -375,6 +382,24 @@ export function purchaseAndResolvePolicies(
listing.price,
policy.id,
transferRequest,
environment,
);
break;
case 'kiosk_lock_rule::Rule':
manolisliolios marked this conversation as resolved.
Show resolved Hide resolved
if (!extraParams?.ownedKiosk || !extraParams?.ownedKioskCap)
throw new Error(
`This item type ${itemType} has a 'kiosk_lock_rule', but function call is missing user's kiosk and kioskCap params`,
);
hasKioskLockRule = true;
resolveKioskLockRule(
tx,
itemType,
purchasedItem,
extraParams.ownedKiosk,
extraParams.ownedKioskCap,
policy.id,
transferRequest,
environment,
);
break;
default:
Expand All @@ -385,5 +410,8 @@ export function purchaseAndResolvePolicies(
// confirm the Transfer Policy request.
confirmRequest(tx, itemType, policy.id, transferRequest);

return purchasedItem;
return {
item: purchasedItem,
canTransfer: !hasKioskLockRule,
};
}