Skip to content

Commit

Permalink
feat: demo mantle
Browse files Browse the repository at this point in the history
  • Loading branch information
Haypierre committed Feb 5, 2025
1 parent a4eafae commit e1ecb99
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 41 deletions.
32 changes: 24 additions & 8 deletions demo/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Address, privateKeyToAccount } from "viem/accounts";
import { baseSepolia, optimismSepolia } from "viem/chains";
import { baseSepolia, optimismSepolia, mantleSepoliaTestnet } from "viem/chains";
// Add proper type definition
export type supportedChain = "optimism" | "base";
export type supportedChain = "optimism" | "base" | "mantle";

export const V7SimpleAccountFactoryAddress =
"0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985";
Expand Down Expand Up @@ -40,14 +40,25 @@ export const openfortContracts: Record<supportedChain, OpenfortContracts> = {
},
},
optimism: {
paymaster: "0x511985306FDDE63cda68F5675EC296AAd826b5b8",
invoiceManager: "0x9dDB3Af574307DFEfE9d69D09A0BBcF55b9e2D34",
vaultManager: "0xd454fbc6Df5D9d91Fa02e60fD46CDD2208d0b33b",
paymaster: "0x4036469C65800b6A2278BB9603c3Aae8e22e046d",
invoiceManager: "0xf81173107aA5c72042d3F6676AD61aE08242d364",
vaultManager: "0x56818692A9d313Bf39Ac095E5670Ebd66B98F6EA",
vaults: {
"0x2522F4Fc9aF2E1954a3D13f7a5B2683A00a4543A":
"0xaF45f62eB99AD2091440336ca714B21F06525978",
"0xFE84D9E1B3A2AbAB1EB53A9D50E58B6D7FFe268C",
"0xd926e338e047aF920F59390fF98A3114CCDcab4a":
"0x5b306B655B84Bc3201e6f9577d0CDcc7C2e9Ebfb",
"0x3f4c40dE7702FcDF93d4DF9bdbBe8c34F107a21a",
},
},
mantle: {
paymaster: "0xeDb665E8e20f95bA4d79a8C208aeFF21f05dC88B",
invoiceManager: "0x4501B873f3DA90a79B4F898E2627B88b63F37039",
vaultManager: "0x8BFf6f29A6435C35a29dCE67baEa050160A9e41e",
vaults: {
"0x4855090BbFf14397E1d48C9f4Cd7F111618F071a":
"0x30C788123bF7540828CEc9dA861Eca4009DECef8",
"0x76501186fB44d508b9aeC50899037F33C6FF4A36":
"0x537758a6b09D042B12b7C0Cb18CEc466E83640E1",
},
},
};
Expand All @@ -56,28 +67,33 @@ export const openfortContracts: Record<supportedChain, OpenfortContracts> = {
export const vaultA = {
base: "0x5502B2Da288Be13F48eE46E3261690Ed4a1e71f9",
optimism: "0xaF45f62eB99AD2091440336ca714B21F06525978",
mantle: "0x30C788123bF7540828CEc9dA861Eca4009DECef8",
};

export const tokenA = {
base: "0xfF3311cd15aB091B00421B23BcB60df02EFD8db7",
optimism: "0x2522F4Fc9aF2E1954a3D13f7a5B2683A00a4543A",
mantle: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // Sponsor with native token on Mantle
};

export const tokenB = {
base: "0xa9a0179e045cF39C5F4d914583dc3648DfBDeeF1",
optimism: "0xd926e338e047aF920F59390fF98A3114CCDcab4a",
mantle: "0x76501186fB44d508b9aeC50899037F33C6FF4A36",
};

export const demoNFTs = {
base: "0xD129bda7CE0888d7Fd66ff46e7577c96984d678f",
optimism: "0x9999999999999999999999999999999999999999",
mantle: "0x824a4c49a1306F0a5e2e05c8e93510442363893e", // DEMO: DemoNativeNFT on Mantle
};

export const chainIDs = {
base: baseSepolia.id,
optimism: optimismSepolia.id,
mantle: mantleSepoliaTestnet.id,
};

export function isValidChain(chain: string): chain is supportedChain {
return chain === "optimism" || chain === "base";
return chain === "optimism" || chain === "base" || chain === "mantle";
}
46 changes: 32 additions & 14 deletions demo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.requiredOption("-s, --account-salt <salt>", "account salt")
.action(async ({ chain, accountSalt }) => {
Expand Down Expand Up @@ -73,7 +73,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.requiredOption("-t, --token <token>", "token address")
.requiredOption("-a, --amount <amount>", "amount to lock")
Expand Down Expand Up @@ -127,7 +127,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.action(async ({ chain }) => {
if (!isValidChain(chain)) {
Expand All @@ -143,7 +143,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.requiredOption("-i, --ipfs-hash <ipfs-hash>", "ipfs hash")
.requiredOption("-s, --account-salt <salt>", "account salt")
Expand All @@ -164,13 +164,26 @@ program
version: "0.7",
},
});

const accountAddress = await account.getAddress();
console.log(`Account Address: ${accountAddress}`);
const paymaster = openfortContracts[chain].paymaster;
console.log(`Paymaster: ${paymaster}`);
const unsignedUserOp = await bundlerClient.prepareUserOperation({
account: account,
calls: [

let calls = [];
if (chain === "mantle") {
calls = [
{
to: demoNFTs[chain] as Address,
abi: parseAbi(["function mint(string)"]),
functionName: "mint",
args: [ipfsHash],
value: nftPrice, // DEMO: pay with native token on Mantle
},
]
} else {
// On optimism and base, keep paying with ERC20 tokens
calls = [
{
to: tokenA[chain] as Address,
abi: parseAbi(["function transferFrom(address, address, uint256)"]),
Expand All @@ -189,7 +202,12 @@ program
functionName: "mint",
args: [ipfsHash],
},
],
]
}

const unsignedUserOp = await bundlerClient.prepareUserOperation({
account: account,
calls: calls,
verificationGasLimit: 1000000n,
postVerificationGasLimit: 1000000n,
preVerificationGas: 1000000n,
Expand Down Expand Up @@ -220,7 +238,7 @@ program
});
console.log(`UserOp sent: ${hash}`);
// TODO: support multiple source chains
const srcChain = chain === "base" ? "optimism" : "base";
const srcChain = "optimism";
const invoiceId = await invoiceManager.writeInvoice({
account: accountAddress,
nonce: BigInt(unsignedUserOp.nonce),
Expand Down Expand Up @@ -277,7 +295,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.requiredOption("-t, --token <token>", "token address")
.requiredOption("-a, --amount <amount>", "amount to send")
Expand Down Expand Up @@ -306,7 +324,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.requiredOption("-p, --proof <proof>", "proof")
.requiredOption("-i, --invoice-id <invoice-id>", "invoice id")
Expand Down Expand Up @@ -352,7 +370,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.action(async ({ chain }) => {
if (!isValidChain(chain)) {
Expand Down Expand Up @@ -383,7 +401,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.requiredOption("-s, --account-salt <salt>", "account salt")
.action(async ({ chain, accountSalt }) => {
Expand Down Expand Up @@ -453,7 +471,7 @@ program
.addOption(
new Command()
.createOption("-c, --chain <chain>", "choose chain")
.choices(["base", "optimism"]),
.choices(["base", "mantle", "optimism"]),
)
.requiredOption("-s, --account-salt <salt>", "account salt")
.action(async ({ chain, accountSalt }) => {
Expand Down
4 changes: 2 additions & 2 deletions demo/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ export function getRepayTokens(sender: Address) {
// TODO: check sender locked-funds
return concat([
"0x01", // length of the array (only one repay token)
vaultA["optimism"] as Address,
pad(numberToHex(500), { size: 32 }),
vaultA["optimism"] as Address, // DEMO: only gets repaid on optimism
pad(numberToHex(500), { size: 32 }), // DEMO: fixed amount tokens are repaid
pad(numberToHex(chainIDs["optimism"]), { size: 32 }),
]);
}
Expand Down
65 changes: 49 additions & 16 deletions demo/viemClients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,89 @@ import { BundlerClient, createBundlerClient } from "viem/account-abstraction";
import { baseSepolia, optimismSepolia } from "viem/chains";
import { ownerAccount, supportedChain } from "./constants";
import { getPaymasterActions } from "./paymaster";
import { mantleSepoliaTestnet } from "viem/chains";

export const optimismPublicClient = createPublicClient({

// ============================= PUBLIC CLIENTS =============================

const mantleSepoliaPublicClient = createPublicClient({
chain: mantleSepoliaTestnet,
transport: http(),
});

const optimismPublicClient = createPublicClient({
chain: optimismSepolia,
transport: http(),
});

export const baseSepoliaPublicClient = createPublicClient({
const baseSepoliaPublicClient = createPublicClient({
chain: baseSepolia,
transport: http(),
});

export const baseSepoliaWalletClient = createWalletClient({
export const publicClients: Record<supportedChain, PublicClient> = {
optimism: optimismPublicClient as PublicClient,
base: baseSepoliaPublicClient as PublicClient,
mantle: mantleSepoliaPublicClient as PublicClient,
};


// ============================= WALLET CLIENTS =============================

const optimismWalletClient = createWalletClient({
account: ownerAccount,
chain: optimismSepolia,
transport: http(),
});

const baseSepoliaWalletClient = createWalletClient({
account: ownerAccount,
chain: baseSepolia,
transport: http(),
});

export const optimismWalletClient = createWalletClient({
const mantleSepoliaWalletClient = createWalletClient({
account: ownerAccount,
chain: optimismSepolia,
chain: mantleSepoliaTestnet,
transport: http(),
});

export const baseSepoliaBundlerClient = createBundlerClient({
export const walletClients: Record<supportedChain, WalletClient> = {
optimism: optimismWalletClient,
base: baseSepoliaWalletClient,
mantle: mantleSepoliaWalletClient,
};

// ============================= BUNDLER CLIENTS =============================

const baseSepoliaBundlerClient = createBundlerClient({
client: baseSepoliaPublicClient,
paymaster: getPaymasterActions("base"),
transport: http(
`https://api.pimlico.io/v2/base-sepolia/rpc?apikey=${process.env.PIMLICO_API_KEY}`,
),
});

export const optimismBundlerClient = createBundlerClient({

const optimismBundlerClient = createBundlerClient({
client: optimismPublicClient,
paymaster: getPaymasterActions("optimism"),
transport: http(
`https://api.pimlico.io/v2/optimism-sepolia/rpc?apikey=${process.env.PIMLICO_API_KEY}`,
),
});

export const walletClients: Record<supportedChain, WalletClient> = {
optimism: optimismWalletClient,
base: baseSepoliaWalletClient,
};

export const publicClients: Record<supportedChain, PublicClient> = {
optimism: optimismPublicClient as PublicClient,
base: baseSepoliaPublicClient as PublicClient,
};
// NOTE: running local bundler for mantle sepolia demo because pimlico doesn't support it yet
const mantleSepoliaBundlerClient = createBundlerClient({
client: mantleSepoliaPublicClient,
paymaster: getPaymasterActions("mantle"),
transport: http("http://127.0.0.1:4337"),
});

export const bundlerClients: Record<supportedChain, BundlerClient> = {
optimism: optimismBundlerClient as BundlerClient,
base: baseSepoliaBundlerClient as BundlerClient,
mantle: mantleSepoliaBundlerClient as BundlerClient,
};


1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
src = "src"
out = "out"
libs = ["lib"]
evm_version = "paris"
via_ir = true
gas_reports = ["*"]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
9 changes: 8 additions & 1 deletion src/paymasters/CABPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
}

function withdraw(address token, uint256 amount) external override onlyOwner {
IERC20(token).safeTransfer(owner(), amount);
if (token == NATIVE_TOKEN) {
(bool success,) = payable(owner()).call{value: amount}("");
require(success, "Native token transfer failed");
} else {
IERC20(token).safeTransfer(owner(), amount);
}
}

function getHash(PackedUserOperation calldata userOp, uint48 validUntil, uint48 validAfter)
Expand Down Expand Up @@ -275,4 +280,6 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
}
return abi.encodePacked(uint8(repayTokens.length), encodedRepayToken);
}

receive() external payable {}
}

0 comments on commit e1ecb99

Please sign in to comment.