Skip to main content

With the SDK

In the previous exercise, we originated a simple asset, ACME Corp equity. As we did so, we discovered that there are many more configurable properties available to address common business requirements.

The Polymesh Dashboard is constructed with the SDK. The SDK supports every process you see there, and more. Use the SDK to build integrations with internal systems. Fortunately, the SDK's methods are intelligible when you know what it is you intend to do.

tip

If you want to take a closer look at the SDK, a peek into the SDK documentation is recommendable.

The SDK is a comprehensive set of business-level methods for inspecting and interacting with the Polymesh network using either Javascript or Typescript, at the developer's discretion.

You can find it here @polymeshassociation/polymesh-sdk

Preconditions

If you went through the Quick Start, we can assume that you have created an account (a public-private key pair) on the Polymesh Testnet, that you associated it with an account, and that you credited it with POLYX. We shall call this personal signing key aliceKey, and the personal account it represents alice.

Here, we are going to follow along the Token Studio Dashboard exercise, and do the same mistake. whereby she creates the asset with her personal account, and which we will eventually fix. The credible simple reason why Alice created the asset with her personal account is that her and her co-founders wanted to act fast so as to have the ticker available before getting the company through CDD.

To recap:

  • Alice, ACME's CEO and acting agent, already has an individual Polymesh account, named alice, tied to a primary private key named aliceKey;
  • aliceKey's private key is based on the "word1 word2 ..." mnemonic;
  • A Polymesh client has been instantiated by Alice so she can do the next actions:
const signingManagerAlice: LocalSigningManager =
await LocalSigningManager.create({
accounts: [
{
mnemonic: 'word1 word2 ...',
},
],
});

const apiAlice: Polymesh = await Polymesh.connect({
nodeUrl: 'wss://testnet-rpc.polymesh.live', // or your preferred node
signingManager: signingManagerAlice,
});

Ticker reservation

Before creating the asset proper, Alice needs to reserve the ACME ticker so that it is not squatted while the founders incorporate the company. Think of it on the same level as grabbing your .com domain as early as possible:

const reservationQueue: TransactionQueue<TickerReservation> =
await apiAlice.assets.reserveTicker({
ticker: 'ACME',
});

The TransactionQueue type is just that, a queue. The transaction(s) in it have not been launched. Notice that:

  • It is a generic type parameterised with TickerReservation. This means that, eventually, the queue yields an instance of TickerReservation;
  • The constructor of TransactionQueue expects a context; it is in this context object that apiAlice is referenced so it is understood that aliceKey is the private key to use for signing;
  • Each transaction in the queue has its own status;
  • The queue itself has its own status.
info

Of note, the reservation cost, at the time of writing, is of 1,000 POLYX, before network fees.

Let's run it.

const reservation: TickerReservation = await reservationQueue.run();

It is at this point that the necessary signatures are collected for the transactions. apiAlice was created straight with the mnemonic, so the signature will be affixed automatically. However, if this was taking place in the browser, for instance in the Token Studio Dashboard and with the Polymesh Wallet extension, then there is a possibility that Alice will refuse to sign when prompted.

tip

You would need to try .run() catch it for errors. Here, we opted for clarity and omitted this detail.

Also note that with await reservationQueue.run() we patiently wait for the queue to finalise all its transactions. However, a TransactionQueue can provide intermediate information about its changing status and that of its component transactions. If this is of interest to you, you can pass callbacks to onStatusChange and onTransactionStatusChange.

At this stage, Alice owns the reservation. You can confirm it with the following:

const alice: Identity = await apiAlice.getSigningIdentity();
const details: TickerReservationDetails = await reservation.details();
const owner: Identity = details.owner;
assert(owner.did === alice.did);

Something is not immediately apparent from the few lines of code above. It is nonetheless important to point it out.

When we created apiAlice with await Polymesh.connect(), we passed a signingManager which was created with an accountMnemonic that allowed it to recreate the aliceKey private key. On chain the public key is expressed in SS58 format and referred to as an "address" or "account". The account is then is associated with an identity, whether as a primary key, like here, or a secondary one. When the private key is used to sign a transaction, it is the associated identity that will be considered to be the one doing the action.

This associated account information is not embedded in the private key. It is an association that is stored on the blockchain, may change in the future, and as such, it needs to be retrieved to be known.

info

So when we wrote await in await apiAlice.getSigningIdentity(), this is no accident. We indeed need to do a round trip to the blockchain to know what account our public key is associated with.

Ticker creation

Now that the ticker is reserved, it is time to issue the asset.

Oh wait! The reservation may have happened some time ago. After all a reservation remains valid for 60 days, for instance. And your const reservation: TickerReservation instance might no longer be in memory.

How do you get it back? Use assets.getTickerReservation:

const signingManagerAlice: LocalSigningManager = await LocalSigningManager.create({...});
const apiAlice: Polymesh = await Polymesh.connect({...});
const reservation: TickerReservation = await apiAlice.assets.getTickerReservation({
"ticker": "ACME"
});

You will note that the class TickerReservation, just like the transaction queue, keeps a protected context: Context. This context in turn keeps a polymeshApi. It is in there that we find the implicit knowledge that it is alice's account that is asking for the next actions. If it were any other account that had called assets.getTickerReservation, this other account would not be able to follow up with a .createAsset command because it doesn't own the reservation.

With our reservation in memory we now can create it:

const assetQueue: TransactionQueue<Asset> = await reservation.createAsset({
name: 'ACME Co',
assetType: KnownAssetType.EquityPreferred,
isDivisible: false,
});

We have another queue, so, as we did before:

