Skip to content

Commit

Permalink
implement all functions
Browse files Browse the repository at this point in the history
  • Loading branch information
kyscott18 committed Jul 30, 2023
1 parent aea3d76 commit 9bf9103
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 18 deletions.
6 changes: 4 additions & 2 deletions packages/core/src/erc20/reads.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ describe("erc20 reads", () => {
).toBe(true);
});

test.todo("can read nonce");

test("can get token", async () => {
const token = await readAndParse(
getErc20(publicClient, {
Expand All @@ -142,7 +144,7 @@ describe("erc20 reads", () => {
expect(token.decimals).toBe(18);
});

test.todo("can approve", async () => {});
test.todo("can get permit token");

test.todo("can transfer from", async () => {});
test.todo("can check if permit");
});
95 changes: 90 additions & 5 deletions packages/core/src/erc20/reads.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { createAmountFromRaw } from "../amountUtils.js";
import { erc20ABI } from "../generated.js";
import type { ReverseMirageRead } from "../types.js";
import type { ERC20, ERC20Amount } from "./types.js";
import { type Address, type PublicClient, getAddress } from "viem";
import type {
ERC20,
ERC20Amount,
ERC20Permit,
ERC20PermitData,
} from "./types.js";
import { type Address, type Hex, type PublicClient, getAddress } from "viem";

export const erc20BalanceOf = <TERC20 extends ERC20>(
publicClient: PublicClient,
Expand All @@ -20,9 +25,48 @@ export const erc20BalanceOf = <TERC20 extends ERC20>(
} satisfies ReverseMirageRead<bigint>;
};

export const erc20PermitNonce = () => {};
export const erc20PermitNonce = (
publicClient: PublicClient,
args: { erc20: ERC20; address: Address },
) => {
return {
read: () =>
publicClient.readContract({
abi: erc20ABI,
address: args.erc20.address,
functionName: "nonces",
args: [args.address],
}),
parse: (data): bigint => data,
} satisfies ReverseMirageRead<bigint>;
};

export const erc20PermitData = () => {};
export const erc20PermitData = <TERC20 extends ERC20Permit>(
publicClient: PublicClient,
args: { erc20: TERC20; address: Address },
) => {
return {
read: () =>
Promise.all([
publicClient.readContract({
abi: erc20ABI,
address: args.erc20.address,
functionName: "balanceOf",
args: [args.address],
}),
publicClient.readContract({
abi: erc20ABI,
address: args.erc20.address,
functionName: "nonces",
args: [args.address],
}),
]),
parse: (data): ERC20PermitData<TERC20> => ({
...createAmountFromRaw(args.erc20, data[0]),
nonce: data[1],
}),
} satisfies ReverseMirageRead<[bigint, bigint]>;
};

export const erc20Allowance = <TERC20 extends ERC20>(
publicClient: PublicClient,
Expand Down Expand Up @@ -100,6 +144,21 @@ export const erc20Decimals = (
} satisfies ReverseMirageRead<number>;
};

export const erc20PermitDomainSeparator = (
publicClient: PublicClient,
args: { erc20: Pick<ERC20Permit, "address"> },
) => {
return {
read: () =>
publicClient.readContract({
abi: erc20ABI,
address: args.erc20.address,
functionName: "DOMAIN_SEPARATOR",
}),
parse: (data) => data,
} satisfies ReverseMirageRead<Hex>;
};

export const getErc20 = (
publicClient: PublicClient,
args: { erc20: Pick<ERC20, "address" | "chainID"> },
Expand All @@ -122,4 +181,30 @@ export const getErc20 = (
} satisfies ReverseMirageRead<[string, string, number]>;
};

export const getErc20Permit = () => {};
export const getErc20Permit = (
publicClient: PublicClient,
args: {
erc20: Pick<ERC20Permit, "address" | "chainID"> &
Partial<Pick<ERC20Permit, "version">>;
},
) => {
return {
read: () =>
Promise.all([
erc20Name(publicClient, args).read(),
erc20Symbol(publicClient, args).read(),
erc20Decimals(publicClient, args).read(),
]),
parse: (data): ERC20Permit => ({
type: "erc20",
name: data[0],
symbol: data[1],
decimals: data[2],
address: getAddress(args.erc20.address),
chainID: args.erc20.chainID,
version: args.erc20.version ?? "1",
}),
} satisfies ReverseMirageRead<[string, string, number]>;
};

export const erc20IsPermit = () => {};
8 changes: 4 additions & 4 deletions packages/core/src/erc20/types.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import type { Amount } from "../amountUtils.js";
import type { Token } from "../types.js";
import type { Hex } from "viem";
import type { Address } from "viem/accounts";

export type ERC20 = Token<"erc20"> & {
address: Address;
decimals: number;
};

export type ERC20Permit = ERC20 & { domainSeparator: Hex };
export type ERC20Permit = ERC20 & { version: string };

export type ERC20Data<TERC20 extends ERC20> = Amount<TERC20>;

export type ERC20Amount<TERC20 extends ERC20> = ERC20Data<TERC20>;

export type ERC20PermitData<TERC20 extends ERC20> = ERC20Data<TERC20> & {
export type ERC20PermitData<TERC20 extends ERC20Permit> = ERC20Data<TERC20> & {
nonce: bigint;
};

export type ERC20PermitAmount<TERC20 extends ERC20> = ERC20PermitData<TERC20>;
export type ERC20PermitAmount<TERC20 extends ERC20Permit> =
ERC20PermitData<TERC20>;
9 changes: 9 additions & 0 deletions packages/core/src/erc20/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { describe, test } from "vitest";

describe("utils", () => {
test.todo("can create erc20");

test.todo("can create permit erc20");

test.todo("can get typed data hash");
});
61 changes: 58 additions & 3 deletions packages/core/src/erc20/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ERC20 } from "./types.js";
import { type Address, getAddress } from "viem";
import type { ERC20, ERC20Permit, ERC20PermitData } from "./types.js";
import { type Address, getAddress, hashTypedData } from "viem";

export const createErc20 = (
address: Address,
Expand All @@ -16,4 +16,59 @@ export const createErc20 = (
chainID,
});

export const getPermitTypedDataHash = () => {};
export const createErc20Permit = (
address: Address,
name: string,
symbol: string,
decimals: number,
version: string,
chainID: number,
): ERC20Permit => ({
type: "erc20",
address: getAddress(address),
name,
symbol,
decimals,
version,
chainID,
});

export const PermitType = {
Permit: [
{
name: "owner",
type: "address",
},
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
} as const;

export const erc20PermitTypedDataHash = (permit: {
amount: ERC20PermitData<ERC20Permit>;
owner: Address;
spender: Address;
deadline: bigint;
}) => {
const domain = {
name: permit.amount.token.name,
version: permit.amount.token.version,
chainId: permit.amount.token.chainID,
verifyingContract: permit.amount.token.address,
} as const;

return hashTypedData({
domain,
types: PermitType,
primaryType: "Permit",
message: {
owner: permit.owner,
spender: permit.spender,
value: permit.amount.amount,
nonce: permit.amount.nonce,
deadline: permit.deadline,
},
});
};
2 changes: 2 additions & 0 deletions packages/core/src/erc20/writes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,6 @@ describe("erc20 writes", () => {
test.todo("can approve", async () => {});

test.todo("can transfer from", async () => {});

test.todo("can permit");
});
81 changes: 77 additions & 4 deletions packages/core/src/erc20/writes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { erc20ABI } from "../generated.js";
import type { ReverseMirageWrite } from "../types.js";
import type { ERC20, ERC20Amount } from "./types.js";
import type { Account, PublicClient, WalletClient } from "viem";
import type {
ERC20,
ERC20Amount,
ERC20Permit,
ERC20PermitAmount,
ERC20PermitData,
} from "./types.js";
import { PermitType } from "./utils.js";
import invariant from "tiny-invariant";
import type { Account, Hex, PublicClient, WalletClient } from "viem";
import type { Address } from "viem/accounts";

export const erc20Transfer = async (
Expand Down Expand Up @@ -65,6 +73,71 @@ export const erc20TransferFrom = async (
return { hash, result, request };
};

export const erc20SignPermit = async () => {};
export const erc20SignPermit = async (
walletClient: WalletClient,
account: Account | Address,
permit: {
amount: ERC20PermitData<ERC20Permit>;
owner: Address;
spender: Address;
deadline: bigint;
},
) => {
const domain = {
name: permit.amount.token.name,
version: permit.amount.token.version,
chainId: permit.amount.token.chainID,
verifyingContract: permit.amount.token.address,
} as const;

return walletClient.signTypedData({
domain,
account,
types: PermitType,
primaryType: "Permit",
message: {
owner: permit.owner,
spender: permit.spender,
value: permit.amount.amount,
nonce: permit.amount.nonce,
deadline: permit.deadline,
},
});
};

export const erc20Permit = async () => {};
export const erc20Permit = async (
publicClient: PublicClient,
walletClient: WalletClient,
account: Account | Address,
args: {
owner: Address;
spender: Address;
erc20Permit: ERC20PermitAmount<ERC20Permit>;
deadline: bigint;
signature: Hex;
},
): Promise<ReverseMirageWrite<typeof erc20ABI, "permit">> => {
invariant(args.signature.length === 67, "invalid signature length");

const r = `0x${args.signature.substring(2, 2 + 32)}` as const;
const s = `0x${args.signature.substring(34, 34 + 32)}` as const;
const v = Number(args.signature.substring(66));

const { request, result } = await publicClient.simulateContract({
address: args.erc20Permit.token.address,
abi: erc20ABI,
functionName: "permit",
args: [
args.owner,
args.spender,
args.erc20Permit.amount,
args.deadline,
v,
r,
s,
],
account,
});
const hash = await walletClient.writeContract(request);
return { hash, result, request };
};

0 comments on commit 9bf9103

Please sign in to comment.