Skip to content
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

Add NearConfig to Near Safe Constructor #59

Merged
merged 3 commits into from
Sep 22, 2024
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
6 changes: 3 additions & 3 deletions examples/send-tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ethers } from "ethers";
import { isAddress } from "viem";

import { loadArgs, loadEnv } from "./cli";
import { TransactionManager } from "../src";
import { NearSafe } from "../src";

dotenv.config();

Expand All @@ -13,7 +13,7 @@ async function main(): Promise<void> {
{ mpcContractId, recoveryAddress, usePaymaster },
] = await Promise.all([loadEnv(), loadArgs()]);
const chainId = 11155111;
const txManager = await TransactionManager.create({
const txManager = await NearSafe.create({
accountId: nearAccountId,
mpcContractId,
pimlicoKey,
Expand Down Expand Up @@ -52,7 +52,7 @@ async function main(): Promise<void> {
// TODO: Evaluate gas cost (in ETH)
const gasCost = ethers.parseEther("0.01");
// Whenever not using paymaster, or on value transfer, the Safe must be funded.
const sufficientFunded = await txManager.safeSufficientlyFunded(
const sufficientFunded = await txManager.sufficientlyFunded(
chainId,
transactions,
usePaymaster ? 0n : gasCost
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from "./tx-manager";
export * from "./near-safe";
export * from "./types";
export * from "./util";

Expand Down
6 changes: 3 additions & 3 deletions src/lib/safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ interface DeploymentData {
/**
* All contracts used in account creation & execution
*/
export class ContractSuite {
export class SafeContractSuite {
// Used only for stateless contract reads.
dummyClient: PublicClient;
singleton: DeploymentData;
Expand All @@ -67,7 +67,7 @@ export class ContractSuite {
this.entryPoint = entryPoint;
}

static async init(): Promise<ContractSuite> {
static async init(): Promise<SafeContractSuite> {
// TODO - this is a cheeky hack.
const client = getClient(11155111);
const safeDeployment = (fn: DeploymentFunction): Promise<DeploymentData> =>
Expand All @@ -92,7 +92,7 @@ export class ContractSuite {
// moduleSetup: await moduleSetup.getAddress(),
// entryPoint: await entryPoint.getAddress(),
// });
return new ContractSuite(
return new SafeContractSuite(
client,
singleton,
proxyFactory,
Expand Down
164 changes: 79 additions & 85 deletions src/tx-manager.ts → src/near-safe.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NearConfig } from "near-api-js/lib/near";
import { FinalExecutionOutcome } from "near-api-js/lib/providers";
import {
NearEthAdapter,
Expand All @@ -12,7 +13,7 @@ import { Address, Hash, Hex, serializeSignature } from "viem";

import { Erc4337Bundler } from "./lib/bundler";
import { encodeMulti } from "./lib/multisend";
import { ContractSuite } from "./lib/safe";
import { SafeContractSuite } from "./lib/safe";
import { decodeSafeMessage } from "./lib/safe-message";
import {
EncodedTxData,
Expand All @@ -27,62 +28,64 @@ import {
packSignature,
} from "./util";

export class TransactionManager {
export interface NearSafeConfig {
accountId: string;
mpcContractId: string;
pimlicoKey: string;
nearConfig?: NearConfig;
privateKey?: string;
safeSaltNonce?: string;
}

export class NearSafe {
readonly nearAdapter: NearEthAdapter;
readonly address: Address;

private safePack: ContractSuite;
private safePack: SafeContractSuite;
private setup: string;
private pimlicoKey: string;
private safeSaltNonce: string;
private deployedChains: Set<number>;

static async create(config: NearSafeConfig): Promise<NearSafe> {
const { pimlicoKey, safeSaltNonce } = config;
const [nearAdapter, safePack] = await Promise.all([
setupAdapter({ ...config }),
SafeContractSuite.init(),
]);

const setup = safePack.getSetup([nearAdapter.address]);
const safeAddress = await safePack.addressForSetup(setup, safeSaltNonce);
console.log(`
Near Adapter:
Near Account ID: ${nearAdapter.nearAccountId()}
MPC EOA: ${nearAdapter.address}
Safe: ${safeAddress}
`);
return new NearSafe(
nearAdapter,
safePack,
pimlicoKey,
setup,
safeAddress,
safeSaltNonce || "0"
);
}

constructor(
nearAdapter: NearEthAdapter,
safePack: ContractSuite,
safePack: SafeContractSuite,
pimlicoKey: string,
setup: string,
safeAddress: Address,
safeSaltNonce: string
) {
this.nearAdapter = nearAdapter;
this.address = safeAddress;

this.setup = setup;
this.safePack = safePack;
this.pimlicoKey = pimlicoKey;
this.setup = setup;
this.address = safeAddress;
this.safeSaltNonce = safeSaltNonce;
this.deployedChains = new Set();
}

static async create(config: {
accountId: string;
mpcContractId: string;
pimlicoKey: string;
privateKey?: string;
safeSaltNonce?: string;
}): Promise<TransactionManager> {
const { pimlicoKey } = config;
const [nearAdapter, safePack] = await Promise.all([
setupAdapter({ ...config }),
ContractSuite.init(),
]);
console.log(
`Near Adapter: ${nearAdapter.nearAccountId()} <> ${nearAdapter.address}`
);
const setup = safePack.getSetup([nearAdapter.address]);
const safeAddress = await safePack.addressForSetup(
setup,
config.safeSaltNonce
);
console.log(`Safe Address: ${safeAddress}`);
return new TransactionManager(
nearAdapter,
safePack,
pimlicoKey,
setup,
safeAddress,
config.safeSaltNonce || "0"
);
}

get mpcAddress(): Address {
Expand All @@ -97,14 +100,6 @@ export class TransactionManager {
return await getClient(chainId).getBalance({ address: this.address });
}

bundlerForChainId(chainId: number): Erc4337Bundler {
return new Erc4337Bundler(
this.safePack.entryPoint.address,
this.pimlicoKey,
chainId
);
}

async buildTransaction(args: {
chainId: number;
transactions: MetaTransaction[];
Expand Down Expand Up @@ -175,6 +170,28 @@ export class TransactionManager {
},
};
}
async broadcastEvm(
chainId: number,
outcome: FinalExecutionOutcome,
unsignedUserOp: UserOperation
): Promise<{ signature: Hex; receipt: UserOperationReceipt }> {
const signature = packSignature(
serializeSignature(signatureFromOutcome(outcome))
);
try {
return {
signature,
receipt: await this.executeTransaction(chainId, {
...unsignedUserOp,
signature,
}),
};
} catch (error: unknown) {
throw new Error(
`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`
);
}
}

async executeTransaction(
chainId: number,
Expand All @@ -193,26 +210,10 @@ export class TransactionManager {
}

async safeDeployed(chainId: number): Promise<boolean> {
// Early exit if already known.
if (chainId in this.deployedChains) {
return true;
}
const deployed = await isContract(this.address, chainId);
if (deployed) {
this.deployedChains.add(chainId);
}
return deployed;
return isContract(this.address, chainId);
}

addOwnerTx(address: Address): MetaTransaction {
return {
to: this.address,
value: "0",
data: this.safePack.addOwnerData(address),
};
}

async safeSufficientlyFunded(
async sufficientlyFunded(
chainId: number,
transactions: MetaTransaction[],
gasCost: bigint
Expand All @@ -228,27 +229,20 @@ export class TransactionManager {
return txValue + gasCost < safeBalance;
}

async broadcastEvm(
chainId: number,
outcome: FinalExecutionOutcome,
unsignedUserOp: UserOperation
): Promise<{ signature: Hex; receipt: UserOperationReceipt }> {
const signature = packSignature(
serializeSignature(signatureFromOutcome(outcome))
addOwnerTx(address: Address): MetaTransaction {
return {
to: this.address,
value: "0",
data: this.safePack.addOwnerData(address),
};
}

private bundlerForChainId(chainId: number): Erc4337Bundler {
return new Erc4337Bundler(
this.safePack.entryPoint.address,
this.pimlicoKey,
chainId
);
try {
return {
signature,
receipt: await this.executeTransaction(chainId, {
...unsignedUserOp,
signature,
}),
};
} catch (error: unknown) {
throw new Error(
`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`
);
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/lib/safe.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { zeroAddress } from "viem";

import { ContractSuite as ViemPack } from "../../src/lib/safe";
import { SafeContractSuite as ViemPack } from "../../src/lib/safe";
import { ContractSuite as EthPack } from "../ethers-safe";

describe("Safe Pack", () => {
Expand Down