@unilogin/sdk
Version:
SDK is a JS library, that communicates with relayer. SDK allows managing contract, by creating basic contract-calling messages.
150 lines (125 loc) • 5.99 kB
text/typescript
import {ApplicationWallet, DEFAULT_GAS_LIMIT, ExecutionOptions, generateBackupCode, Message, SdkExecutionOptions, walletFromBrain, ETHER_NATIVE_TOKEN, OperationType, ensure, SignedMessage, Device, Procedure, GasMode} from '@unilogin/commons';
import {utils} from 'ethers';
import {BigNumber} from 'ethers/utils';
import {OnBalanceChange} from '../../core/observers/BalanceObserver';
import {AbstractWallet} from './AbstractWallet';
import {BackupCodesWithExecution} from './BackupCodesWithExecution';
import {InvalidGasLimit} from '../../core/utils/errors';
import {ensureSufficientGas} from '../../core/utils/validation';
import UniLoginSdk from '../sdk';
import {Execution} from '../../core/services/ExecutionFactory';
import {propertyFromSubscription} from '../../core/utils/propertyFromSubscription';
export class DeployedWallet extends AbstractWallet {
constructor(
contractAddress: string,
name: string,
privateKey: string,
public readonly sdk: UniLoginSdk,
) {
super(contractAddress, name, privateKey);
}
get asApplicationWallet(): ApplicationWallet {
return {
contractAddress: this.contractAddress,
name: this.name,
privateKey: this.privateKey,
};
}
addKey(publicKey: string, executionOptions: ExecutionOptions): Promise<Execution> {
return this.selfExecute('addKey', [publicKey], {gasLimit: DEFAULT_GAS_LIMIT, ...executionOptions});
}
addKeys(publicKeys: string[], executionOptions: ExecutionOptions): Promise<Execution> {
return this.selfExecute('addKeys', [publicKeys], {gasLimit: DEFAULT_GAS_LIMIT, ...executionOptions});
}
removeKey(key: string, executionOptions: ExecutionOptions): Promise<Execution> {
return this.selfExecute('removeKey', [key], {gasLimit: DEFAULT_GAS_LIMIT, ...executionOptions});
}
removeCurrentKey(executionOptions: ExecutionOptions): Promise<Execution> {
const ownKey = utils.computeAddress(this.privateKey);
return this.removeKey(ownKey, executionOptions);
}
async denyRequests() {
const authorisationRequest = {contractAddress: this.contractAddress};
await this.sdk.walletContractService.signRelayerRequest(this.privateKey, authorisationRequest);
await this.sdk.relayerApi.denyConnection(authorisationRequest);
}
setRequiredSignatures(requiredSignatures: number, executionOptions: ExecutionOptions): Promise<Execution> {
return this.selfExecute('setRequiredSignatures', [requiredSignatures], {gasLimit: DEFAULT_GAS_LIMIT, ...executionOptions});
}
async execute(message: Partial<Message>): Promise<Execution> {
const relayerConfig = this.sdk.getRelayerConfig();
const nonce = message.nonce || await this.getNonce();
const partialMessage = {
gasToken: ETHER_NATIVE_TOKEN.address,
...message,
nonce,
refundReceiver: relayerConfig.relayerAddress,
operationType: OperationType.call,
from: this.contractAddress,
gasPrice: this.sdk.isRefundPaid() ? '0' : message.gasPrice,
};
ensure(partialMessage.gasLimit! <= relayerConfig.maxGasLimit, InvalidGasLimit, `${partialMessage.gasLimit} provided, when relayer's max gas limit is ${relayerConfig.maxGasLimit}`);
const signedMessage: SignedMessage = await this.sdk.messageConverter.messageToSignedMessage(partialMessage, this.privateKey);
await this.sdk.sufficientBalanceValidator.validate(signedMessage);
ensureSufficientGas(signedMessage);
return this.sdk.executionFactory.createExecution(signedMessage);
}
keyExist(key: string): Promise<boolean> {
return this.sdk.walletContractService.keyExist(this.contractAddress, key);
}
getNonce(): Promise<number> {
return this.sdk.walletContractService.lastNonce(this.contractAddress);
}
getConnectedDevices(): Promise<any> {
return this.sdk.getConnectedDevices(this.contractAddress, this.privateKey);
}
async getKeys() {
return (await this.getConnectedDevices())
.map((device: Device) => device.publicKey);
}
getRequiredSignatures(): Promise<BigNumber> {
return this.sdk.walletContractService.requiredSignatures(this.contractAddress);
}
getGasModes(): Promise<GasMode[]> {
return this.sdk.getGasModes();
}
async selfExecute(method: string, args: any[], executionOptions: SdkExecutionOptions): Promise<Execution> {
const data = await this.sdk.walletContractService.encodeFunction(this.contractAddress, method, args);
const message: Partial<Message> = {
...executionOptions,
to: this.contractAddress,
from: this.contractAddress,
data,
};
return this.execute(message);
}
async generateBackupCodes(executionOptions: ExecutionOptions): Promise<BackupCodesWithExecution> {
const codes: string[] = [generateBackupCode()];
const addresses: string[] = [];
for (const code of codes) {
const {address} = await walletFromBrain(this.name, code);
addresses.push(address);
}
const execution = await this.addKey(addresses[0], {gasLimit: DEFAULT_GAS_LIMIT, ...executionOptions});
return new BackupCodesWithExecution(execution, codes);
}
signMessage(bytes: Uint8Array) {
return this.sdk.walletContractService.signMessage(this.contractAddress, this.privateKey, bytes);
}
subscribeAuthorisations(callback: Function) {
return this.sdk.subscribeAuthorisations(this.contractAddress, this.privateKey, callback);
}
readonly authorizations = propertyFromSubscription(this.subscribeAuthorisations.bind(this), []);
subscribeToBalances(callback: OnBalanceChange) {
return this.sdk.subscribeToBalances(this.contractAddress, callback);
}
async subscribeDisconnected(onDisconnected: Procedure) {
const eventName = await this.sdk.walletContractService.getEventNameFor(this.contractAddress, 'KeyRemoved');
const unsubscribe = this.sdk.subscribe(
eventName,
{contractAddress: this.contractAddress, key: this.publicKey},
() => onDisconnected(),
);
return () => unsubscribe();
}
}