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 }