-
Notifications
You must be signed in to change notification settings - Fork 11
feat: add EIP-7702 gasless operations documentation #249
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| ``` | ||
|
|
||
| ## 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 | ||
| ``` | ||
|
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. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
📝 Committable suggestion
🧰 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
Source: Linters/SAST tools