Skip to content
Merged
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
258 changes: 258 additions & 0 deletions docs/eip7702/gasless-operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
---
id: gasless-operations
title: Gasless Operations
sidebar_label: Gasless Operations
---

# Gasless Operations

All functions on this page accept a `smartAccountClient` built as described in [Setup](./setup). They return a `Promise<string>` containing the UserOp transaction hash.

:::tip Remarks encryption
All `remarks` strings are automatically encrypted with the document ID (`options.id`) before being sent on-chain. Pass the document's `id` in the `TransactionOptions` argument whenever remarks are present.
:::

---

## Deploy a Token Registry

Deploys a new `TradeTrustToken` registry clone through the `PlatformPaymaster`. The caller must have at least 1 deployment credit (`setUserWhitelist`).

```ts
import { deployTokenRegistryGasless } from '@trustvc/trustvc';

const txHash = await deployTokenRegistryGasless(
'My Shipping Line', // registry name
'MSL', // registry symbol
smartAccountClient,
{
paymasterAddress: '0xYourPaymaster...',
tokenRegistryImplAddress: '0x64bc665056DC8bE4092e569ED13a7F273Be28cD2', // TDocDeployer on Sepolia
},
);
```

The `RegistryDeployed(user, deployed, creditsLeft)` event is emitted by the paymaster. Parse it from the receipt to get the deployed registry address.

---

## Mint a Document

Mints a new trade document on an authorized registry. Automatically authorizes the beneficiary, holder, and the new `TitleEscrow` on the paymaster.

```ts
import { mintGasless } from '@trustvc/trustvc';

const txHash = await mintGasless(
{
paymasterAddress: '0xYourPaymaster...',
tokenRegistryAddress: '0xYourRegistry...',
},
smartAccountClient,
{
beneficiaryAddress: '0xBeneficiary...',
holderAddress: '0xHolder...',
tokenId: '0xdeadbeef', // or a BigInt
remarks: 'Initial issuance', // optional
},
{ id: 'document-uuid' }, // used to encrypt remarks
);
```

The `TitleEscrowLinked(titleEscrow, registry)` event is emitted. Parse it to get the deployed `TitleEscrow` address.

---

## Title Escrow Operations

All operations below target the `TitleEscrow` contract directly. The `smartAccountClient` owner must be the current holder or beneficiary as appropriate.

### Transfer Holder

Transfers the **holder** role to a new address. Caller must be the current holder.

```ts
import { transferHolderGasless } from '@trustvc/trustvc';

const txHash = await transferHolderGasless(
{ titleEscrowAddress: '0xTitleEscrow...' },
smartAccountClient,
{
holderAddress: '0xNewHolder...',
remarks: 'Transferring to freight forwarder', // optional
},
{ id: 'document-uuid' },
);
```

### Transfer Beneficiary (Nominate)

Nominates a new beneficiary. Caller must be the current beneficiary.

```ts
import { transferBeneficiaryGasless } from '@trustvc/trustvc';

const txHash = await transferBeneficiaryGasless(
{ titleEscrowAddress: '0xTitleEscrow...' },
smartAccountClient,
{
newBeneficiaryAddress: '0xNewBeneficiary...',
remarks: 'Endorsing to buyer', // optional
},
{ id: 'document-uuid' },
);
```

### Transfer Both Owners

Transfers holder and beneficiary in one transaction. Caller must be both holder and beneficiary.

```ts
import { transferOwnersGasless } from '@trustvc/trustvc';

const txHash = await transferOwnersGasless(
{ titleEscrowAddress: '0xTitleEscrow...' },
smartAccountClient,
{
newBeneficiaryAddress: '0xNewBeneficiary...',
newHolderAddress: '0xNewHolder...',
remarks: 'Full transfer', // optional
},
{ id: 'document-uuid' },
);
```

### Nominate a Beneficiary

Nominates a beneficiary without immediately completing the transfer. The nominated beneficiary must accept separately.

```ts
import { nominateGasless } from '@trustvc/trustvc';

const txHash = await nominateGasless(
{ titleEscrowAddress: '0xTitleEscrow...' },
smartAccountClient,
{
newBeneficiaryAddress: '0xNominatedBeneficiary...',
remarks: 'Nomination for endorsement', // optional
},
{ id: 'document-uuid' },
);
```

### Reject a Pending Transfer — Holder

Rejects a pending holder transfer. Caller must be the current holder.

```ts
import { rejectTransferHolderGasless } from '@trustvc/trustvc';

const txHash = await rejectTransferHolderGasless(
{ titleEscrowAddress: '0xTitleEscrow...' },
smartAccountClient,
{ remarks: 'Rejecting transfer' }, // optional
{ id: 'document-uuid' },
);
```

