@citizenwallet/sdk
Version:
An sdk to easily work with citizen wallet.
281 lines • 16 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.revokeSession = exports.getTwoFAAddress = exports.isSessionExpired = exports.confirmSession = exports.verifyIncomingSessionRequest = exports.requestSession = exports.verifySessionConfirm = exports.verifySessionRequest = exports.generateSessionHash = exports.generateSessionRequestHash = exports.generateSessionSalt = void 0;
const ethers_1 = require("ethers");
const bundler_1 = require("../bundler");
const SessionManagerModule_json_1 = __importDefault(require("../abi/SessionManagerModule.json"));
const TwoFAFactory_json_1 = __importDefault(require("../abi/TwoFAFactory.json"));
const sessionManagerInterface = new ethers_1.Interface(SessionManagerModule_json_1.default.abi);
const twoFAFactoryInterface = new ethers_1.Interface(TwoFAFactory_json_1.default.abi);
/**
* Generates a unique salt for a session based on a source and type.
* The salt is created by hashing the concatenated source and type with a colon separator.
*
* @param {string} params.source - The source identifier for the session
* @param {string} params.type - The type identifier for the session
* @returns {string} A bytes32 hash of the source and type combination
*/
const generateSessionSalt = ({ source, type, }) => {
return (0, ethers_1.id)(`${source}:${type}`);
};
exports.generateSessionSalt = generateSessionSalt;
/**
* Generates a hash for a session request by encoding and hashing the session parameters.
* Uses ABI encoding to ensure compatibility with the Dart implementation.
*
* @param {CommunityConfig} params.community - Instance of CommunityConfig
* @param {string} params.sessionOwner - The address of the session owner (address of private key)
* @param {string} params.salt - The unique salt generated for this session
* @param {number} params.expiry - UTC timestamp in seconds when the session expires
* @returns {string} A bytes32 hash of the encoded session request parameters
*/
const generateSessionRequestHash = ({ community, sessionOwner, salt, expiry, }) => {
const sessionProvider = community.primarySessionConfig.provider_address;
// Use ABI encoding to match the Dart implementation
const abiCoder = new ethers_1.AbiCoder();
const packedData = abiCoder.encode(['address', 'address', 'bytes32', 'uint48'], [sessionProvider, sessionOwner, salt, BigInt(expiry)]);
const result = (0, ethers_1.keccak256)(packedData);
return result;
};
exports.generateSessionRequestHash = generateSessionRequestHash;
/**
* Generates the final session hash by combining the session request hash with a challenge number.
* Uses ABI encoding to ensure compatibility with the Dart implementation.
*
* @param {string} params.sessionRequestHash - The hash generated from generateSessionRequestHash
* @param {number} params.challenge - The challenge number to combine with the session request hash
* @returns {string} A bytes32 hash of the encoded session parameters and challenge
*/
const generateSessionHash = ({ sessionRequestHash, challenge, }) => {
// Use ABI encoding to match the Dart implementation
const abiCoder = new ethers_1.AbiCoder();
const packedData = abiCoder.encode(['bytes32', 'uint256'], [sessionRequestHash, BigInt(challenge)]);
return (0, ethers_1.keccak256)(packedData);
};
exports.generateSessionHash = generateSessionHash;
/**
* Verifies a session request by validating the signature against the session owner's address.
* This function combines the session salt generation and request hash generation to verify
* that the signature was created by the session owner.
*
* @param {CommunityConfig} params.community - Instance of CommunityConfig
* @param {string} params.sessionOwner - The address of the session owner to verify against
* @param {string} params.source - The source identifier used in generating the session salt
* @param {string} params.type - The type identifier used in generating the session salt
* @param {number} params.expiry - UTC timestamp in seconds when the session expires
* @param {string} params.signature - The signature to verify, created by the session owner
* @returns {boolean} True if the signature is valid and matches the session owner's address
*/
const verifySessionRequest = ({ community, sessionOwner, source, type, expiry, signature, }) => {
const salt = (0, exports.generateSessionSalt)({ source, type });
const sessionRequestHash = (0, exports.generateSessionRequestHash)({
community,
sessionOwner,
salt,
expiry,
});
const recoveredAddress = (0, ethers_1.verifyMessage)((0, ethers_1.getBytes)(sessionRequestHash), signature);
return recoveredAddress === sessionOwner;
};
exports.verifySessionRequest = verifySessionRequest;
/**
* Verifies a session confirmation by validating the signed session hash against the session owner's address.
* This function checks if the signature of the session hash was created by the session owner.
*
* @param {string} params.sessionOwner - The address of the session owner to verify against
* @param {string} params.sessionHash - The session hash generated from generateSessionHash
* @param {string} params.signedSessionHash - The signature of the session hash to verify
* @returns {boolean} True if the signature is valid and matches the session owner's address
*/
const verifySessionConfirm = ({ sessionOwner, sessionHash, signedSessionHash, }) => {
const recoveredAddress = (0, ethers_1.verifyMessage)((0, ethers_1.getBytes)(sessionHash), signedSessionHash);
return recoveredAddress === sessionOwner;
};
exports.verifySessionConfirm = verifySessionConfirm;
/**
* Submits a session request to the session manager contract through a bundler service.
* This function encodes the session parameters and sends a transaction to create a new session.
* The challenge expiry is automatically set to 120 seconds from the current time.
*
* @param {CommunityConfig} params.community - An instance of CommunityConfig
* @param {Wallet} params.signer - The wallet instance used to sign the transaction
* @param {string} params.sessionSalt - The unique salt generated for this session
* @param {string} params.sessionRequestHash - The hash of the session request parameters
* @param {string} params.signedSessionRequestHash - The signature of the session request hash
* @param {string} params.signedSessionHash - The signature of the session hash
* @param {number} params.sessionExpiry - UTC timestamp in seconds when the session expires
* @returns {Promise<string>} The transaction hash of the session request
*/
const requestSession = async ({ community, signer, sessionSalt, sessionRequestHash, signedSessionRequestHash, signedSessionHash, sessionExpiry, }) => {
const sessionModuleAddress = community.primarySessionConfig.module_address;
const sessionProvider = community.primarySessionConfig.provider_address;
const bundler = new bundler_1.BundlerService(community);
const challengeExpiry = Math.floor(Date.now() / 1000) + 120;
const data = (0, ethers_1.getBytes)(sessionManagerInterface.encodeFunctionData('request', [
sessionSalt,
sessionRequestHash,
signedSessionRequestHash,
signedSessionHash,
sessionExpiry,
challengeExpiry,
]));
const tx = await bundler.call(signer, sessionModuleAddress, sessionProvider, data);
return tx;
};
exports.requestSession = requestSession;
/**
* Verifies an incoming session request by checking its validity against the session manager contract.
* Performs multiple validations:
* 1. Checks if the session request exists in the contract
* 2. Validates that the session has not expired
* 3. Validates that the challenge period has not expired
* 4. Verifies the signature matches the stored signature
*
* @param {CommunityConfig} params.community - Instance of CommunityConfig
* @param {Wallet} params.signer - The wallet instance used to sign and verify the session
* @param {string} params.sessionRequestHash - The hash of the session request to verify
* @param {string} params.sessionHash - The session hash to sign and compare
* @returns {Promise<boolean>} True if the session request is valid and signatures match, false otherwise
* @throws {Error} If session request is not found, expired, or challenge period has ended
*/
const verifyIncomingSessionRequest = async ({ community, signer, sessionRequestHash, sessionHash, options, }) => {
const { accountFactoryAddress } = options ?? {};
try {
// Get the session manager contract address
const sessionModuleAddress = community.primarySessionConfig.module_address;
const sessionProvider = community.primarySessionConfig.provider_address;
const rpcProvider = new ethers_1.JsonRpcProvider(community.getRPCUrl(accountFactoryAddress));
const contract = new ethers_1.Contract(sessionModuleAddress, sessionManagerInterface, rpcProvider);
const result = await contract.sessionRequests(sessionProvider, sessionRequestHash);
if (result.length < 5) {
throw new Error('Session request not found');
}
// check the expiry
const expiry = Number(result[0]);
const now = Math.floor(Date.now() / 1000);
if (expiry < now) {
throw new Error('Session request expired');
}
// check the challenge expiry
const challengeExpiry = Number(result[1]);
if (challengeExpiry < now) {
throw new Error('Challenge expired');
}
// Extract the stored signedSessionHash from the result
const storedSignedSessionHash = result[2];
// Sign the provided sessionHash with the signer
const calculatedSignedSessionHash = await signer.signMessage((0, ethers_1.getBytes)(sessionHash));
// Compare the stored signedSessionHash with the provided one
return storedSignedSessionHash === calculatedSignedSessionHash;
}
catch (error) {
console.error('Error verifying incoming session request:', error);
return false;
}
};
exports.verifyIncomingSessionRequest = verifyIncomingSessionRequest;
/**
* Confirms a session request by submitting the confirmation transaction through a bundler service.
* This function encodes the session confirmation parameters and sends them to the session manager contract.
*
* @param {CommunityConfig} params.community - Instance of CommunityConfig containing contract addresses
* @param {Wallet} params.signer - The wallet instance used to sign the transaction
* @param {string} params.sessionRequestHash - The hash of the original session request
* @param {string} params.sessionHash - The final session hash to confirm
* @param {string} params.signedSessionHash - The signature of the session hash
* @returns {Promise<string>} The transaction hash of the confirmation transaction
*/
const confirmSession = async ({ community, signer, sessionRequestHash, sessionHash, signedSessionHash, }) => {
const sessionModuleAddress = community.primarySessionConfig.module_address;
const sessionProvider = community.primarySessionConfig.provider_address;
const bundler = new bundler_1.BundlerService(community);
const data = (0, ethers_1.getBytes)(sessionManagerInterface.encodeFunctionData('confirm', [
sessionRequestHash,
sessionHash,
signedSessionHash,
]));
const tx = await bundler.call(signer, sessionModuleAddress, sessionProvider, data);
return tx;
};
exports.confirmSession = confirmSession;
/**
* Checks if a session between an account and owner has expired by querying the session manager contract.
* This function makes a direct call to the contract's isExpired method.
*
* @param {CommunityConfig} params.community - Instance of CommunityConfig containing contract addresses and RPC URL
* @param {string} params.account - The account address to check the session for
* @param {string} params.owner - The owner address to check the session against
* @returns {Promise<boolean>} True if the session has expired or doesn't exist, false if the session is still valid
* @throws {Error} Logs error and returns false if the contract call fails
*/
const isSessionExpired = async ({ community, account, owner, options, }) => {
const { accountFactoryAddress } = options ?? {};
// Get the session manager contract address
const sessionModuleAddress = community.primarySessionConfig.module_address;
const rpcProvider = new ethers_1.JsonRpcProvider(community.getRPCUrl(accountFactoryAddress));
const contract = new ethers_1.Contract(sessionModuleAddress, sessionManagerInterface, rpcProvider);
try {
const result = await contract.isExpired(account, owner);
return result;
}
catch (error) {
console.error('Error checking if session is expired:', error);
return true;
}
};
exports.isSessionExpired = isSessionExpired;
/**
* Retrieves the deterministic address for a 2FA account based on the community configuration, source, and type.
* This function queries the 2FA factory contract to compute the address that would be created with these parameters.
*
* @param {CommunityConfig} params.community - Instance of CommunityConfig containing factory and provider addresses
* @param {string} params.source - The source identifier used to generate the salt
* @param {string} params.type - The type identifier used to generate the salt
* @returns {Promise<string | null>} The computed 2FA address if successful, null if the computation fails
*/
const getTwoFAAddress = async ({ community, source, type, options, }) => {
const { accountFactoryAddress, sessionFactoryAddress, sessionProviderAddress, rpcUrl, } = options ?? {};
const factoryAddress = sessionFactoryAddress ?? community.primarySessionConfig.factory_address;
const providerAddress = sessionProviderAddress ?? community.primarySessionConfig.provider_address;
const salt = (0, exports.generateSessionSalt)({ source, type });
const saltBigInt = BigInt(salt);
const resolvedRpcUrl = rpcUrl ?? community.getRPCUrl(accountFactoryAddress);
const rpcProvider = new ethers_1.JsonRpcProvider(resolvedRpcUrl);
const contract = new ethers_1.Contract(factoryAddress, twoFAFactoryInterface, rpcProvider);
try {
const result = await contract.getFunction('getAddress')(providerAddress, saltBigInt);
return result;
}
catch (error) {
console.error('Error getting twoFA address:', error);
return null;
}
};
exports.getTwoFAAddress = getTwoFAAddress;
/**
* Revokes an active session between a signer and an account through the session manager contract.
* This function sends a revocation transaction using the bundler service.
*
* @param {CommunityConfig} params.community - Instance of CommunityConfig containing contract addresses
* @param {Wallet} params.signer - The wallet instance used to sign and revoke the session
* @param {string} params.account - The account address from which to revoke the session
* @returns {Promise<string|null>} The transaction hash if successful, null if the revocation fails
*/
const revokeSession = async ({ community, signer, account, }) => {
const sessionModuleAddress = community.primarySessionConfig.module_address;
const bundler = new bundler_1.BundlerService(community);
const data = (0, ethers_1.getBytes)(sessionManagerInterface.encodeFunctionData('revoke', [signer.address]));
try {
const tx = await bundler.call(signer, sessionModuleAddress, account, data);
return tx;
}
catch (error) {
console.error('Error revoking session:', error);
return null;
}
};
exports.revokeSession = revokeSession;
//# sourceMappingURL=index.js.map