
Product
Introducing Socket Firewall Enterprise: Flexible, Configurable Protection for Modern Package Ecosystems
Socket Firewall Enterprise is now available with flexible deployment, configurable policies, and expanded language support.
@identity.com/sol-did-client
Advanced tools
did:sol ClientA typescript client library for registering, manipulating, and resolving DIDs
using the did:sol method.
With Version ^3.0.0, the @identity.com/sol-did-client has been updated to use a new
authoritative program on the Solana blockchain (Address: didso1...). DIDs with a
persisted state with the legacy program idDa4... remains valid unless transferred
to the new program. The did:sol resolver will resolve DIDs in the following priority:
didso1...idDa4...The version <3.0.0 of the sol-did-client will remain available @identity.com/sol-did-client-legacy
, but technically a
resolution with the legacy program no longer returns an authoritative result (since it
ignores the state of the new program). Therefore, to perform a DID resolution a library update is
strongly encouraged. The legacy did sol client is also exported in the new library as LegacyClient.
Please see below how to migrate your DIDs to the new program.
The sol-did-client library provides the following features:
did:sol.Ed25519VerificationKey2018, EcdsaSecp256k1RecoveryMethod2020 and EcdsaSecp256k1VerificationKey2019. This means DID state changes can be performed by only providing a valid secp256k1 signature to the program (it still requires a permissionless proxy).did:sol state to the new authoritative program.enum for types and bit-flags for certain properties).OWNERSHIP_PROOF to indicate that a Verification Method Key signature was verified on-chain.DID_DOC_HIDDEN flag that enables hiding a Verification Method from the DID resolution.In the command line of the project folder, type the following and then press Enter:
yarn add @identity.com/sol-did-client #
or
npm install @identity.com/sol-did-client
Use the following TypeScript code to complete the listed commands.
Create a service for a SOL-DID by using the following code snippet:
const authority = Keypair.generate();
const cluster: ExtendedCluster = 'localnet';
const didSolIdentifier = DidSolIdentifier.create(authority.publicKey, cluster);
// create service for a did:sol:${authority.publicKey}
const service = await DidSolService.build(didSolIdentifier);
Resolve a SOL-DID with the following code snippet:
const didDoc = await service.resolve();
console.log(JSON.stringify(didDoc, null, 2));
did:sol DIDs are resolved in the following way:
Generative DIDs are DIDs that have no persisted DID data account. (e.g. every valid Solana Account/Wallet is in this state).
This will return a generative DID document where only the public key of the Account is a valid Verification Method.Persisted DIDs are DIDs that have a persisted DID data account. Here the DID document represents the state that is found
on-chain.did:sol ManipulationThe following are instructions that can be executed against a DID.
When manipulating a DID, three elements are generally required:
authority, a (native) Verification Method with a Capability Invocation flag, that is allowed to manipulate the DID.fee payer, a Solana account that covers the cost of the transaction execution.(rent) payer, a Solana account that covers an (eventual) initialization or resize of the DID data account.Often, all of these entities are required for successful DID manipulation. However, when increasing the DID account size,
then only a rent payer is needed. In most cases, all three elements are represented by the same account, but this is
not a requirement. It is possible to implement a permissionless proxy that satisfies the fee payer and the rent payer to submit an authority-signed instruction/transaction to the chain. For example, a dApp might opt to pay fees to encourage user onboarding.
Generally, a manipulative DID operation has the following form:
service.OPERATION(...params) // returns the instance of the service to chain another operation
where each operation return as a builder that enables configuring certain aspects of how the operation is translated or executed.
For example:
await service
.addVerificationMethod({
fragment: 'eth-address',
keyData: Buffer.from(ethAddress),
methodType: VerificationMethodType.EcdsaSecp256k1RecoveryMethod2020,
flags: [VerificationMethodFlags.CapabilityInvocation, VerificationMethodFlags.Authentication],
},
nonAuthority.publicKey
)
.withAutomaticAlloc(nonAuthority.publicKey)
.withPartialSigners(nonAuthority)
.withSolWallet(feePayerWallet)
.withEthSigner(authorityEthKey)
.rpc();
adds a new Verification Method to the DID, and sets the authority (1) as nonAuthority (which in this example is actually
NOT a valid authority). It also uses nonAuthority as a rent payer (3) via withAutomaticAlloc and uses
solWallet as a Wallet interface signer of the transaction that covers the transaction fee (2). Secondly, the
instruction is signed by authorityEthKey itself, which IS an actual authority (1) on the DID and permits the
update. Finally rpc() creates the instruction(s) and transaction and sends it to the chain. It is a terminal method
of the builder and needs to be awaited (returning a promise of the signature string).
Here's a breakdown of all exposed Builder functions:
withAutomaticAlloc(payer: PublicKey): DidSolServiceBuilder: Automatically enables a perfect resize of the DID data account.
If required, this will generate an additional initialize or resize instruction that is executed before the actual
service instruction in order to bring the account to the required size.withEthSigner(ethSigner: EthSigner): DidSolServiceBuilder: Allows to set an EthSigner e.g. as provided by ethers.
This signs all (supported) instructions with the provided signMessage interface, which needs to adhere to EIP-191.
If the DID contains a matching Verification Method of type EcdsaSecp256k1RecoveryMethod2020 or EcdsaSecp256k1VerificationKey2019 (with a Capability Invocation flag),
no Solana Authority (1) is required.withConnection(connection: Connection): DidSolServiceBuilder: Overrides the Solana Connection used for rpc().withConfirmOptions(confirmOptions: ConfirmOptions): DidSolServiceBuilder Overrides the Solana ConfirmationOptions used for rpc().withSolWallet(solWallet: Wallet): DidSolServiceBuilder Allows overriding the Solana Wallet interface that is used to sign the transaction within rpc().withPartialSigners(...signers: Signer[]): DidSolServiceBuilder: Sets partialSigners to sign the transaction in rpc().async rpc(opts?: ConfirmOptions): Promis e<string>: Terminal method that creates the instruction(s), builds and signs the transaction, and sends it to the chain. Furthermore, it translates the chain-specific error code into a human-readable error message.async transaction(): Promise<Transaction>: Terminal method that creates the instruction(s), and builds the transaction (Eth signing will be applied if applicable, but no Solana Transaction handling is performed).async instructions(): Promise<TransactionInstruction[]>: Terminal method that creates and returns the instruction(s) (Eth signing will be applied if applicable).DidSolService allows chaining multiple operations one after another. For example:
await service
.removeService('service-4', nonAuthoritySigner.publicKey) // remove
.addService({
fragment: 'service-5',
serviceType: 'service-type-5',
serviceEndpoint: 'http://localhost:3005',
}, true, nonAuthoritySigner.publicKey) // update
.addService({
fragment: 'service-6',
serviceType: 'service-type-6',
serviceEndpoint: 'http://localhost:3006',
}, false, nonAuthoritySigner.publicKey) // add
.withEthSigner(ethAuthority1)
.withSolWallet(nonAuthorityWallet)
.withAutomaticAlloc(nonAuthoritySigner.publicKey)
.rpc();
This operation chains one(1) removeService and two(2) addService operations that are signed
by and ethSigner and optionally resizes the account.
All DID operations can be performed with withAutomaticAlloc(payer: PublicKey), which automatically creates a DID data account of the required size. However, the API still supports manually initializing a DID account of any size. Allocating an account upfront that is large enough for future modifications might prevent the need to use payers for subsequent operations. However, allocating a larger account increases the rent fees for that account.
await service.initialize(1_000, payer.publicKey).rpc();
The initialize operation does not support withAutomaticAlloc OR withEthSigner. Using initialize without an
argument will set the default DID authority as payer and size it to the minimal initial size required.
Generally, all DID operations can be performed with withAutomaticAlloc(payer: PublicKey), which automatically creates or resizes a DID data account of the required size. However, the API also supports manually resizing a DID account of any size.
await service.resize(1_500, payer.publicKey).rpc();
The resize operation does not support withAutomaticAlloc. Using initialize without an argument will set the default DID authority as payer.
This operation adds a new Verification Method to the DID. The keyData can be a generically sized UInt8Array, but logically it must match the methodType specified.
await service.addVerificationMethod({
fragment: 'eth-address',
keyData: Buffer.from(ethAddress),
methodType: VerificationMethodType.EcdsaSecp256k1RecoveryMethod2020,
flags: [VerificationMethodFlags.CapabilityInvocation, VerificationMethodFlags.Authentication],
})
.withAutomaticAlloc(authority.publicKey)
.rpc();
This code removes a Verification Method with the given fragment from the DID. It is important to keep at least one valid Verification Method with a Capability Invocation flag to prevent a lockout.
await service
.removeVerificationMethod('eth-address')
.withAutomaticAlloc(authority.publicKey)
.rpc();
This sets/updates the flag on an existing VerificationMethod. Important if the flag contains VerificationMethodFlags.OwnershipProof
this transaction MUST use the same authority as the Verification Method. (e.g. proving that the owner can sign with
that specific VM). VerificationMethodFlags.OwnershipProof is supported for the following VerificationMethodTypes:
Ed25519VerificationKey2018EcdsaSecp256k1RecoveryMethod2020EcdsaSecp256k1VerificationKey2019In this example, the 'default" VM must match the authority.publicKey.
await service
.setVerificationMethodFlags('default',
[VerificationMethodFlags.CapabilityInvocation, VerificationMethodFlags.OwnershipProof],
authority.publicKey)
.withAutomaticAlloc(authority.publicKey)
.rpc();
This operation sets a new service on a DID. serviceType are strings, not enums, and can therefore be freely defined.
await service
.addService(
{
fragment: 'service-1',
serviceType: 'service-type-1',
serviceEndpoint: 'http://localhost:3000',
})
.rpc();
This operation removes a service with the given fragment name from the DID.
await service.removeService('service-1')
.withAutomaticAlloc(nonAuthority.publicKey)
.rpc();
This operation sets/updates the controllers of a DID. This overwrites any existing controllers.
await service
.setControllers([
`did:sol:localnet:${Keypair.generate().publicKey.toBase58()}`,
`did:ethr:${EthWallet.createRandom().address}`,
])
.withAutomaticAlloc(authority.publicKey)
.rpc();
Technically did:sol controllers are verified to be valid Solana Account Keys and stored accordingly, while all other
types of controller DID are persisted as strings.
This operation allows for bulk updates of all changeable properties of a DID.
Please note, that this is a more destructive operation and should be handled with care. Furthermore, by overwriting all Verification Methods it removes ANY existing VerificationMethodFlags.OwnershipProof,
which are not allowed to be specified within the bulk update.
await service
.update({
controllerDIDs: [
`did:sol:localnet:${Keypair.generate().publicKey.toBase58()}`,
`did:ethr:${EthWallet.createRandom().address}`,
],
services: [
{
fragment: 'service-1',
serviceType: 'service-type-1',
serviceEndpoint: 'http://localhost:3000',
},
{
fragment: 'service-2',
serviceType: 'service-type-2',
serviceEndpoint: 'http://localhost:3001',
}
],
verificationMethods: [
{
fragment: 'default',
keyData: authority.publicKey.toBytes(),
methodType: VerificationMethodType.Ed25519VerificationKey2018,
flags: [VerificationMethodFlags.CapabilityInvocation, VerificationMethodFlags.Authentication],
},
{
fragment: 'eth-address',
keyData: Buffer.from(ethAddress),
methodType: VerificationMethodType.EcdsaSecp256k1RecoveryMethod2020,
flags: [VerificationMethodFlags.CapabilityInvocation, VerificationMethodFlags.Authentication],
}
],
})
.withAutomaticAlloc(authority.publicKey)
.rpc();
The default Verification Method can never be removed, therefore not setting it within the update Method will cause all flags to be removed from it.
This operation allows for bulk updates of all changeable properties of a DID by passing an existing DidSolDocument.This is a destructive operation and should be handled with care. Furthermore, by overwriting all Verification Methods it removes ANY existing VerificationMethodFlags.OwnershipProof,
which are not allowed to be specified within the bulk update.
const document = DidSolDocument.fromDoc(
{
"@context": [
"https://w3id.org/did/v1.0",
"https://w3id.org/sol/v2.0"
],
"id": "did:sol:localnet:LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a",
"controller": [
"did:sol:localnet:A2oYuurjzc8ACwQQN56SBLv1kUmYJJTBjwMNWVNgVaT3"
],
"verificationMethod": [
{
"id": "did:sol:localnet:LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a#default",
"type": "Ed25519VerificationKey2018",
"controller": "did:sol:localnet:LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a",
"publicKeyBase58": "LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a"
},
{
"id": "did:sol:localnet:LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a#ledger",
"type": "Ed25519VerificationKey2018",
"controller": "did:sol:localnet:LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a",
"publicKeyBase58": "A2oYuurjzc8ACwQQN56SBLv1kUmYJJTBjwMNWVNgVaT3"
}
],
"authentication": [],
"assertionMethod": [],
"keyAgreement": [],
"capabilityInvocation": [
"did:sol:localnet:LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a#default",
"did:sol:localnet:LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a#ledger"
],
"capabilityDelegation": [],
"service": [
{
"id": "did:sol:localnet:LEGVfbHQ8VNuquHgWhHwZMKW4GMFemQWD13Vf3hY71a#test784378",
"type": "testType784378",
"serviceEndpoint": "testEndpoint784378"
}
]
});
await service
.updateFromDoc(document)
.withAutomaticAlloc(legacyAuthority.publicKey)
.rpc();
This transaction closes a DID account, reverting it to its generative state.
await service.close(rentDestination.publicKey).rpc();
The rent for the DID data account will be return to rentDestination. Closing a DID account requires using an AuthoritySigner.
Legacy DIDs resolve without issues with the current did:sol resolver, however, if you want to migrate a legacy DID (e.g. to use the new features), you can do so with the following code:
In order to migrate a DID the following requirements need to be met:
If no persisted state exists on either program it is a generative DID that does not need migration.
The prerequisites can be checked with await service.isMigratable().
The actual migration is done the following way:
// check if DID in service can be migrated.
const canMigrate = await service.isMigratable()
if (canMigrate) {
// migrate DID
await service
.migrate(nonAuthoritySigner.publicKey, legacyAuthority.publicKey)
.withPartialSigners(nonAuthoritySigner)
.withSolWallet(legacyAuthorityWallet)
.rpc();
}
Note, that the migrate function works with a nonAuthoritySigner, e.g. that means ANYONE
can migrate any DID to the new program. However, the migration keeps the state, costs no more rent, and adds functionality, so no harm is done through the DID migration. Since legacy DIDs often automatically allocated a lot of space and new migrated DIDs are optimally space efficient, the migration to a new DID can actually save SOL.
In this example, however,
nonAuthoritySigner is the rent payer for the new account.
In this example, migrate takes an optional legacyAuthority argument. If specified, it closes
the legacy DID account automatically and recovers the rent to the rent payer of the new
account (nonAuthoritySigner in this example). This action can only be done by an AuthoritySigner, which protects the owner’s rent payer fees from being stolen.
Note: Before contributing to this project, please check out the code of conduct and contributing guidelines.
nvm i
yarn
Install Solana locally by following the steps described here. Also, install Anchor by using the information found here
anchor test
FAQs
A powerful DID-method on Solana
We found that @identity.com/sol-did-client demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 15 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket Firewall Enterprise is now available with flexible deployment, configurable policies, and expanded language support.

Security News
Open source dashboard CNAPulse tracks CVE Numbering Authorities’ publishing activity, highlighting trends and transparency across the CVE ecosystem.

Product
Detect malware, unsafe data flows, and license issues in GitHub Actions with Socket’s new workflow scanning support.