-
Notifications
You must be signed in to change notification settings - Fork 0
feat: pay on behalf #34
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
Open
RishabhS7
wants to merge
7
commits into
beta
Choose a base branch
from
feat/pay-on-behalf-transactions
base: beta
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
ee841bf
feat: pay on behalf
RishabhS7 11c08f0
chore: stop tracking wr/ and decrypted.json
RishabhS7 18eb4a9
fix: remove test files
RishabhS7 6013d86
fix: package lock
RishabhS7 2f54727
fix: regenerate package-lock.json from a clean install
RishabhS7 43350b6
fix: address code-review findings on gasless commands
RishabhS7 45f8f02
fix: code rabbit comments
RishabhS7 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
Large diffs are not rendered by default.
Oops, something went wrong.
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
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,29 @@ | ||
| import { error } from 'signale'; | ||
| import { addAuthorizedCaller } from '@trustvc/trustvc'; | ||
| import { promptAddress } from '../../../utils'; | ||
| import { promptForPaymasterAdminWalletInputs, runPaymasterAdminAction } from './common'; | ||
|
|
||
| export const command = 'add-authorized-caller'; | ||
|
|
||
| export const describe = | ||
| 'Authorizes an address to trigger sponsored holder/beneficiary title-escrow or registry calls (Path A) on a PlatformPaymaster'; | ||
|
|
||
| export const handler = async (): Promise<string | undefined> => { | ||
| try { | ||
| const base = await promptForPaymasterAdminWalletInputs(); | ||
| const caller = await promptAddress('caller', 'address to authorize on the paymaster'); | ||
|
|
||
| return await runPaymasterAdminAction({ | ||
| ...base, | ||
| actionLabel: `Authorizing caller ${caller}`, | ||
| execute: (wallet) => | ||
| addAuthorizedCaller( | ||
| wallet, | ||
| base.paymasterAddress as `0x${string}`, | ||
| caller as `0x${string}`, | ||
| ), | ||
| }); | ||
| } catch (err: unknown) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| } | ||
| }; |
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,25 @@ | ||
| import { error } from 'signale'; | ||
| import { addRegistry } from '@trustvc/trustvc'; | ||
| import { promptAddress } from '../../../utils'; | ||
| import { promptForPaymasterAdminWalletInputs, runPaymasterAdminAction } from './common'; | ||
|
|
||
| export const command = 'add-registry'; | ||
|
|
||
| export const describe = | ||
| 'Authorizes a token registry so its calls can be sponsored by a PlatformPaymaster'; | ||
|
|
||
| export const handler = async (): Promise<string | undefined> => { | ||
| try { | ||
| const base = await promptForPaymasterAdminWalletInputs(); | ||
| const registry = await promptAddress('registry', 'token registry address to authorize'); | ||
|
|
||
| return await runPaymasterAdminAction({ | ||
| ...base, | ||
| actionLabel: `Authorizing registry ${registry}`, | ||
| execute: (wallet) => | ||
| addRegistry(wallet, base.paymasterAddress as `0x${string}`, registry as `0x${string}`), | ||
| }); | ||
| } catch (err: unknown) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| } | ||
| }; |
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,29 @@ | ||
| import { error } from 'signale'; | ||
| import { addTitleEscrow } from '@trustvc/trustvc'; | ||
| import { promptAddress } from '../../../utils'; | ||
| import { promptForPaymasterAdminWalletInputs, runPaymasterAdminAction } from './common'; | ||
|
|
||
| export const command = 'add-title-escrow'; | ||
|
|
||
| export const describe = | ||
| 'Authorizes a title escrow so its calls can be sponsored by a PlatformPaymaster'; | ||
|
|
||
| export const handler = async (): Promise<string | undefined> => { | ||
| try { | ||
| const base = await promptForPaymasterAdminWalletInputs(); | ||
| const titleEscrow = await promptAddress('title escrow', 'title escrow address to authorize'); | ||
|
|
||
| return await runPaymasterAdminAction({ | ||
| ...base, | ||
| actionLabel: `Authorizing title escrow ${titleEscrow}`, | ||
| execute: (wallet) => | ||
| addTitleEscrow( | ||
| wallet, | ||
| base.paymasterAddress as `0x${string}`, | ||
| titleEscrow as `0x${string}`, | ||
| ), | ||
| }); | ||
| } catch (err: unknown) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| } | ||
| }; |
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,85 @@ | ||
| import { error, info, success } from 'signale'; | ||
| import { | ||
| getErrorMessage, | ||
| getEtherscanAddress, | ||
| getWalletOrSigner, | ||
| promptAddress, | ||
| promptNetworkSelection, | ||
| promptWalletSelection, | ||
| } from '../../../utils'; | ||
| import { assertGaslessSupportedNetwork, redactPimlicoApiKey } from '../config'; | ||
| import { GaslessWalletOption } from '../types'; | ||
|
|
||
| /** | ||
| * PlatformPaymaster admin functions (add/remove authorized caller, registry, title escrow; | ||
| * user whitelist credits; daily limit) are all owner-only, regular (non-gasless) transactions — | ||
| * the owner pays gas directly to configure their own paymaster. | ||
| */ | ||
| export type PaymasterAdminWalletCommand = GaslessWalletOption & { | ||
| network: string; | ||
| paymasterAddress: string; | ||
| }; | ||
|
|
||
| export const promptForPaymasterAdminWalletInputs = | ||
| async (): Promise<PaymasterAdminWalletCommand> => { | ||
| const network = await promptNetworkSelection(); | ||
| assertGaslessSupportedNetwork(network); | ||
|
|
||
| const paymasterAddress = await promptAddress( | ||
| 'paymaster', | ||
| 'PlatformPaymaster contract to administer', | ||
| ); | ||
|
|
||
| const { encryptedWalletPath, key, keyFile } = await promptWalletSelection(); | ||
|
|
||
| return { | ||
| network, | ||
| paymasterAddress: paymasterAddress as string, | ||
| encryptedWalletPath, | ||
| key, | ||
| keyFile, | ||
| }; | ||
| }; | ||
|
|
||
| export interface RunPaymasterAdminActionArgs extends PaymasterAdminWalletCommand { | ||
| actionLabel: string; | ||
| /** Wallet is a raw ethers Signer here, not a viem WalletClient — no smart account involved. */ | ||
| execute: (wallet: Awaited<ReturnType<typeof getWalletOrSigner>>) => Promise<`0x${string}`>; | ||
| } | ||
|
|
||
| /** | ||
| * Shared runner for every paymaster admin action: resolves a regular wallet, runs the given | ||
| * on-chain call, and reports the transaction hash. Errors are caught and logged here, so callers | ||
| * only need to guard their own prompting step. | ||
| */ | ||
| export const runPaymasterAdminAction = async ({ | ||
| network, | ||
| paymasterAddress, | ||
| encryptedWalletPath, | ||
| key, | ||
| keyFile, | ||
| actionLabel, | ||
| execute, | ||
| }: RunPaymasterAdminActionArgs): Promise<string | undefined> => { | ||
| try { | ||
| const assertedNetwork = assertGaslessSupportedNetwork(network); | ||
|
|
||
| const wallet = await getWalletOrSigner({ | ||
| network: assertedNetwork, | ||
| encryptedWalletPath, | ||
| key, | ||
| keyFile, | ||
| }); | ||
|
|
||
| info(`${actionLabel} on PlatformPaymaster ${paymasterAddress}...`); | ||
|
|
||
| const txHash = await execute(wallet); | ||
|
|
||
| success(`${actionLabel} — done`); | ||
| info(`Find more details at ${getEtherscanAddress({ network: assertedNetwork })}/tx/${txHash}`); | ||
|
|
||
| return txHash; | ||
| } catch (e) { | ||
| error(redactPimlicoApiKey(getErrorMessage(e))); | ||
| } | ||
| }; |
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,82 @@ | ||
| import { error, info, success } from 'signale'; | ||
| import { createWalletClient, http } from 'viem'; | ||
| import { privateKeyToAccount } from 'viem/accounts'; | ||
| import { | ||
| getErrorMessage, | ||
| getEtherscanAddress, | ||
| getWalletOrSigner, | ||
| promptNetworkSelection, | ||
| promptWalletSelection, | ||
| } from '../../../utils'; | ||
| import { | ||
| assertGaslessSupportedNetwork, | ||
| getEip7702ImplementationAddress, | ||
| getGaslessRpcUrl, | ||
| getViemChain, | ||
| redactPimlicoApiKey, | ||
| } from '../config'; | ||
|
|
||
| // Submits a standalone EIP-7702 authorization for a user's own EOA — the first of the three | ||
| // setup steps ("Delegate" in the README) needed before an account can act as a smart account. | ||
| // Regular, non-gasless transaction: the user pays gas to delegate their own account. Every gasless | ||
| // command already bundles this automatically with its first sponsored UserOperation, so running | ||
| // this separately is only needed to set delegation up ahead of time. | ||
| export const command = 'delegate-user'; | ||
|
|
||
| export const describe = | ||
| "Delegates a user's EOA to the deployed EIP7702Implementation contract via a standalone EIP-7702 authorization. Regular transaction — the user pays gas to delegate their own account."; | ||
|
|
||
| export const handler = async (): Promise<string | undefined> => { | ||
| try { | ||
| const network = await promptNetworkSelection(); | ||
| const assertedNetwork = assertGaslessSupportedNetwork(network); | ||
|
|
||
| const { encryptedWalletPath, key, keyFile } = await promptWalletSelection(); | ||
|
|
||
| // Only used to recover the raw private key: viem needs a LocalAccount to sign the | ||
| // authorization, not an ethers Signer. | ||
| const wallet = await getWalletOrSigner({ | ||
| network: assertedNetwork, | ||
| encryptedWalletPath, | ||
| key, | ||
| keyFile, | ||
| }); | ||
|
|
||
| const privateKey = (wallet as { privateKey?: string }).privateKey; | ||
| if (!privateKey) { | ||
| throw new Error( | ||
| 'Delegating requires direct access to a private key (encrypted wallet file, --key, --key-file, or OA_PRIVATE_KEY). AWS KMS signers are not supported.', | ||
| ); | ||
| } | ||
|
|
||
| const implementationAddress = getEip7702ImplementationAddress(assertedNetwork); | ||
| const account = privateKeyToAccount(privateKey as `0x${string}`); | ||
| const walletClient = createWalletClient({ | ||
| account, | ||
| chain: getViemChain(assertedNetwork), | ||
| transport: http(getGaslessRpcUrl(assertedNetwork)), | ||
| }); | ||
|
|
||
| info(`Delegating ${account.address} to ${implementationAddress} on ${assertedNetwork}...`); | ||
|
|
||
| // `executor: 'self'` tells viem the account signing the authorization is also the one | ||
| // submitting the transaction, so it correctly uses nonce + 1 rather than the current nonce. | ||
| const authorization = await walletClient.signAuthorization({ | ||
| contractAddress: implementationAddress, | ||
| executor: 'self', | ||
| }); | ||
|
|
||
| const txHash = await walletClient.sendTransaction({ | ||
| authorizationList: [authorization], | ||
| to: account.address, | ||
| value: 0n, | ||
| }); | ||
|
|
||
| success(`Account ${account.address} delegated to ${implementationAddress}`); | ||
| info(`Find more details at ${getEtherscanAddress({ network: assertedNetwork })}/tx/${txHash}`); | ||
|
|
||
| return txHash; | ||
| } catch (e) { | ||
| error(redactPimlicoApiKey(getErrorMessage(e))); | ||
| } | ||
| }; |
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,45 @@ | ||
| import { error } from 'signale'; | ||
| import { input } from '@inquirer/prompts'; | ||
| import { ethers } from 'ethers'; | ||
| import { eip7702Abis } from '@trustvc/trustvc'; | ||
| import { promptForPaymasterAdminWalletInputs, runPaymasterAdminAction } from './common'; | ||
|
|
||
| export const command = 'fund-paymaster'; | ||
|
|
||
| export const describe = | ||
| "Deposits ETH into a PlatformPaymaster's EntryPoint gas balance so it can sponsor UserOperations. Regular transaction — the caller pays the deposited amount plus gas."; | ||
|
|
||
| export const handler = async (): Promise<string | undefined> => { | ||
| try { | ||
| const base = await promptForPaymasterAdminWalletInputs(); | ||
|
|
||
| const amountEth = await input({ | ||
| message: 'Enter the amount to deposit in ETH:', | ||
| required: true, | ||
| validate: (value: string) => { | ||
| if (!/^\d*\.?\d+$/.test(value) || Number(value) <= 0) { | ||
| return 'Amount must be a positive number (ETH)'; | ||
| } | ||
| return true; | ||
| }, | ||
| }); | ||
| const amount = ethers.parseEther(amountEth); | ||
|
|
||
| return await runPaymasterAdminAction({ | ||
| ...base, | ||
| actionLabel: `Depositing ${amountEth} ETH`, | ||
| execute: async (wallet) => { | ||
| const contract = new ethers.Contract( | ||
| base.paymasterAddress, | ||
| eip7702Abis.platformPaymasterAbi, | ||
| wallet, | ||
| ); | ||
| const tx = await contract.deposit({ value: amount }); | ||
| const receipt = await tx.wait(); | ||
| return receipt.hash as `0x${string}`; | ||
| }, | ||
| }); | ||
| } catch (err: unknown) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| } | ||
| }; |
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,11 @@ | ||
| import { Argv } from 'yargs'; | ||
|
|
||
| export const command = 'paymaster-admin <method>'; | ||
|
|
||
| export const describe = | ||
| 'Administer a PlatformPaymaster contract (owner-only; regular, non-gasless transactions)'; | ||
|
|
||
| export const builder = (yargs: Argv): Argv => | ||
| yargs.commandDir(__dirname, { extensions: ['ts', 'js'] }); | ||
|
|
||
| export const handler = (): void => {}; |
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,29 @@ | ||
| import { error } from 'signale'; | ||
| import { removeAuthorizedCaller } from '@trustvc/trustvc'; | ||
| import { promptAddress } from '../../../utils'; | ||
| import { promptForPaymasterAdminWalletInputs, runPaymasterAdminAction } from './common'; | ||
|
|
||
| export const command = 'remove-authorized-caller'; | ||
|
|
||
| export const describe = | ||
| 'Removes an address from the authorized-caller list (Path A) on a PlatformPaymaster'; | ||
|
|
||
| export const handler = async (): Promise<string | undefined> => { | ||
| try { | ||
| const base = await promptForPaymasterAdminWalletInputs(); | ||
| const caller = await promptAddress('caller', 'address to remove from the paymaster'); | ||
|
|
||
| return await runPaymasterAdminAction({ | ||
| ...base, | ||
| actionLabel: `Removing caller ${caller}`, | ||
| execute: (wallet) => | ||
| removeAuthorizedCaller( | ||
| wallet, | ||
| base.paymasterAddress as `0x${string}`, | ||
| caller as `0x${string}`, | ||
| ), | ||
| }); | ||
| } catch (err: unknown) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| } | ||
| }; |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.