Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/content/GettingStarted.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ const decryptedBytes = await client.decrypt({
});
```

:::tip

If your `seal_approve` function takes an owned object as an argument (for example, a `PurchaseReceipt` NFT), you must call `tx.setSender(account.address)` before `tx.build()`. See the [Decryption Guide](/UsingSeal#decryption) for details.
Copy link
Collaborator

@joyqvq joyqvq Mar 9, 2026

Choose a reason for hiding this comment

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

you can actually use tx.build({ client: suiClient, onlyTransactionKind: true }) and the sdk will resolve the owned object without knowing the sender.

the sdk resolves this using resolveObjectReferences so setSender is not needed


:::

Learn more in the [Decryption Guide](/UsingSeal#decryption).

## Next steps
Expand Down
21 changes: 21 additions & 0 deletions docs/content/UsingSeal.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,27 @@ const decryptedBytes = await client.decrypt({

Seal evaluates the transaction as if the user sent it. In Move, `TxContext::sender()` returns the account that signed with the session key.

:::tip[Using owned objects in seal_approve]

If your `seal_approve` function takes an **owned object** as a parameter (for example, a `PurchaseReceipt` or access NFT used as proof of access), you must call `tx.setSender(address)` before building the transaction:

```typescript
const tx = new Transaction();
tx.setSender(account.address); // Required when seal_approve references owned objects
tx.moveCall({
target: `${packageId}::${moduleName}::seal_approve`,
arguments: [
tx.pure.vector("u8", fromHEX(id)),
tx.object(ownedObjectId), // owned object
]
});
const txBytes = await tx.build({ client: suiClient, onlyTransactionKind: true });
```

Without `setSender()`, `tx.build()` defaults the sender to `0x0` and fails to resolve owned object inputs, resulting in an ownership mismatch error. This is not needed when `seal_approve` only uses shared objects and pure values.

:::

:::tip

To debug a transaction, call `dryRunTransactionBlock` directly with the transaction block.
Expand Down
4 changes: 4 additions & 0 deletions examples/frontend/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const downloadAndDecrypt = async (
const ids = batch.map((enc) => EncryptedObject.parse(new Uint8Array(enc)).id);
const tx = new Transaction();
ids.forEach((id) => moveCallConstructor(tx, id));
// Note: if moveCallConstructor references owned objects, tx.setSender(address)
// must be called before build(). See docs/content/UsingSeal.mdx for details.
const txBytes = await tx.build({ client: suiClient, onlyTransactionKind: true });
try {
await sealClient.fetchKeys({ ids, txBytes, sessionKey, threshold: 2 });
Expand All @@ -87,6 +89,8 @@ export const downloadAndDecrypt = async (
const fullId = EncryptedObject.parse(new Uint8Array(encryptedData)).id;
const tx = new Transaction();
moveCallConstructor(tx, fullId);
// Note: if moveCallConstructor references owned objects, tx.setSender(address)
// must be called before build(). See docs/content/UsingSeal.mdx for details.
const txBytes = await tx.build({ client: suiClient, onlyTransactionKind: true });
try {
// Note that all keys are fetched above, so this only local decryption is done
Expand Down
Loading