{"version":3,"file":"resolveDomain.cjs","sources":["../../../src/domain/resolveDomain.ts"],"sourcesContent":["import {\r\n  Address,\r\n  GetAccountInfoApi,\r\n  GetMultipleAccountsApi,\r\n  GetTokenLargestAccountsApi,\r\n  Rpc,\r\n  fetchEncodedAccount,\r\n  fetchEncodedAccounts,\r\n  getPublicKeyFromAddress,\r\n} from \"@solana/kit\";\r\n\r\nimport { addressCodec, utf8Codec } from \"../codecs\";\r\nimport {\r\n  CouldNotFindNftOwnerError,\r\n  DomainDoesNotExistError,\r\n  InvalidRoAError,\r\n  InvalidValidationError,\r\n  PdaOwnerNotAllowedError,\r\n  RecordMalformedError,\r\n} from \"../errors\";\r\nimport { getNftOwner } from \"../nft/getNftOwner\";\r\nimport { getRecordV1Address } from \"../record/getRecordV1Address\";\r\nimport { getRecordV2Address } from \"../record/getRecordV2Address\";\r\nimport { NftState, NftTag } from \"../states/nft\";\r\nimport { RecordState } from \"../states/record\";\r\nimport { RegistryState } from \"../states/registry\";\r\nimport { Record } from \"../types/record\";\r\nimport { Validation } from \"../types/validation\";\r\nimport { checkAddressOnCurve } from \"../utils/checkAddressOnCurve\";\r\nimport { uint8ArrayToHex } from \"../utils/uint8Array/uint8ArrayToHex\";\r\nimport { uint8ArraysEqual } from \"../utils/uint8Array/uint8ArraysEqual\";\r\nimport { getDomainAddress } from \"./getDomainAddress\";\r\n\r\ninterface ResolveDomainParams {\r\n  rpc: Rpc<\r\n    GetAccountInfoApi & GetMultipleAccountsApi & GetTokenLargestAccountsApi\r\n  >;\r\n  domain: string;\r\n  options?: ResolveOptions;\r\n}\r\n\r\nexport type AllowPda = \"any\" | boolean;\r\n\r\ntype ResolveOptions = AllowPda extends true\r\n  ? {\r\n      allowPda: true;\r\n      programIds: Address[];\r\n    }\r\n  : {\r\n      allowPda: AllowPda;\r\n      programIds?: Address[];\r\n    };\r\n\r\n/**\r\n * Verifies the signature of a Solana record using Ed25519 cryptographic verification.\r\n *\r\n * @param data - The record data to verify.\r\n * @param signature - The signature associated with the record.\r\n * @param address - The address of the record's ownery.\r\n * @returns A promise that resolves to a boolean indicating whether the signature is valid.\r\n */\r\nconst verifySolRecordV1Signature = async ({\r\n  data,\r\n  signature,\r\n  address,\r\n}: {\r\n  data: Uint8Array;\r\n  signature: Uint8Array;\r\n  address: Address;\r\n}) => {\r\n  const publicKey = await getPublicKeyFromAddress(address);\r\n\r\n  // Convert `data` to a hex string and then back to a `Uint8Array`\r\n  const encodedHexString = utf8Codec.encode(uint8ArrayToHex(data));\r\n\r\n  const result = await crypto.subtle.verify(\r\n    {\r\n      name: \"Ed25519\",\r\n    },\r\n    publicKey,\r\n    signature,\r\n    encodedHexString\r\n  );\r\n\r\n  return result;\r\n};\r\n\r\n/**\r\n * Resolves domain according to SNS-IP 5.\r\n *\r\n * @param params - An object containing the following properties:\r\n *   - `rpc`: An RPC interface implementing GetAccountInfoApi, GetMultipleAccountsApi, and GetTokenLargestAccountsApi.\r\n *   - `domain`: The domain to resolve.\r\n *   - `config`: (Optional) Configuration for resolving the domain, including whether to allow PDA owners\r\n *     and permissible program IDs.\r\n * @returns A promise that resolves to the target address.\r\n */\r\nexport const resolveDomain = async ({\r\n  rpc,\r\n  domain,\r\n  options = { allowPda: false },\r\n}: ResolveDomainParams): Promise<Address> => {\r\n  const { domainAddress } = await getDomainAddress({ domain });\r\n  const nftAddress = await NftState.getAddress(domainAddress);\r\n  const solRecordV1Address = await getRecordV1Address({\r\n    domain,\r\n    record: Record.SOL,\r\n  });\r\n  const solRecordV2Address = await getRecordV2Address({\r\n    domain,\r\n    record: Record.SOL,\r\n  });\r\n  const [domainAccount, nftAccount, solRecordV1Account, solRecordV2Account] =\r\n    await fetchEncodedAccounts(rpc, [\r\n      domainAddress,\r\n      nftAddress,\r\n      solRecordV1Address,\r\n      solRecordV2Address,\r\n    ]);\r\n\r\n  if (!domainAccount.exists) {\r\n    throw new DomainDoesNotExistError(`Domain ${domain} does not exist`);\r\n  }\r\n\r\n  const registry = RegistryState.deserialize(domainAccount.data);\r\n\r\n  // If NFT account exists, then the NFT owner is the domain owner\r\n  if (nftAccount.exists) {\r\n    const nftRecord = NftState.deserialize(nftAccount.data);\r\n    if (nftRecord.tag === NftTag.ActiveRecord) {\r\n      const nftOwner = await getNftOwner({ rpc, domainAddress });\r\n      if (!nftOwner) {\r\n        throw new CouldNotFindNftOwnerError();\r\n      }\r\n      return nftOwner;\r\n    }\r\n  }\r\n\r\n  // Check SOL record V2\r\n  recordV2: if (solRecordV2Account.exists) {\r\n    const recordV2 = RecordState.deserialize(solRecordV2Account.data);\r\n    const stalenessId = recordV2.getStalenessId();\r\n    const roaId = recordV2.getRoAId();\r\n    const content = recordV2.getContent();\r\n\r\n    if (content.length !== 32) {\r\n      throw new RecordMalformedError(\"Record is malformed\");\r\n    }\r\n\r\n    if (\r\n      recordV2.header.rightOfAssociationValidation !== Validation.Solana ||\r\n      recordV2.header.stalenessValidation !== Validation.Solana\r\n    ) {\r\n      throw new InvalidValidationError();\r\n    }\r\n\r\n    if (registry.owner !== addressCodec.decode(stalenessId)) {\r\n      break recordV2;\r\n    }\r\n\r\n    if (uint8ArraysEqual(roaId, content)) {\r\n      return addressCodec.decode(content);\r\n    }\r\n\r\n    throw new InvalidRoAError(\r\n      `The RoA ID shoudl be ${addressCodec.decode(content)} but is ${addressCodec.decode(roaId)} `\r\n    );\r\n  }\r\n\r\n  // Check SOL record V1\r\n  if (solRecordV1Account.exists) {\r\n    const data = new Uint8Array([\r\n      ...solRecordV1Account.data.slice(\r\n        RegistryState.HEADER_LEN,\r\n        RegistryState.HEADER_LEN + 32\r\n      ),\r\n      ...addressCodec.encode(solRecordV1Address),\r\n    ]);\r\n\r\n    const signature = solRecordV1Account.data.slice(\r\n      RegistryState.HEADER_LEN + 32,\r\n      RegistryState.HEADER_LEN + 32 + 64\r\n    );\r\n\r\n    const valid = await verifySolRecordV1Signature({\r\n      data,\r\n      signature,\r\n      address: registry.owner,\r\n    });\r\n\r\n    if (valid) {\r\n      return addressCodec.decode(\r\n        solRecordV1Account.data.slice(\r\n          RegistryState.HEADER_LEN,\r\n          RegistryState.HEADER_LEN + 32\r\n        )\r\n      );\r\n    }\r\n  }\r\n\r\n  // Check if the registry owner is a PDA\r\n  const isOnCurve = checkAddressOnCurve(registry.owner);\r\n\r\n  if (!isOnCurve) {\r\n    if (options.allowPda === \"any\") {\r\n      return registry.owner;\r\n    } else if (options.allowPda) {\r\n      const ownerAccount = await fetchEncodedAccount(rpc, registry.owner);\r\n\r\n      if (!ownerAccount.exists) {\r\n        throw new PdaOwnerNotAllowedError(\"Invalid domain owner account\");\r\n      }\r\n\r\n      const isAllowed = options.programIds?.some(\r\n        (e) => ownerAccount.programAddress === e\r\n      );\r\n\r\n      if (isAllowed) {\r\n        return registry.owner;\r\n      }\r\n\r\n      throw new PdaOwnerNotAllowedError(\r\n        `The program ${ownerAccount.programAddress} is not allowed`\r\n      );\r\n    } else {\r\n      throw new PdaOwnerNotAllowedError();\r\n    }\r\n  }\r\n\r\n  return registry.owner;\r\n};\r\n"],"names":["async","rpc","domain","options","allowPda","domainAddress","getDomainAddress","nftAddress","NftState","getAddress","solRecordV1Address","getRecordV1Address","record","Record","SOL","solRecordV2Address","getRecordV2Address","domainAccount","nftAccount","solRecordV1Account","solRecordV2Account","fetchEncodedAccounts","exists","DomainDoesNotExistError","registry","RegistryState","deserialize","data","tag","NftTag","ActiveRecord","nftOwner","getNftOwner","CouldNotFindNftOwnerError","recordV2","RecordState","stalenessId","getStalenessId","roaId","getRoAId","content","getContent","length","RecordMalformedError","header","rightOfAssociationValidation","Validation","Solana","stalenessValidation","InvalidValidationError","owner","addressCodec","decode","uint8ArraysEqual","InvalidRoAError","Uint8Array","slice","HEADER_LEN","encode","signature","valid","address","publicKey","getPublicKeyFromAddress","encodedHexString","utf8Codec","uint8ArrayToHex","crypto","subtle","verify","name","verifySolRecordV1Signature","checkAddressOnCurve","ownerAccount","fetchEncodedAccount","PdaOwnerNotAllowedError","isAllowed","programIds","some","e","programAddress"],"mappings":"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"}