Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
at main 153 lines 3.6 kB view raw
1import { 2 type AbiItem, 3 type ByteArray, 4 type Hex, 5 type Prettify, 6 serializeSignature 7} from "viem"; 8import { sign } from "viem/accounts"; 9import { 10 bytesToString, 11 decodeFunctionData, 12 encodeAbiParameters, 13 encodeFunctionResult, 14 encodePacked, 15 keccak256, 16 parseAbi, 17 toBytes 18} from "viem/utils"; 19 20type ResolverQueryAddr = { 21 args: 22 | readonly [nodeHash: `0x${string}`] 23 | readonly [nodeHash: `0x${string}`, coinType: bigint]; 24 functionName: "addr"; 25}; 26 27type ResolverQueryText = { 28 args: readonly [nodeHash: `0x${string}`, key: string]; 29 functionName: "text"; 30}; 31 32type ResolverQueryContentHash = { 33 args: readonly [nodeHash: `0x${string}`]; 34 functionName: "contenthash"; 35}; 36 37export type ResolverQuery = Prettify< 38 ResolverQueryAddr | ResolverQueryText | ResolverQueryContentHash 39>; 40 41type DecodedRequestFullReturnType = { 42 name: string; 43 query: ResolverQuery; 44}; 45 46function bytesToPacket(bytes: ByteArray): string { 47 let offset = 0; 48 let result = ""; 49 50 while (offset < bytes.length) { 51 const len = bytes[offset]; 52 if (len === 0) { 53 offset += 1; 54 break; 55 } 56 57 result += `${bytesToString(bytes.subarray(offset + 1, offset + len + 1))}.`; 58 offset += len + 1; 59 } 60 61 return result.replace(/\.$/, ""); 62} 63 64function dnsDecodeName(encodedName: string): string { 65 const bytesName = toBytes(encodedName); 66 return bytesToPacket(bytesName); 67} 68 69const OFFCHAIN_RESOLVER_ABI = parseAbi([ 70 "function resolve(bytes calldata name, bytes calldata data) view returns(bytes memory result, uint64 expires, bytes memory sig)" 71]); 72 73const RESOLVER_ABI = parseAbi([ 74 "function addr(bytes32 node) view returns (address)", 75 "function addr(bytes32 node, uint256 coinType) view returns (bytes memory)", 76 "function text(bytes32 node, string key) view returns (string memory)", 77 "function contenthash(bytes32 node) view returns (bytes memory)" 78]); 79 80export function decodeEnsOffchainRequest({ 81 data 82}: { 83 sender: `0x${string}`; 84 data: `0x${string}`; 85}): DecodedRequestFullReturnType { 86 const decodedResolveCall = decodeFunctionData({ 87 abi: OFFCHAIN_RESOLVER_ABI, 88 data 89 }); 90 91 const [dnsEncodedName, encodedResolveCall] = decodedResolveCall.args; 92 const name = dnsDecodeName(dnsEncodedName); 93 const query = decodeFunctionData({ 94 abi: RESOLVER_ABI, 95 data: encodedResolveCall 96 }); 97 98 return { 99 name, 100 query 101 }; 102} 103 104export async function encodeEnsOffchainResponse( 105 request: { sender: `0x${string}`; data: `0x${string}` }, 106 result: string, 107 signerPrivateKey: Hex 108): Promise<Hex> { 109 const { sender, data } = request; 110 const { query } = decodeEnsOffchainRequest({ data, sender }); 111 const ttl = 1000; 112 const validUntil = Math.floor(Date.now() / 1000 + ttl); 113 114 const abiItem: AbiItem | undefined = RESOLVER_ABI.find( 115 (abi) => 116 abi.name === query.functionName && abi.inputs.length === query.args.length 117 ); 118 119 const functionResult = encodeFunctionResult({ 120 abi: [abiItem], 121 functionName: query.functionName, 122 result 123 }); 124 125 const messageHash = keccak256( 126 encodePacked( 127 ["bytes", "address", "uint64", "bytes32", "bytes32"], 128 [ 129 "0x1900", 130 sender, 131 BigInt(validUntil), 132 keccak256(data), 133 keccak256(functionResult) 134 ] 135 ) 136 ); 137 138 const sig = await sign({ 139 hash: messageHash, 140 privateKey: signerPrivateKey 141 }); 142 143 const encodedResponse = encodeAbiParameters( 144 [ 145 { name: "result", type: "bytes" }, 146 { name: "expires", type: "uint64" }, 147 { name: "sig", type: "bytes" } 148 ], 149 [functionResult, BigInt(validUntil), serializeSignature(sig)] 150 ); 151 152 return encodedResponse; 153}