@opendatalabs/vana-sdk
Version:
A TypeScript library for interacting with Vana Network smart contracts.
240 lines • 7.84 kB
JavaScript
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