const asset: Asset = await assetQueue.run();

Implicit in the creation of this asset is that Alice, as a private individual, is both the asset's owner and its primary issuance agent (PIA). We were satisfied with this situation only up to this point. Now this needs to change.

This asset instance will not always be in memory, so if we wanted to fetch it at a later date, we would do:

const asset: Asset = await apiAlice.assets.getAsset({
ticker: 'ACME',
});

Secondary Accounts

Now we assume that ACME has gone through CDD and has an account, complete with a private key and its mnemonic, which, in a mirror fashion of that of Alice gives us:

const signingManagerAcme: LocalSigningManager =
await LocalSigningManager.create({
accounts: [
{
mnemonic: 'word21 word22 ...',
},
],
});
const apiAcme: Polymesh = await Polymesh.connect({
nodeUrl: 'wss://testnet-rpc.polymesh.live', // or your preferred node
signingManager: signingManagerAcme,
});
const acme: Identity = await apiAcme.getSigningIdentity();
const acmeDid: string = acme.did;

On her end, Alice, has created another mnemonic for a private key she intends to use when she acts as the CEO of ACME. Again:

const signingManagerCeo: LocalSigningManager = await LocalSigningManager.create(
{
accounts: [
{
mnemonic: 'word31 word32 ...',
},
],
}
);
const apiCeo: Polymesh = await Polymesh.connect({
nodeUrl: 'wss://testnet-rpc.polymesh.live', // or your preferred node
signingManager: signingManagerCeo,
});

At this point, apiCeo has no associated account. It is a signing key in search of an account. Alice first needs to get her public key:

const pubCeo: string = apiCeo.accountManagement.getAccount().address;

Then she needs to send this pubCeo information to apiAcme. When this is done, the company can invite her to be a secondary key:

await apiAcme.accountManagement.inviteAccount({
targetAccount: pubCeo,
});

With the invitation sent out into the blockchain, back at her computer, Alice, with knowledge of ACME account's number, acmeDid, can do:

const ceoAccount: Account = apiCeo.accountManagement.getAccount();
const pendingAuthorizations: AuthorizationRequest[] =
await ceoAccount.authorizations.getReceived();
const acmeAuthorization: AuthorizationRequest = pendingAuthorizations.find(
(pendingAuthorization: AuthorizationRequest) => {
return pendingAuthorization.issuer.did === acmeDid;
}
);
const acceptQueue: TransactionQueue<void> = await acmeAuthorization.accept();
await acceptQueue.run();

With this done, apiCeo now allows Alice to properly act as the CEO, on behalf of ACME.

Asset ownership transfer

With the keys and accounts finally set right, it is time for Alice to fix the asset situation, and transfer its ownership to ACME.

Since it is Alice the individual who owns the asset, she has to go back to using her personal account.

const asset: Asset = await apiAlice.assets.getAsset({
ticker: 'ACME',
});
const transferQueue: TransactionQueue<Asset> = await asset.transferOwnership({
target: acmeDid,
});
await transferQueue.run();

With the authorisation recorded in the blockchain, and on the way, Alice can stay on the same computer and switch from her personal identity to her identity as the CEO of ACME to accept the authorisation.

She first needs to recall her personal account number, or did.

const alice: Identity = await apiAlice.getSigningIdentity();
const aliceDid: string = alice.did;

So she can narrow down the authorisation, instead of blindly accepting whatever is in the pipeline.

const pendingAuthorizations: AuthorizationRequest[] =
await ceoAccount.authorizations.getReceived();
const transferAuthorization: AuthorizationRequest = pendingAuthorizations.find(
(pendingAuthorization: AuthorizationRequest) => {
return pendingAuthorization.issuer.did === aliceDid;
}
);
const acceptQueue: TransactionQueue<void> =
await transferAuthorization.accept();
await acceptQueue.run();

With this, the asset is rightfully owned by ACME the company.

Compliance

We are not done yet with the asset, though.

As the CEO, Alice still needs to do one more step, that is, to define the conditions of ownership. Namely, require any account who acquires the asset to not have a jurisdictional attestation of Liechtenstein. An exception will be made for the primary issuance agent, who is simply used as a conduit and can send to anyone.

We use ACME's account as the KYC service provider, but realistically, it should be another account.

const acmeCompliance: Compliance = asset.compliance;
const acmeRequirements: Requirements = acmeCompliance.requirements;
const acme: Identity = await apiCeo.getSigningIdentity();
const setRequirementsQueue: TransactionQueue<Asset> =
await acmeRequirements.set({
requirements: [
[
{
target: ConditionTarget.Sender,
type: ConditionType.IsExternalAgent,
},
],
[
{
target: ConditionTarget.Receiver,
type: ConditionType.IsPresent,
claim: {
type: ClaimType.KnowYourCustomer,
scope: {
type: ScopeType.Ticker,
value: asset.ticker,
},
},
trustedClaimIssuers: [
{
identity: acme.did,
trustedFor: [ClaimType.KnowYourCustomer],
},
],
},
{
target: ConditionTarget.Receiver,
type: ConditionType.IsAbsent,
claim: {
type: ClaimType.Jurisdiction,
code: CountryCode.Li,
scope: {
type: ScopeType.Ticker,
value: asset.ticker,
},
},
trustedClaimIssuers: [
{
identity: acme.did,
trustedFor: [ClaimType.Jurisdiction],
},
],
},
],
],
});
const updatedAsset: Asset = await setRequirementsQueue.run();

With this, the asset is originated. Nobody, including Alice under her personal account, is yet a holder of any amount of the asset, though, we remedy that in the next chapter when we tackle distribution.