### Reject a Pending Transfer — Beneficiary

Rejects a pending beneficiary (nomination). Caller must be the current beneficiary.

```ts
import { rejectTransferBeneficiaryGasless } from '@trustvc/trustvc';

const txHash = await rejectTransferBeneficiaryGasless(
{ titleEscrowAddress: '0xTitleEscrow...' },
smartAccountClient,
{ remarks: 'Rejecting nomination' },
{ id: 'document-uuid' },
);
```

### Reject a Pending Transfer — Both Owners

Rejects a pending combined transfer. Caller must be both current holder and beneficiary.

```ts
import { rejectTransferOwnersGasless } from '@trustvc/trustvc';

const txHash = await rejectTransferOwnersGasless(
{ titleEscrowAddress: '0xTitleEscrow...' },
smartAccountClient,
{ remarks: 'Rejecting combined transfer' },
{ id: 'document-uuid' },
);
```

---

## Return to Issuer

At the end of a document's lifecycle, the holder (who is also the beneficiary) returns the token to the issuer.

```ts
import { returnToIssuerGasless } from '@trustvc/trustvc';

const txHash = await returnToIssuerGasless(
{ titleEscrowAddress: '0xTitleEscrow...' },
smartAccountClient,
{ remarks: 'Surrendering document' }, // optional
{ id: 'document-uuid' },
);
```

### Reject a Returned Document

The platform (registry admin) restores the document back to the title escrow, rejecting the return.

```ts
import { rejectReturnedGasless } from '@trustvc/trustvc';

const txHash = await rejectReturnedGasless(
{ tokenRegistryAddress: '0xYourRegistry...' },
smartAccountClient,
{
tokenId: '0xdeadbeef',
remarks: 'Return rejected', // optional
},
{ id: 'document-uuid' },
);
```

### Accept a Returned Document (Burn)

The platform accepts the return and burns the document.

```ts
import { acceptReturnedGasless } from '@trustvc/trustvc';

const txHash = await acceptReturnedGasless(
{ tokenRegistryAddress: '0xYourRegistry...' },
smartAccountClient,
{
tokenId: '0xdeadbeef',
remarks: 'Document accepted and destroyed', // optional
},
{ id: 'document-uuid' },
);
```

---

## Summary

| Function | Target | Caller |
|---|---|---|
| `deployTokenRegistryGasless` | PlatformPaymaster | Whitelisted user |
| `mintGasless` | PlatformPaymaster | User with MINTER_ROLE |
| `transferHolderGasless` | TitleEscrow | Current holder |
| `transferBeneficiaryGasless` | TitleEscrow | Current beneficiary |
| `transferOwnersGasless` | TitleEscrow | Holder and beneficiary |
| `nominateGasless` | TitleEscrow | Current beneficiary |
| `rejectTransferHolderGasless` | TitleEscrow | Current holder |
| `rejectTransferBeneficiaryGasless` | TitleEscrow | Current beneficiary |
| `rejectTransferOwnersGasless` | TitleEscrow | Holder and beneficiary |
| `returnToIssuerGasless` | TitleEscrow | Holder = beneficiary |
| `rejectReturnedGasless` | Token Registry | Registry admin |
| `acceptReturnedGasless` | Token Registry | Registry admin |
108 changes: 108 additions & 0 deletions docs/eip7702/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
id: overview
title: Gasless Operations (EIP-7702)
sidebar_label: Overview
---

# Gasless Operations with EIP-7702

:::caution Beta
Gasless transactions are currently in **beta**. APIs, contract addresses, and behavior may change before the stable release. Use on testnet only and do not rely on this feature in production.
:::

TrustVC supports **gasless trade document operations** via [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) (smart account delegation). Users can deploy token registries, mint documents, and perform all title escrow operations without holding ETH — gas is sponsored by a **PlatformPaymaster**.

## Architecture

The system is built on three smart contracts:

