diff --git a/contracts-bits-n-bobs/.gitignore b/contracts-bits-n-bobs/.gitignore new file mode 100644 index 0000000..31313b4 --- /dev/null +++ b/contracts-bits-n-bobs/.gitignore @@ -0,0 +1,3 @@ +artifacts +cache +typechain-types \ No newline at end of file diff --git a/frontend/src/ERC20/IERC20.ts b/frontend/src/ERC20/IERC20.ts new file mode 100644 index 0000000..34bd365 --- /dev/null +++ b/frontend/src/ERC20/IERC20.ts @@ -0,0 +1,342 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from "ethers"; +import type { + FunctionFragment, + Result, + EventFragment, +} from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent, + PromiseOrValue, +} from "./common"; + +export interface IERC20Interface extends utils.Interface { + functions: { + "allowance(address,address)": FunctionFragment; + "approve(address,uint256)": FunctionFragment; + "balanceOf(address)": FunctionFragment; + "totalSupply()": FunctionFragment; + "transfer(address,uint256)": FunctionFragment; + "transferFrom(address,address,uint256)": FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: + | "allowance" + | "approve" + | "balanceOf" + | "totalSupply" + | "transfer" + | "transferFrom" + ): FunctionFragment; + + encodeFunctionData( + functionFragment: "allowance", + values: [PromiseOrValue, PromiseOrValue] + ): string; + encodeFunctionData( + functionFragment: "approve", + values: [PromiseOrValue, PromiseOrValue] + ): string; + encodeFunctionData( + functionFragment: "balanceOf", + values: [PromiseOrValue] + ): string; + encodeFunctionData( + functionFragment: "totalSupply", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "transfer", + values: [PromiseOrValue, PromiseOrValue] + ): string; + encodeFunctionData( + functionFragment: "transferFrom", + values: [ + PromiseOrValue, + PromiseOrValue, + PromiseOrValue + ] + ): string; + + decodeFunctionResult(functionFragment: "allowance", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "approve", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "balanceOf", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "totalSupply", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "transfer", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "transferFrom", + data: BytesLike + ): Result; + + events: { + "Approval(address,address,uint256)": EventFragment; + "Transfer(address,address,uint256)": EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: "Approval"): EventFragment; + getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment; +} + +export interface ApprovalEventObject { + owner: string; + spender: string; + value: BigNumber; +} +export type ApprovalEvent = TypedEvent< + [string, string, BigNumber], + ApprovalEventObject +>; + +export type ApprovalEventFilter = TypedEventFilter; + +export interface TransferEventObject { + from: string; + to: string; + value: BigNumber; +} +export type TransferEvent = TypedEvent< + [string, string, BigNumber], + TransferEventObject +>; + +export type TransferEventFilter = TypedEventFilter; + +export interface IERC20 extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: IERC20Interface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>; + + listeners( + eventFilter?: TypedEventFilter + ): Array>; + listeners(eventName?: string): Array; + removeAllListeners( + eventFilter: TypedEventFilter + ): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + allowance( + owner: PromiseOrValue, + spender: PromiseOrValue, + overrides?: CallOverrides + ): Promise<[BigNumber]>; + + approve( + spender: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + balanceOf( + account: PromiseOrValue, + overrides?: CallOverrides + ): Promise<[BigNumber]>; + + totalSupply(overrides?: CallOverrides): Promise<[BigNumber]>; + + transfer( + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + transferFrom( + from: PromiseOrValue, + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + }; + + allowance( + owner: PromiseOrValue, + spender: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + approve( + spender: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + balanceOf( + account: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + totalSupply(overrides?: CallOverrides): Promise; + + transfer( + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + transferFrom( + from: PromiseOrValue, + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + callStatic: { + allowance( + owner: PromiseOrValue, + spender: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + approve( + spender: PromiseOrValue, + amount: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + balanceOf( + account: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + totalSupply(overrides?: CallOverrides): Promise; + + transfer( + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + transferFrom( + from: PromiseOrValue, + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + }; + + filters: { + "Approval(address,address,uint256)"( + owner?: PromiseOrValue | null, + spender?: PromiseOrValue | null, + value?: null + ): ApprovalEventFilter; + Approval( + owner?: PromiseOrValue | null, + spender?: PromiseOrValue | null, + value?: null + ): ApprovalEventFilter; + + "Transfer(address,address,uint256)"( + from?: PromiseOrValue | null, + to?: PromiseOrValue | null, + value?: null + ): TransferEventFilter; + Transfer( + from?: PromiseOrValue | null, + to?: PromiseOrValue | null, + value?: null + ): TransferEventFilter; + }; + + estimateGas: { + allowance( + owner: PromiseOrValue, + spender: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + approve( + spender: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + balanceOf( + account: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + totalSupply(overrides?: CallOverrides): Promise; + + transfer( + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + transferFrom( + from: PromiseOrValue, + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + }; + + populateTransaction: { + allowance( + owner: PromiseOrValue, + spender: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + approve( + spender: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + balanceOf( + account: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + + totalSupply(overrides?: CallOverrides): Promise; + + transfer( + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + + transferFrom( + from: PromiseOrValue, + to: PromiseOrValue, + amount: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + }; +} diff --git a/frontend/src/ERC20/IERC20__factory.ts b/frontend/src/ERC20/IERC20__factory.ts new file mode 100644 index 0000000..0329513 --- /dev/null +++ b/frontend/src/ERC20/IERC20__factory.ts @@ -0,0 +1,206 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Signer, utils } from "ethers"; +import type { Provider } from "@ethersproject/providers"; +import type { + IERC20, + IERC20Interface, +} from "./IERC20"; + +const _abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "spender", + type: "address", + }, + ], + name: "allowance", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "spender", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "approve", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "transfer", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "transferFrom", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + +export class IERC20__factory { + static readonly abi = _abi; + static createInterface(): IERC20Interface { + return new utils.Interface(_abi) as IERC20Interface; + } + static connect(address: string, signerOrProvider: Signer | Provider): IERC20 { + return new Contract(address, _abi, signerOrProvider) as IERC20; + } +} diff --git a/frontend/src/ERC20/common.ts b/frontend/src/ERC20/common.ts new file mode 100644 index 0000000..4c90b08 --- /dev/null +++ b/frontend/src/ERC20/common.ts @@ -0,0 +1,46 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { Listener } from "@ethersproject/providers"; +import type { Event, EventFilter } from "ethers"; + +export interface TypedEvent< + TArgsArray extends Array = any, + TArgsObject = any +> extends Event { + args: TArgsArray & TArgsObject; +} + +export interface TypedEventFilter<_TEvent extends TypedEvent> + extends EventFilter {} + +export interface TypedListener { + (...listenerArg: [...__TypechainArgsArray, TEvent]): void; +} + +type __TypechainArgsArray = T extends TypedEvent ? U : never; + +export interface OnEvent { + ( + eventFilter: TypedEventFilter, + listener: TypedListener + ): TRes; + (eventName: string, listener: Listener): TRes; +} + +export type MinEthersFactory = { + deploy(...a: ARGS[]): Promise; +}; + +export type GetContractTypeFromFactory = F extends MinEthersFactory< + infer C, + any +> + ? C + : never; + +export type GetARGsTypeFromFactory = F extends MinEthersFactory + ? Parameters + : never; + +export type PromiseOrValue = T | Promise; diff --git a/frontend/src/app/AppContext.ts b/frontend/src/app/AppContext.ts index 690c42b..64a8d01 100644 --- a/frontend/src/app/AppContext.ts +++ b/frontend/src/app/AppContext.ts @@ -113,10 +113,24 @@ export default class AppContext { console.log('tx complete'); } - async addSignature(paymentChannel: PaymentChannel, payment: Payment) { - const encodedPayment = PaymentChannel.encodePayment(payment); + createPayment( + simplePayment: Pick, + ): Payment { + return { + sender: this.address, + nonce: '0x00', // TODO: nonce + token: '0x0000000000000000000000000000000000000000', // TODO: token + ...simplePayment, + }; + } - const signature = this.signer.sign(ethers.utils.hexlify(encodedPayment)); + async addSignature(paymentChannel: PaymentChannel, payment: Payment) { + const encodedPayment = await PaymentChannel.encodePayment( + payment, + this.aaProvider, + ); + + const signature = this.signer.sign(encodedPayment); await paymentChannel.addSignature(this.signer.pubkey, signature); } diff --git a/frontend/src/app/PaymentChannel.ts b/frontend/src/app/PaymentChannel.ts index 871aa55..ec839f1 100644 --- a/frontend/src/app/PaymentChannel.ts +++ b/frontend/src/app/PaymentChannel.ts @@ -3,11 +3,21 @@ import * as io from 'io-ts'; import { signer } from '@thehubbleproject/bls'; import Channel from '../utils/Channel'; import assertType from '../utils/assertType'; +import { ERC4337EthersProvider } from '@account-abstraction/sdk'; +import { IERC20__factory } from '../ERC20/IERC20__factory'; + +const callGasLimit = '1000000'; +const verificationGasLimit = '1000000'; +const preVerificationGas = '1000000'; +const maxFeePerGas = '100000000000'; // 100 gwei +const maxPriorityFeePerGas = '1000000000'; // 1 gwei export const Payment = io.type({ + sender: io.string, + nonce: io.string, token: io.string, to: io.string, - amount: io.number, + amount: io.string, description: io.string, }); @@ -78,8 +88,33 @@ export default class PaymentChannel { }; } - static encodePayment(payment: Payment) { - // TODO: Get the actual message that needs to be signed - return new TextEncoder().encode(JSON.stringify(payment)); + static async encodePayment( + payment: Payment, + aaProvider: ERC4337EthersProvider, + ) { + const callData = await aaProvider.smartAccountAPI.encodeExecute( + payment.token, + 0, + IERC20__factory.createInterface().encodeFunctionData('transfer', [ + payment.to, + payment.amount, + ]), + ); + + const userOpHash = await aaProvider.smartAccountAPI.getUserOpHash({ + sender: payment.sender, + nonce: payment.nonce, + initCode: '0x', + callData, + callGasLimit, + verificationGasLimit, + preVerificationGas, + maxFeePerGas, + maxPriorityFeePerGas, + paymasterAndData: '0x', + signature: '0x', + }); + + return userOpHash; } }