Skip to content

Commit fd83278

Browse files
authored
Merge pull request #7 from openfort-xyz/feat/repay
Feat/repay
2 parents d222354 + b46b1cd commit fd83278

23 files changed

+1398
-922
lines changed

demo/Invoice.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { z } from "zod";
2+
import {
3+
encodeAbiParameters,
4+
encodePacked,
5+
getAddress,
6+
Hex,
7+
keccak256,
8+
} from "viem";
9+
import fs from "fs/promises";
10+
import path from "path";
11+
import { getRepayTokens } from "./utils";
12+
13+
const RepayTokenInfoSchema = z.object({
14+
vault: z
15+
.string()
16+
.regex(/^0x[a-fA-F0-9]{40}$/)
17+
.transform((val) => getAddress(val)),
18+
amount: z.string().transform((val) => BigInt(val)),
19+
chainId: z.string().transform((val) => BigInt(val)),
20+
});
21+
22+
const InvoiceWithRepayTokensSchema = z.object({
23+
account: z
24+
.string()
25+
.regex(/^0x[a-fA-F0-9]{40}$/)
26+
.transform((val) => getAddress(val)),
27+
paymaster: z
28+
.string()
29+
.regex(/^0x[a-fA-F0-9]{40}$/)
30+
.transform((val) => getAddress(val)),
31+
nonce: z.string().transform((val) => BigInt(val)),
32+
sponsorChainId: z.string().transform((val) => BigInt(val)),
33+
repayTokenInfos: z.array(RepayTokenInfoSchema),
34+
});
35+
36+
const InvoiceIdSchema = z
37+
.string()
38+
.regex(/^0x[a-fA-F0-9]{64}$/, "Must be a 32-byte hex string");
39+
40+
const InvoicesSchema = z.record(InvoiceIdSchema, InvoiceWithRepayTokensSchema);
41+
42+
export type InvoiceId = z.infer<typeof InvoiceIdSchema>;
43+
export type InvoiceWithRepayTokens = z.infer<
44+
typeof InvoiceWithRepayTokensSchema
45+
>;
46+
export type Invoices = z.infer<typeof InvoicesSchema>;
47+
48+
export interface InvoiceIO {
49+
readInvoice(invoiceId: InvoiceId): Promise<InvoiceWithRepayTokens>;
50+
writeInvoice(invoice: InvoiceWithRepayTokens): Promise<InvoiceId>;
51+
}
52+
53+
/**
54+
* InvoiceManager reads and writes invoices to a JSON file.
55+
* FOR DEMO PURPOSES ONLY - implement InvoiceIO to use a database in production.
56+
*/
57+
58+
class InvoiceManager implements InvoiceIO {
59+
private invoicesPath: string;
60+
61+
constructor(invoicesPath: string) {
62+
this.invoicesPath = invoicesPath;
63+
}
64+
65+
async readInvoice(invoiceId: InvoiceId): Promise<InvoiceWithRepayTokens> {
66+
const invoices = await this.readInvoices();
67+
const invoice = invoices[invoiceId];
68+
if (!invoice) {
69+
throw new Error(`Invoice ID ${invoiceId} not found`);
70+
}
71+
return invoice;
72+
}
73+
74+
async writeInvoice(invoice: InvoiceWithRepayTokens): Promise<InvoiceId> {
75+
try {
76+
const invoices = await this.readInvoices();
77+
const invoiceId = this.getInvoiceId(invoice);
78+
invoices[invoiceId] = invoice;
79+
const serializedInvoices = JSON.stringify(
80+
invoices,
81+
(key, value) => (typeof value === "bigint" ? value.toString() : value),
82+
2,
83+
);
84+
await fs.writeFile(this.invoicesPath, serializedInvoices);
85+
return invoiceId;
86+
} catch (error: any) {
87+
console.error("Error handling invoices:", error.message);
88+
throw new Error("Failed to write the invoice.");
89+
}
90+
}
91+
92+
getInvoiceId(invoice: InvoiceWithRepayTokens): InvoiceId {
93+
const repayTokensEncoded = getRepayTokens(invoice.account);
94+
const packed = encodePacked(
95+
["address", "address", "uint256", "uint256", "bytes"],
96+
[
97+
invoice.account,
98+
invoice.paymaster,
99+
invoice.nonce,
100+
invoice.sponsorChainId,
101+
repayTokensEncoded,
102+
],
103+
);
104+
return keccak256(packed) as InvoiceId;
105+
}
106+
107+
private async readInvoices(): Promise<Invoices> {
108+
try {
109+
const fileContent = await fs.readFile(this.invoicesPath, "utf8");
110+
const invoicesJson = fileContent ? JSON.parse(fileContent) : {};
111+
return InvoicesSchema.parse(invoicesJson);
112+
} catch (err: any) {
113+
if (err.code === "ENOENT") {
114+
throw new Error(`File not found at path: ${this.invoicesPath}`);
115+
} else if (err.name === "SyntaxError") {
116+
throw new Error(`Invalid JSON format in file: ${this.invoicesPath}`);
117+
} else if (err instanceof z.ZodError) {
118+
throw new Error(`Validation error: ${err.message}`);
119+
} else {
120+
throw new Error(`An unexpected error occurred: ${err.message}`);
121+
}
122+
}
123+
}
124+
}
125+
126+
export const invoiceManager = new InvoiceManager(
127+
path.join(__dirname, "invoices.json"),
128+
);

0 commit comments

Comments
 (0)