UNPKG

@opendatalabs/vana-sdk

Version:

A TypeScript library for interacting with Vana Network smart contracts.

240 lines 7.84 kB
import { isAddress } from "viem"; import { buildPersonalServerRegistrationTypedData, personalServerRegistrationDomain } from "../protocol/personal-server-registration.js"; import { SERVER_REGISTRATION_TYPES } from "../protocol/eip712.js"; const ACCOUNT_PERSONAL_SERVER_REGISTRATION_INTENT = "personal_server.server_registration.v1"; class AccountPersonalServerRegistrationError extends Error { status; code; details; constructor(input) { super(input.message); this.name = "AccountPersonalServerRegistrationError"; this.status = input.status; this.code = input.code; this.details = input.details; } } const DEFAULT_ACCOUNT_PS_REGISTRATION_PATH = "/api/v1/intents/personal-server-registration/sign"; function trimTrailingSlash(value) { return value.replace(/\/+$/, ""); } function assertAddress(value, name) { if (!isAddress(value)) { throw new Error(`${name} must be a valid EVM address`); } } async function parseAccountResponse(response) { const body = await response.json().catch(() => void 0); if (!response.ok) { throw new AccountPersonalServerRegistrationError({ status: response.status, code: accountErrorCode(body), message: accountErrorMessage(response.status, body), details: body }); } return body; } function accountErrorMessage(status, body) { const nestedMessage = nestedAccountErrorField(body, "message"); if (nestedMessage) { return nestedMessage; } if (isRecord(body) && typeof body.message === "string") { return body.message; } const code = accountErrorCode(body); if (code) { return `Account PS registration signing failed: ${code}`; } return `Account PS registration signing failed: ${status}`; } function accountErrorCode(body) { const nestedCode = nestedAccountErrorField(body, "code"); if (nestedCode) { return nestedCode; } if (isRecord(body)) { if (typeof body.code === "string") { return body.code; } if (typeof body.error === "string") { return body.error; } } return void 0; } function nestedAccountErrorField(body, field) { if (!isRecord(body) || !isRecord(body.error)) { return void 0; } const value = body.error[field]; return typeof value === "string" ? value : void 0; } function isRecord(value) { return typeof value === "object" && value !== null; } function normalizeAccountResponse(response) { return { ...response, status: response.status === "fallback_required" ? "confirmation_required" : response.status, signerAddress: response.signerAddress ?? response.signer?.address, typedData: response.typedData ?? response.typed_data }; } function buildSignedResult(response, request) { assertAddress(response.signerAddress, "signerAddress"); if (response.typedData) { assertTypedDataMatchesRequest( response.typedData, request, response.signerAddress ); } return { signature: response.signature, signerAddress: response.signerAddress, typedData: response.typedData ?? buildPersonalServerRegistrationTypedData({ ownerAddress: response.signerAddress, ...request }), intent: ACCOUNT_PERSONAL_SERVER_REGISTRATION_INTENT }; } function assertTypedDataMatchesRequest(typedData, request, expectedSignerAddress) { assertAddress( typedData.message.ownerAddress, "typedData.message.ownerAddress" ); assertAddress( typedData.message.serverAddress, "typedData.message.serverAddress" ); if (expectedSignerAddress && !sameAddress(typedData.message.ownerAddress, expectedSignerAddress)) { throw new Error( "Account typedData ownerAddress must match the expected signer address" ); } if (!sameAddress(typedData.message.serverAddress, request.serverAddress)) { throw new Error( "Account typedData serverAddress must match the requested serverAddress" ); } if (typedData.message.publicKey !== request.serverPublicKey) { throw new Error( "Account typedData publicKey must match the requested serverPublicKey" ); } if (typedData.message.serverUrl !== request.serverUrl) { throw new Error( "Account typedData serverUrl must match the requested serverUrl" ); } if (typedData.primaryType !== "ServerRegistration") { throw new Error("Account typedData primaryType must be ServerRegistration"); } if (JSON.stringify(typedData.types) !== JSON.stringify(SERVER_REGISTRATION_TYPES)) { throw new Error("Account typedData types must be ServerRegistration types"); } const expectedDomain = personalServerRegistrationDomain({ config: request.config, chainId: request.chainId, verifyingContract: request.verifyingContract }); if (!domainsEqual(typedData.domain, expectedDomain)) { throw new Error("Account typedData domain must match the requested domain"); } } function sameAddress(a, b) { return a.toLowerCase() === b.toLowerCase(); } function domainsEqual(a, b) { if (!a || !b) { return false; } return a.name === b.name && a.version === b.version && Number(a.chainId) === Number(b.chainId) && String(a.verifyingContract ?? "").toLowerCase() === String(b.verifyingContract ?? "").toLowerCase() && a.salt === b.salt; } async function signPersonalServerRegistrationWithAccount(config, request) { assertAddress(request.serverAddress, "serverAddress"); const fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis); const endpoint = new URL( config.endpointPath ?? DEFAULT_ACCOUNT_PS_REGISTRATION_PATH, `${trimTrailingSlash(config.accountOrigin)}/` ); const response = await fetchImpl(endpoint, { method: "POST", headers: { "content-type": "application/json" }, credentials: "include", body: JSON.stringify({ intent: ACCOUNT_PERSONAL_SERVER_REGISTRATION_INTENT, serverAddress: request.serverAddress, serverPublicKey: request.serverPublicKey, serverUrl: request.serverUrl, config: request.config, chainId: request.chainId, verifyingContract: request.verifyingContract }) }); const body = normalizeAccountResponse(await parseAccountResponse(response)); if (body.status === "signed") { if (!body.signature || !body.signerAddress) { throw new Error( "Account signed response must include signature and signerAddress" ); } return { status: "signed", result: buildSignedResult( { signature: body.signature, signerAddress: body.signerAddress, typedData: body.typedData }, request ) }; } if (body.status === "confirmation_required") { if (!body.typedData) { throw new Error( "Account confirmation_required response must include typedData" ); } assertTypedDataMatchesRequest(body.typedData, request, body.signerAddress); if (!config.fallbackSigner) { return { status: "confirmation_required", typedData: body.typedData, signerAddress: body.signerAddress }; } assertTypedDataMatchesRequest( body.typedData, request, config.fallbackSigner.address ); const signature = await config.fallbackSigner.signTypedData(body.typedData); return { status: "fallback_signed", accountStatus: "confirmation_required", result: { signature, signerAddress: config.fallbackSigner.address, typedData: body.typedData, intent: ACCOUNT_PERSONAL_SERVER_REGISTRATION_INTENT } }; } throw new Error( `Unsupported Account PS registration signing status: ${String(body.status)}` ); } export { ACCOUNT_PERSONAL_SERVER_REGISTRATION_INTENT, AccountPersonalServerRegistrationError, signPersonalServerRegistrationWithAccount }; //# sourceMappingURL=personal-server-registration.js.map