/ packages / contracts / mod.ts
mod.ts
  1  // SPDX-FileCopyrightText: 2024 Mass Labs
  2  //
  3  // SPDX-License-Identifier: Unlicense
  4  import {
  5    type Abi,
  6    type Account,
  7    bytesToHex,
  8    type ContractFunctionArgs,
  9    type ContractFunctionName,
 10    type ContractFunctionReturnType,
 11    type Hex,
 12    hexToBytes,
 13    numberToBytes,
 14    pad,
 15    type PublicClient,
 16    type WalletClient,
 17    type WriteContractParameters,
 18    zeroAddress,
 19  } from "viem";
 20  import { parseAccount, privateKeyToAccount } from "viem/accounts";
 21  import tokenAddresses from "./tokenAddresses.json" with { type: "json" };
 22  export { tokenAddresses };
 23  import * as abi from "./src/generated.ts";
 24  export { abi };
 25  
 26  export const permissions = {
 27    addPermission: 0,
 28    removePermission: 1,
 29    updateRootHash: 2,
 30    addRelay: 3,
 31    removeRelay: 4,
 32    replaceRelay: 5,
 33    registerUser: 6,
 34    removeUser: 7,
 35    publishInviteVerifier: 8,
 36  } as const;
 37  
 38  type Mutable = "nonpayable" | "payable";
 39  type ReadOnly = "view" | "pure";
 40  
 41  export function genericWriteContract<
 42    const abiT extends Abi,
 43    const FuncName extends ContractFunctionName<abiT, Mutable>,
 44  >(abi: abiT, functionName: FuncName, address: Hex) {
 45    return (
 46      wallet: WalletClient,
 47      account: Account | Hex,
 48      args: ContractFunctionArgs<abiT, Mutable, FuncName>,
 49    ) => {
 50      return wallet.writeContract({
 51        chain: wallet.chain,
 52        account,
 53        address,
 54        abi,
 55        functionName,
 56        args,
 57      } as WriteContractParameters);
 58    };
 59  }
 60  
 61  export function genericReadContract<
 62    const abiT extends Abi,
 63    const FuncName extends ContractFunctionName<abiT, ReadOnly>,
 64  >(abi: abiT, functionName: FuncName, address: Hex) {
 65    return (
 66      publicClient: PublicClient,
 67      args: ContractFunctionArgs<abiT, ReadOnly, FuncName>,
 68    ): Promise<
 69      ContractFunctionReturnType<
 70        abiT,
 71        ReadOnly,
 72        FuncName,
 73        ContractFunctionArgs<abiT, ReadOnly, FuncName>
 74      >
 75    > => {
 76      return publicClient.readContract({
 77        address,
 78        abi,
 79        functionName,
 80        args,
 81      });
 82    };
 83  }
 84  
 85  export const payTokenPreApproved = genericWriteContract(
 86    abi.paymentsByAddressAbi,
 87    "payTokenPreApproved",
 88    abi.paymentsByAddressAddress,
 89  );
 90  
 91  export const payNative = genericWriteContract(
 92    abi.paymentsByAddressAbi,
 93    "payNative",
 94    abi.paymentsByAddressAddress,
 95  );
 96  
 97  // Logic for payNative vs. payTokenPreApproved is already baked into the pay contract function.
 98  // Not using genericWriteContract here since we need to pass in the value param for native payments.
 99  export function pay(
100    wallet: WalletClient,
101    account: Account | Hex,
102    args: ContractFunctionArgs<
103      typeof abi.paymentsByAddressAbi,
104      "nonpayable" | "payable",
105      "payTokenPreApproved" | "payNative"
106    >,
107  ) {
108    if (Number(args[0].chainId) !== wallet.chain?.id) {
109      throw new Error("Chain ID mismatch");
110    }
111    return wallet.writeContract({
112      chain: wallet.chain,
113      address: abi.paymentsByAddressAddress,
114      account,
115      abi: abi.paymentsByAddressAbi,
116      functionName: "pay",
117      args,
118      // If paying in native currency, pass in the value param.
119      ...(args[0].currency === zeroAddress &&
120        { value: args[0].amount }),
121    });
122  }
123  export const getAllowance = genericReadContract(
124    abi.eddiesAbi,
125    "allowance",
126    abi.eddiesAddress,
127  );
128  
129  export const getPaymentAddress = genericReadContract(
130    abi.paymentsByAddressAbi,
131    "getPaymentAddress",
132    abi.paymentsByAddressAddress,
133  );
134  
135  export const getPaymentIdRaw = genericReadContract(
136    abi.paymentsByAddressAbi,
137    "getPaymentId",
138    abi.paymentsByAddressAddress,
139  );
140  
141  export async function getPaymentId(
142    ...args: Parameters<typeof getPaymentIdRaw>
143  ): Promise<Uint8Array> {
144    const result: bigint = await getPaymentIdRaw(...args);
145    return pad(numberToBytes(result));
146  }
147  
148  export function approveERC20(
149    wallet: WalletClient,
150    account: Account | Hex,
151    address: Hex,
152    args: ContractFunctionArgs<
153      typeof abi.eddiesAbi,
154      "nonpayable" | "payable",
155      "approve"
156    >,
157  ) {
158    return wallet.writeContract({
159      chain: wallet.chain,
160      address,
161      account,
162      abi: abi.eddiesAbi,
163      functionName: "approve",
164      args,
165    });
166  }
167  export const addRelay = genericWriteContract(
168    abi.shopRegAbi,
169    "addRelay",
170    abi.shopRegAddress,
171  );
172  
173  export const setTokenURI = genericWriteContract(
174    abi.shopRegAbi,
175    "setTokenURI",
176    abi.shopRegAddress,
177  );
178  
179  export const publishInviteVerifier = genericWriteContract(
180    abi.shopRegAbi,
181    "publishInviteVerifier",
182    abi.shopRegAddress,
183  );
184  
185  export const redeemInvite = genericWriteContract(
186    abi.shopRegAbi,
187    "redeemInvite",
188    abi.shopRegAddress,
189  );
190  export const checkPermissions = genericReadContract(
191    abi.shopRegAbi,
192    "hasPermission",
193    abi.shopRegAddress,
194  );
195  
196  export const mintShop = genericWriteContract(
197    abi.shopRegAbi,
198    "mint",
199    abi.shopRegAddress,
200  );
201  
202  export const relayRegGetOwnerOf = genericReadContract(
203    abi.relayRegAbi,
204    "ownerOf",
205    abi.relayRegAddress,
206  );
207  
208  export async function redeemInviteSecret(
209    secret: Hex,
210    wallet: WalletClient,
211    account: Hex | Account,
212    shopId: bigint,
213  ) {
214    const address = parseAccount(account).address;
215    const message = "enrolling:" + address.toLowerCase();
216    const tokenAccount = privateKeyToAccount(secret);
217    const sig = await tokenAccount.signMessage({
218      message,
219    });
220    const sigBytes = hexToBytes(sig);
221    const v = sigBytes[64];
222    const r = bytesToHex(sigBytes.slice(0, 32));
223    const s = bytesToHex(sigBytes.slice(32, 64));
224    return redeemInvite(wallet, account, [
225      shopId,
226      v,
227      r,
228      s,
229      address,
230    ]);
231  }