Developer PortalPolymesh DocsSDK DocsRust DocsCommunity

With the SDK

Originating assets with the Polymesh 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 icon

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 @polymathnetwork/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 token with her personal account, and which we will eventually fix. The credible simple reason why Alice created the token 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 apiAlice: Polymesh = await Polymesh.connect({
        "nodeUrl": "wss://alcyone-rpc.polymesh.live", // For instance, or the proper network
        "accountMnemonic": "word1 word2 ..."
    });

Ticker reservation

Before creating the token 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.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 icon

Of note, the reservation cost, at the time of writing, is of 2,500 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 icon

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: CurrentIdentity = await apiAlice.getCurrentIdentity();
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 an identifier accountMnemonic that allowed the SDK to recreate the aliceKey private key. At the risk of repeating what was covered in the introduction module, this private key is not the Polymesh account proper, nor is it the public key. The public key is associated with an account, 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 account 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 icon

So when we wrote await in await apiAlice.getCurrentIdentity(), 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 security token.

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 getTickerReservation:

const apiAlice: Polymesh = await Polymesh.connect({...});
const reservation: TickerReservation = await apiAlice.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 getTickerReservation, this other account would not be able to follow up with a .createToken command because it doesn't own the reservation.

With our reservation in memory, and 10,000 POLYX in alice's wallet, which is the cost of creation of one security token, we now can create it:

const tokenQueue: TransactionQueue<SecurityToken> = await reservation.createToken({
    "name": "ACME Co",
    "tokenType": KnownTokenType.EquityPreferred,
    "isDivisible": false
});

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

const token: SecurityToken = await tokenQueue.run();

Implicit in the creation of this token is that Alice, as a private individual, is both the token'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 token instance will not always be in memory, so if we wanted to fetch it at a later date, we would do:

const token: SecurityToken = await apiAlice.getSecurityToken({
    "ticker": "ACME"
});

Secondary keys

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 apiAcme: Polymesh = await Polymesh.connect({
    "nodeUrl": "wss://alcyone-rpc.polymesh.live", // For instance, or the proper network
    "accountMnemonic": "word21 word22 ..."
});
const acme: CurrentIdentity = await apiAcme.getCurrentIdentity();
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 apiCeo: Polymesh = await Polymesh.connect({
    "nodeUrl": "wss://alcyone-rpc.polymesh.live", // For instance, or the proper network
    "accountMnemonic": "word31 word32 ..."
});

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.getAccount().address;

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

const acme: CurrentIdentity = await apiAcme.getCurrentIdentity();
await acme.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: CurrentAccount = apiCeo.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.

Token ownership transfer

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

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

const token: SecurityToken = await apiAlice.getSecurityToken({
    "ticker": "ACME"
});
const transferQueue: TransactionQueue<SecurityToken> = await token.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: CurrentIdentity = await apiAlice.getCurrentIdentity();
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 token is rightfully owned by ACME the company.

Compliance

We are not done yet with the token, 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 token 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 = token.compliance;
const acmeRequirements: Requirements = acmeCompliance.requirements;
const acme: CurrentIdentity = await apiCeo.getCurrentIdentity();
const setRequirementsQueue: TransactionQueue<SecurityToken> = await acmeRequirements.set({
    "requirements": [
        [
            {
                "target": ConditionTarget.Sender,
                "type": ConditionType.IsPrimaryIssuanceAgent
            }
        ],
        [
            {
                "target": ConditionTarget.Receiver,
                "type": ConditionType.IsPresent,
                "claim": {
                    "type": ClaimType.KnowYourCustomer,
                    "scope": {
                        "type": ScopeType.Ticker,
                        "value": token.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": token.ticker
                    }
                },
                "trustedClaimIssuers": [{
                    "identity": acme.did,
                    "trustedFor": [ClaimType.Jurisdiction]
                }]
            }
        ]
    ]
});
const updatedToken: SecurityToken = await setRequirementsQueue.run();

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

Rate this Page
Would you like to add a message?
Submit
Thank you for your Feedback!