| Contract | Role |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EIP7702Implementation` | Shared smart account logic. EOAs delegate to this once via a type-4 transaction — their bytecode becomes `0xef0100 \|\| impl_address`, giving them full smart-account capability while keeping the same address and private key. |
| `PlatformPaymaster` | Per-platform ERC-4337 paymaster deployed as a minimal-proxy clone. Sponsors gas for registry deploys, mints, and title escrow operations within configured limits. |
| `PlatformAccountFactory` | Deploys `PlatformPaymaster` clones deterministically via `CREATE2`. Cheap (~55 k gas) and the clone address is predictable before deployment. |

## How it works

```
User (EOA, no ETH)
│ 1. Sign EIP-7702 authorization → EOA delegates to EIP7702Implementation
│ 2. Submit UserOperation via Pimlico bundler
Bundler (Pimlico)
│ 3. Calls EntryPoint → validates against PlatformPaymaster
PlatformPaymaster
├── Path A — Title escrow / registry calls
│ Checks: caller ∈ authorizedCallers, target ∈ authorizedRegistries or authorizedTitleEscrows
│ Enforces: dailyLimit per user
└── Path B — deployRegistry / mintDocument on the paymaster itself
deployRegistry: userWhitelist[sender] > 0 (platform whitelists users)
mintDocument: caller has MINTER_ROLE on the registry
```
Comment on lines +27 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the diagram fence.

The unlabeled fenced block at Line 27 triggers the markdownlint warning. Mark it as text (or similar) so the docs lint cleanly.

♻️ Proposed fix
-```
+```text
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
User (EOA, no ETH)
│ 1. Sign EIP-7702 authorization → EOA delegates to EIP7702Implementation
│ 2. Submit UserOperation via Pimlico bundler
Bundler (Pimlico)
│ 3. Calls EntryPoint → validates against PlatformPaymaster
PlatformPaymaster
├── Path A — Title escrow / registry calls
│ Checks: caller ∈ authorizedCallers, target ∈ authorizedRegistries or authorizedTitleEscrows
│ Enforces: dailyLimit per user
└── Path B — deployRegistry / mintDocument on the paymaster itself
deployRegistry: userWhitelist[sender] > 0 (platform whitelists users)
mintDocument: caller has MINTER_ROLE on the registry
```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 27-27: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/eip7702/overview.md` around lines 27 - 48, The fenced diagram block in
overview.md is missing a language tag, which triggers markdownlint. Update the
opening fence for the EIP-7702 overview diagram to use a text-like tag such as
text so the block remains a diagram while linting cleanly; locate the unlabeled
fenced section around the overview diagram content.

Source: Linters/SAST tools


## Deployed addresses

### Sepolia (chainId: 11155111)

| Contract | Address |
| ---------------------------------- | -------------------------------------------- |
| EIP7702Implementation | `0xa46EC3920Ac5fc54F4bA33185A91ae250aDF59B8` |
| PlatformPaymaster (implementation) | `0xa24695178ea881ab7d4d105106e4906a8da4752b` |
| PlatformAccountFactory | `0x7e9ef6363180baa744eb32ceab367a44f52adc9f` |
| TDocDeployer | `0x64bc665056DC8bE4092e569ED13a7F273Be28cD2` |
| EntryPoint v0.8 | `0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108` |

### Polygon Amoy (chainId: 80002)

| Contract | Address |
| ---------------------------------- | -------------------------------------------- |
| EIP7702Implementation | `0x044de1d4515a76ed9e431e8ec89e8d600405fd86` |
| PlatformPaymaster (implementation) | `0x1c4367128933E9a88de26C723F50C288fA0fFea7` |
| PlatformAccountFactory | `0xfbe1d336000d567f98ac5318f7c0144501388409` |
| TDocDeployer | `0xfcafea839e576967b96ad1FBFB52b5CA26cd1D25` |
| EntryPoint v0.8 | `0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108` |

## SDK package

The `@trustvc/trustvc` SDK exposes all gasless functions under the `eip7702-functions` namespace. Install it once:

```bash
npm install @trustvc/trustvc@beta
```
Comment thread
isaackps marked this conversation as resolved.

Import any gasless function directly:

```ts
import {
deployTokenRegistryGasless,
mintGasless,
transferHolderGasless,
transferBeneficiaryGasless,
transferOwnersGasless,
nominateGasless,
returnToIssuerGasless,
rejectReturnedGasless,
acceptReturnedGasless,
rejectTransferHolderGasless,
rejectTransferBeneficiaryGasless,
rejectTransferOwnersGasless,
} from "@trustvc/trustvc";
```

## Prerequisites

Before calling any gasless function you need:

1. **A PlatformPaymaster deployed for your platform** — see [Setup](./setup).
2. **A Pimlico API key** — free tier at [dashboard.pimlico.io](https://dashboard.pimlico.io).
3. **The user's EOA delegated** — one-time type-4 transaction (wallet signs an EIP-7702 authorization).
4. **A built `smartAccountClient`** — the permissionless `SmartAccountClient` wrapping the delegated EOA.

All four are covered in [Setup](./setup). Once set up, jump to [Gasless Operations](./gasless-operations) for code examples.
Loading