@civic/sol-did-client
Version:
A powerful DID-method on Solana
450 lines • 21.2 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DidSolService = void 0;
const anchor_1 = require("@coral-xyz/anchor");
const utils_1 = require("./lib/utils");
const web3_js_1 = require("@solana/web3.js");
const const_1 = require("./lib/const");
const DidSolDocument_1 = require("./DidSolDocument");
const connection_1 = require("./lib/connection");
const DidSolIdentifier_1 = require("./DidSolIdentifier");
const DidAccountSizeHelper_1 = require("./DidAccountSizeHelper");
const wrappers_1 = require("./lib/wrappers");
const DidSolTransactionBuilder_1 = require("./utils/DidSolTransactionBuilder");
/**
* The DidSolService class is a wrapper around the Solana DID program.
* It provides methods for creating, reading, updating, and deleting DID documents.
* Note, the provider or connection in the DidSolService MUST not be used for tx submissions.
* Please use DidSolServiceBuilder instead
*/
class DidSolService extends DidSolTransactionBuilder_1.DidSolTransactionBuilder {
static build(identifier, options = {}) {
// if identifier is a string, parse it
if ((0, utils_1.isStringDID)(identifier)) {
identifier = DidSolIdentifier_1.DidSolIdentifier.parse(identifier);
}
const wallet = options.wallet || new NonSigningWallet();
const confirmOptions = options.confirmOptions || anchor_1.AnchorProvider.defaultOptions();
const connection = options.connection ||
(0, connection_1.getConnectionByCluster)(identifier.clusterType, confirmOptions.preflightCommitment);
// Note, DidSolService never signs, so provider does not need a valid Wallet or confirmOptions.
const provider = new anchor_1.AnchorProvider(connection, wallet, confirmOptions);
const program = (0, utils_1.fetchProgram)(provider);
return new DidSolService(program, identifier.authority, identifier.clusterType, provider.wallet, provider.opts);
}
static buildFromAnchor(program, identifier, provider, wallet) {
// if identifier is a string, parse it
if ((0, utils_1.isStringDID)(identifier)) {
identifier = DidSolIdentifier_1.DidSolIdentifier.parse(identifier);
}
return new DidSolService(program, identifier.authority, identifier.clusterType, wallet ? wallet : provider.wallet, provider.opts);
}
constructor(_program, _didAuthority, _cluster = 'mainnet-beta', wallet = new NonSigningWallet(), confirmOptions = anchor_1.AnchorProvider.defaultOptions()) {
super(wallet, _program.provider.connection, confirmOptions, _program.idl);
this._program = _program;
this._didAuthority = _didAuthority;
this._cluster = _cluster;
this._didDataAccount = (0, utils_1.findProgramAddress)(_didAuthority)[0];
this._legacyDidDataAccount = (0, utils_1.findLegacyProgramAddress)(_didAuthority)[0];
this._identifier = DidSolIdentifier_1.DidSolIdentifier.create(_didAuthority, _cluster);
}
get connection() {
return this._program.provider.connection;
}
get didDataAccount() {
return this._didDataAccount;
}
get legacyDidDataAccount() {
return this._legacyDidDataAccount;
}
getDidAccount() {
return __awaiter(this, void 0, void 0, function* () {
// TODO: this should be reverted as soon as https://github.com/coral-xyz/anchor/issues/2172 is fixed
const accountInfo = yield this._program.account.didAccount.getAccountInfo(this._didDataAccount);
if (accountInfo === null || accountInfo.data.length === 0) {
return null;
}
const dataAccount = this._program.account.didAccount.coder.accounts.decode(
// @ts-ignore: TODO: find a better way
this._program.account.didAccount._idlAccount.name,
// 'DidAccount', // TODO: from "this._program.account.didAccount._idlAccount.name" - How to get this officially?
accountInfo.data);
return wrappers_1.DidSolDataAccount.from(dataAccount, this._cluster);
});
}
getDidAccountWithSize(commitment) {
return __awaiter(this, void 0, void 0, function* () {
const accountInfo = yield this._program.account.didAccount.getAccountInfo(this._didDataAccount, commitment);
if (accountInfo === null || accountInfo.data.length === 0) {
return [null, 0];
}
const size = accountInfo.data.length;
const dataAccount = this._program.account.didAccount.coder.accounts.decode(
// @ts-ignore: TODO: find a better way
this._program.account.didAccount._idlAccount.name,
// 'DidAccount', // TODO: from "this._program.account.didAccount._idlAccount.name" - How to get this officially?
accountInfo.data);
if (!dataAccount) {
return [null, size];
}
return [wrappers_1.DidSolDataAccount.from(dataAccount, this._cluster), size];
});
}
get did() {
return this._identifier.toString();
}
getIdl() {
return this._program.idl;
}
getNonce() {
return __awaiter(this, void 0, void 0, function* () {
const account = yield this._program.account.didAccount.fetchNullable(this._didDataAccount);
return account ? account.nonce : new anchor_1.BN(0);
});
}
/**
* Initializes the did:sol account.
* Does **not** support ethSignInstruction
* @param size The initial size of the account
* @param payer The account to pay the rent-exempt fee with.
*/
initialize(size = const_1.INITIAL_MIN_ACCOUNT_SIZE, payer = this._wallet.publicKey) {
if (size < const_1.INITIAL_MIN_ACCOUNT_SIZE) {
throw new Error(`Account size must be at least ${const_1.INITIAL_MIN_ACCOUNT_SIZE}`);
}
const instructionPromise = this._program.methods
.initialize(size)
.accounts({
authority: this._didAuthority,
payer,
})
.instruction();
this.setInitInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.NotSupported,
didAccountChangeCallback: () => {
throw new Error('Not Implemented');
},
});
return this;
}
/**
* Resize the did:sol account.
* Supports ethSignInstruction
* @param size The new size of the account
* @param payer The account to pay the rent-exempt fee with.
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
resize(size, payer = this._wallet.publicKey, authority = this._wallet.publicKey) {
const instructionPromise = this._program.methods
.resize(size, null)
.accounts({
payer,
authority,
})
.instruction();
this.setResizeInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: () => {
throw new Error('Not Implemented');
},
});
return this;
}
/**
* Close the did:sol account.
* Supports ethSignInstruction
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
* @param destination The destination account to move the lamports to.
*/
close(destination, authority = this._wallet.publicKey) {
const instructionPromise = this._program.methods
.close(null)
.accounts({
authority,
destination,
})
.instruction();
this.setCloseInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: () => {
throw new Error('Not Implemented');
},
});
return this;
}
/**
* Add a VerificationMethod to the did:sol account.
* Supports ethSignInstruction
* @param method The new VerificationMethod to add
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
addVerificationMethod(method, authority = this._wallet.publicKey) {
const vm = {
fragment: method.fragment,
keyData: method.keyData,
methodType: method.methodType,
flags: wrappers_1.VerificationMethodFlags.ofArray(method.flags).raw,
};
const instructionPromise = this._program.methods
.addVerificationMethod(vm, null)
.accounts({
authority,
})
.instruction();
this.addGeneralInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: (account, size) => {
account.verificationMethods.push(vm);
return [
account,
size + DidAccountSizeHelper_1.DidAccountSizeHelper.getVerificationMethodSize(method),
];
},
});
return this;
}
/**
* Remove a VerificationMethod from the did:sol account.
* @param fragment The fragment of the VerificationMethod to remove
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
removeVerificationMethod(fragment, authority = this._wallet.publicKey) {
const instructionPromise = this._program.methods
.removeVerificationMethod(fragment, null)
.accounts({
authority,
})
.instruction();
this.addGeneralInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: (account, size) => {
const index = account.verificationMethods.findIndex((x) => x.fragment === fragment);
let newSize = size;
if (index !== -1) {
newSize -= DidAccountSizeHelper_1.DidAccountSizeHelper.getVerificationMethodSize(account.verificationMethods[index]);
account.verificationMethods.splice(index, 1);
}
return [account, newSize];
},
});
return this;
}
/**
* Add a Service to the did:sol account.
* Supports ethSignInstruction
* @param service The service to add
* @param allowOverwrite If true, will overwrite an existing service with the same id
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
addService(service, allowOverwrite = false, authority = this._wallet.publicKey) {
const instructionPromise = this._program.methods
.addService(service, allowOverwrite, null)
.accounts({
authority,
})
.instruction();
this.addGeneralInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: (account, size) => {
const index = account.services.findIndex((x) => x.fragment === service.fragment);
let newSize = size;
if (allowOverwrite && index !== -1) {
newSize -= DidAccountSizeHelper_1.DidAccountSizeHelper.getServiceSize(account.services[index]);
account.services.splice(index, 1);
}
account.services.push(service);
newSize += DidAccountSizeHelper_1.DidAccountSizeHelper.getServiceSize(service);
return [account, newSize];
},
});
return this;
}
/**
* Removes a Service to the did:sol account.
* Supports ethSignInstruction
* @param fragment The id of the service to remove
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
removeService(fragment, authority = this._wallet.publicKey) {
const instructionPromise = this._program.methods
.removeService(fragment, null)
.accounts({
authority,
})
.instruction();
this.addGeneralInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: (account, size) => {
const index = account.services.findIndex((x) => x.fragment === fragment);
let newSize = size;
if (index !== -1) {
newSize -= DidAccountSizeHelper_1.DidAccountSizeHelper.getServiceSize(account.services[index]);
account.services.splice(index, 1);
}
return [account, newSize];
},
});
return this;
}
/**
* Update the Flags of a VerificationMethod.
* @param fragment The fragment of the VerificationMethod to update
* @param flags The flags to set. If flags contain BitwiseVerificationMethodFlag.OwnershipProof, the transaction must be signed by the exact same VM.
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
setVerificationMethodFlags(fragment, flags, authority = this._wallet.publicKey) {
const instructionPromise = this._program.methods
.setVmFlags({
fragment,
flags: wrappers_1.VerificationMethodFlags.ofArray(flags).raw,
}, null)
.accounts({
authority,
})
.instruction();
this.addGeneralInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: (account, size) => {
// no size change (just flags)
return [account, size];
},
});
return this;
}
/**
* Update the controllers of a Service.
* @param controllerDIDs A list of DIDs to be set as controllers
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
setControllers(controllerDIDs, authority = this._wallet.publicKey) {
const updateControllers = (0, utils_1.validateAndSplitControllers)(controllerDIDs);
const instructionPromise = this._program.methods
.setControllers(updateControllers, null)
.accounts({
authority,
})
.instruction();
this.addGeneralInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: (account, size) => {
const add = updateControllers.nativeControllers.length * 32 +
updateControllers.otherControllers.reduce((acc, c) => acc + 4 + (0, utils_1.getBinarySize)(c), 0);
const remove = account.nativeControllers.length * 32 +
account.otherControllers.reduce((acc, c) => acc + 4 + (0, utils_1.getBinarySize)(c), 0);
account.nativeControllers = updateControllers.nativeControllers;
account.otherControllers = updateControllers.otherControllers;
return [account, size + add - remove];
},
});
return this;
}
/**
* Updates a DID with contents of document.
* @param document A did:sol Document of the DID to update
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
updateFromDoc(document, authority = this._wallet.publicKey) {
if (document.id !== this.did) {
throw new Error(`DID ${document.id} in document does not match DID of Service ${this.did} `);
}
const didSolDocument = DidSolDocument_1.DidSolDocument.fromDoc(document);
const updateArgs = didSolDocument.getDocUpdateArgs();
return this.update(updateArgs, authority);
}
/**
* Updates several properties of a service.
* @param updateArgs A subset of DID properties to update
* @param authority The authority to use. Can be "wrong" if instruction is later signed with ethSigner
*/
update(updateArgs, authority = this._wallet.publicKey) {
const updateControllers = (0, utils_1.validateAndSplitControllers)(updateArgs.controllerDIDs);
const verificationMethods = updateArgs.verificationMethods.map((method) => ({
fragment: method.fragment,
keyData: method.keyData,
methodType: method.methodType,
flags: wrappers_1.VerificationMethodFlags.ofArray(method.flags).raw,
}));
const instructionPromise = this._program.methods
.update({
verificationMethods,
services: updateArgs.services,
nativeControllers: updateControllers.nativeControllers,
otherControllers: updateControllers.otherControllers,
}, null)
.accounts({
authority,
})
.instruction();
this.addGeneralInstruction({
instructionPromise,
ethSignStatus: DidSolTransactionBuilder_1.DidSolEthSignStatusType.Unsigned,
didAccountChangeCallback: (account, size) => {
let add = 0;
add += updateControllers.nativeControllers.length * 32;
add += updateControllers.otherControllers.reduce((acc, c) => acc + 4 + (0, utils_1.getBinarySize)(c), 0);
// 'default' does not take up any "extra" space
const updatedVerificationMethods = verificationMethods.filter((value) => value.fragment !== const_1.DEFAULT_KEY_ID);
add += updatedVerificationMethods.reduce((acc, method) => acc + DidAccountSizeHelper_1.DidAccountSizeHelper.getVerificationMethodSize(method), 0);
add += updateArgs.services.reduce((acc, service) => acc + DidAccountSizeHelper_1.DidAccountSizeHelper.getServiceSize(service), 0);
let remove = 0;
remove += account.nativeControllers.length * 32;
remove += account.otherControllers.reduce((acc, c) => acc + 4 + (0, utils_1.getBinarySize)(c), 0);
// 'default' does not take up any space
remove += account.verificationMethods.reduce((acc, method) => acc + DidAccountSizeHelper_1.DidAccountSizeHelper.getVerificationMethodSize(method), 0);
remove += account.services.reduce((acc, service) => acc + DidAccountSizeHelper_1.DidAccountSizeHelper.getServiceSize(service), 0);
account.verificationMethods = updatedVerificationMethods;
account.services = updateArgs.services;
account.nativeControllers = updateControllers.nativeControllers;
account.otherControllers = updateControllers.otherControllers;
const newSize = size + add - remove;
return [account, newSize];
},
});
return this;
}
/**
* Resolves the DID Document for the did:sol account.
*/
resolve() {
return __awaiter(this, arguments, void 0, function* (checkLegacy = true) {
const didDataAccount = yield this.getDidAccount();
if (didDataAccount) {
return DidSolDocument_1.DidSolDocument.from(didDataAccount);
}
// generative case
return DidSolDocument_1.DidSolDocument.sparse(DidSolIdentifier_1.DidSolIdentifier.create(this._didAuthority, this._cluster));
});
}
}
exports.DidSolService = DidSolService;
class NonSigningWallet {
constructor() {
this.publicKey = new web3_js_1.PublicKey('11111111111111111111111111111111');
this.payer = web3_js_1.Keypair.generate();
}
signAllTransactions(txs) {
return Promise.resolve(txs);
}
signTransaction(tx) {
return Promise.resolve(tx);
}
}
//# sourceMappingURL=DidSolService.js.map