With the SDK
As always, everything that can be done via the Token Studio Dashboard can be integrated with internal systems using the SDK. Use this to automate and scale repetitive processes.
Let's see how ACME's share distribution would be performed using methods in the SDK. What we want to achieve is for ACME to issue 20,000 shares of ACME Corp to Alice the individual. As CEO, Alice will act for ACME.
Preconditions
We assume that:
- ACME, the company, already has a Polymesh account,
acme
; - Alice, ACME Co's CEO and acting agent, holds a secondary private key for
acme
that allows her to act on its behalf; - Alice the CEO can instantiate a Polymesh client whenever she needs to act with:
- TypeScript
- JavaScript
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,
});
const signingManagerCeo = await LocalSigningManager.create({
accounts: [
{
mnemonic: 'word31 word32 ...',
},
],
});
const apiCeo = await Polymesh.connect({
nodeUrl: 'wss://testnet-rpc.polymesh.live', // or your preferred node
signingManager: signingManagerCeo,
});
- The ACME token has already been created and has been retrieved and instantiated with the following:
- TypeScript
- JavaScript
const asset: Asset = await apiCeo.assets.getAsset({
ticker: 'ACME',
});
const asset = await apiCeo.assets.getAsset({
ticker: 'ACME',
});
acme
is the owner of the ACME token.acme
will also be the token's primary issuance agent;- Alice has an account,
alice
, that represents her as an individual; - Alice the individual also can instantiate a Polymesh client whenever she needs to act with:
- TypeScript
- JavaScript
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,
});
const signingManagerAlice = await LocalSigningManager.create({
accounts: [
{
mnemonic: 'word1 word2 ...',
},
],
});
const apiAlice = await Polymesh.connect({
nodeUrl: 'wss://testnet-rpc.polymesh.live', // or your preferred node
signingManager: signingManagerAlice,
});
Now that we went through the preconditions, let's continue preparing the distribution.
Prepare the distribution
Time for Alice to get her agreed amount of shares.
As we mentioned in the previous SDK chapter, implicit in const token
is that the context
is apiCeo
, because we used apiCeo
to retrieve its information. In other words, any action performed on a token
instantiated by apiCeo
will effectively be done by the acme
account.
At the time of its creation, the ACME token had a total supply of zero. Alice is personally entitled to 20,000 of the eventual 100,000 total supply of shares that the founders intend to issue. The primary issuance agent first issues, in effect mints, the shares to themselves, then sends them to their ultimate beneficiary(ies), like Alice.
Sending securities to someone needs the consent of the recipient. The correct Polymesh structure to achieve that is to create a settlement instruction. All settlement instructions are created in a venue. Therefore, we will create a venue.
Let's get started. We fetched ACME's account in the previous step:
- TypeScript
- JavaScript
const distributionVenueQueue: TransactionQueue<Venue> =
await acme.settlements.createVenue({
type: VenueType.Distribution,
description: 'For ACME Co',
});
const distributionVenue: Venue = await distributionVenueQueue.run();
const distributionVenueId: string = distributionVenue.id.toString();
const distributionVenueQueue = await acme.settlements.createVenue({
type: 'Distribution',
description: 'For ACME Co',
});
const distributionVenue = await distributionVenueQueue.run();
const distributionVenueId = distributionVenue.id.toString();
We keep distributionVenueId
, the string id of the venue, for later as this is information that will need to be shared with the ultimate recipient.
Now that we have the venue, let's have the CEO issue enough shares to later transfer to Alice. Of course, if the CEO planned on issuing shares for all founders of ACME Corp, she would issue more.
- TypeScript
- JavaScript
const asset: Asset = await apiCeo.assets.getAsset({
ticker: 'ACME',
});
const issuance: Issuance = asset.issuance;
const asset = await apiCeo.assets.getAsset({
ticker: 'ACME',
});
const issuance = asset.issuance;
Here too, in issuance
, the context is still apiCeo
. In fact, its parent
is the asset
itself.
- TypeScript
- JavaScript
const issueQueue: TransactionQueue<Asset> = await issuance.issue({
amount: new BigNumber('20000'),
});
await issueQueue.run();
const issueQueue = await issuance.issue({
amount: new BigNumber('20000'),
});
await issueQueue.run();
There is no beneficiary to mention because an issue
command sends the shares to the issuance agent, which, at this point is acme
. Additionally, it sends the shares to the account's default portfolio.
At this point, ACME, has 20,000 received shares. We still need to send them over to Alice the individual.
In which portfolio?
Now, Alice doesn't want to receive her ACME shares into her default portfolio. Instead, she wants to have them sent to a new "cold store" portfolio, she first needs to create the portfolio.
Here we are talking about Alice, the eventual owner, not the CEO, so we are going to use her Polymesh client instance apiAlice
:
- TypeScript
- JavaScript
const portfolioQueue: TransactionQueue<NumberedPortfolio> =
await apiAlice.identities.createPortfolio({
name: 'Cold store',
});
const coldStore: NumberedPortfolio = await portfolioQueue.run();
const portfolioQueue = await await apiAlice.identities.createPortfolio({
name: 'Cold store',
});
const coldStore = await portfolioQueue.run();
To retrieve the portfolio at a later date, anyone can use the string coldStore.id
, or find the portfolio by name.
Time to move the shares.
Now that Alice has created the portfolio in which she wishes to receive her shares, she needs to prepare some information for her alter ego, the CEO:
- TypeScript
- JavaScript
const aliceDid: string = alice.did;
const coldStoreId: string = coldStore.id.toString(10);
const aliceDid = alice.did;
const coldStoreId = coldStore.id;
Moving the shares, finally
Who creates the settlement instruction to move the shares? Alice the CEO, on behalf of ACME, so let's flip back to the proper context: apiCeo
.
On her end, she needs to re-instantiate some instances given the previous information:
- TypeScript
- JavaScript
const acme: Identity = await apiCeo.getSigningIdentity();
const acmeDefaultPortfolio: DefaultPortfolio =
await acme.portfolios.getPortfolio();
const acmeDid: string = acme.did;
const alice: Identity = await apiCeo.identities.getIdentity({
did: aliceDid,
});
const aliceColdStore: Portfolio = await alice.portfolios.getPortfolio({
portfolioId: new BigNumber(coldStoreId),
});
const acme = await apiCeo.getSigningIdentity();
const acmeDefaultPortfolio = await acme.portfolios.getPortfolio();
const acmeDid = acme.did;
const alice = await apiCeo.identities.getIdentity({
did: aliceDid,
});
const aliceColdStore = await alice.portfolios.getPortfolio({
portfolioId: new BigNumber(coldStoreId),
});
Now to the settlement instruction:
- TypeScript
- JavaScript
const distributionInstructionQueue: TransactionQueue<Instruction> =
await distributionVenue.addInstruction({
legs: [
{
amount: new BigNumber(20000),
from: acmeDefaultPortfolio,
to: aliceColdStore,
asset: asset,
},
],
});
const distributionInstruction: Instruction =
await distributionInstructionQueue.run();
const distributionInstructionId: string =
distributionInstruction.id.toString(10);
const distributionInstructionQueue = await distributionVenue.addInstruction({
legs: [
{
amount: new BigNumber(20000),
from: acmeDefaultPortfolio,
to: aliceColdStore,
asset: asset,
},
],
});
const distributionInstruction = await distributionInstructionQueue.run();
const distributionInstructionId = distributionInstruction.id;
This instruction is now recorded in the blockchain as pending. Alice the CEO is satisfied with it. She doesn't need to affirm it as the SDK identified her affirmation would have been required anyway, and so used the relevant add_and_affirm_instruction
extrinsic right away.
Does Alice agree?
Let's return to Alice, the future share owner, and seek her affirmation.
Since she is most likely on another computer or system, we need once more to reinstantiate the instruction from Alice's point of view. For that to happen, Alice the CEO needs to send, or copy-paste, acmeDid
, distributionVenueId
and distributionInstructionId
to Alice's system:
- TypeScript
- JavaScript
const acme: Identity = await apiAlice.identities.getIdentity({
did: acmeDid,
});
const venue: Venue = (await acme.getVenues()).find((venue: Venue) => {
return venue.id.isEqualTo(new BigNumber(distributionVenueId));
});
const instructions: Instruction[] = await venue.getInstructions();
const aliceInstruction: Instruction = instructions.pending.find(
(instruction: Instruction) => {
return instruction.id.isEqualTo(new BigNumber(distributionInstructionId));
}
);
const acme = await apiAlice.identities.getIdentity({
did: acmeDid,
});
const venue = (await acme.getVenues()).find((venue) => {
return venue.id.isEqualTo(new BigNumber(distributionVenueId));
});
const instructions = await venue.getInstructions();
const aliceInstruction = instructions.pending.find((instruction) => {
return instruction.id.isEqualTo(new BigNumber(distributionInstructionId));
});
Alice could also have chosen to retrieve all the pending instructions associated with her SigningIdentity
. That would have included all instructions, not just that one from the distribution venue she knows about. With a view to reduce the possibility of spam, she chose to use the venue to retrieve the instructions.
Now, it is just a matter of affirming the instruction:
- TypeScript
- JavaScript
const updatedInstructionQueue: TransactionQueue<Instruction> =
await aliceInstruction.affirm();
const updatedInstruction: Instruction = await updatedInstructionQueue.run();
const updatedInstructionQueue = await aliceInstruction.affirm();
const updatedInstruction = await updatedInstructionQueue.run();
And with this, Alice got her 20,000 shares.
Can Alice confirm she received the shares?
Well, she can check what's inside her portfolio:
- TypeScript
- JavaScript
const coldStore: NumberedPortfolio = await alice.portfolios.getPortfolio({
portfolioId: new BigNumber(coldStoreId),
});
const acmeAssetBalances: PortfolioBalance[] = await coldStore.getAssetBalances({
assets: ['ACME'],
});
const acmeAssetBalance: PortfolioBalance = acmeAssetBalances[0];
assert(acmeAssetBalance.total.isEqualTo(new BigNumber('20000')));
const coldStore = await alice.portfolios.getPortfolio({
portfolioId: new BigNumber(coldStoreId),
});
const acmeAssetBalances = await coldStore.getAssetBalances({
assets: ['ACME'],
});
const acmeAssetBalance = acmeAssetBalances[0];
assert(acmeAssetBalance.total.isEqualTo(new BigNumber('20000')));
How can Alice issue the shares of other founders?
As the primary issuance agent, she can repeat the steps to create one instruction per founder.
On the other hand, if the founders have decided that they should all receive their shares in a single atomic instruction, then, Alice would have to create a single instruction with all the legs:
- TypeScript
- JavaScript
const issueQueue: TransactionQueue<Asset> = await issuance.issue({
amount: new BigNumber('100000'),
});
await issueQueue.run();
const distributionInstructionQueue: TransactionQueue<Instruction> =
await distributionVenue.addInstruction({
legs: [
{
amount: new BigNumber(20000),
from: acmeDefaultPortfolio,
to: aliceColdStore,
asset: asset,
},
{
amount: new BigNumber(30000),
from: acmeDefaultPortfolio,
to: bobTargetPortfolio,
asset: asset,
},
{
amount: new BigNumber(50000),
from: acmeDefaultPortfolio,
to: carolTargetPortfolio,
asset: asset,
},
],
});
const distributionInstruction: Instruction =
await distributionInstructionQueue.run();
const distributionInstructionId: string =
distributionInstruction.id.toString(10);
const issueQueue = await issuance.issue({
amount: new BigNumber('100000'),
});
await issueQueue.run();
const distributionInstructionQueue = await distributionVenue.addInstruction({
legs: [
{
amount: new BigNumber(20000),
from: acmeDefaultPortfolio,
to: aliceColdStore,
asset: asset,
},
{
amount: new BigNumber(30000),
from: acmeDefaultPortfolio,
to: bobTargetPortfolio,
asset: asset,
},
{
amount: new BigNumber(50000),
from: acmeDefaultPortfolio,
to: carolTargetPortfolio,
asset: asset,
},
],
});
const distributionInstruction = await distributionInstructionQueue.run();
const distributionInstructionId = distributionInstruction.id.toString(10);
or whichever distribution was agreed between the parties.
The advantage of this single operation is that all parties can see in one go whether the eventual outcome is as per previously agreed.
When all is done
Assuming all parties have received their shares, it could be advantageous for the company to create a checkpoint. This post-issuance checkpoint would provide visibility about what was achieved during the issuance. A simple checkpoint is all you need. Note that only the token owner can create a checkpoint:
- TypeScript
- JavaScript
const checkpointQueue: TransactionQueue<Checkpoint> =
await asset.checkpoints.create();
const checkpoint: Checkpoint = await checkpointQueue.run();
const postDistributionCheckpointId: string = checkpoint.id.toString(10);
const checkpointQueue = await asset.checkpoints.create();
const checkpoint = await checkpointQueue.run();
const postDistributionCheckpointId = checkpoint.id.toString(10);
The checkpoint has an id, 0
if it is ACME's first checkpoint, which can be kept off-chain for future reference.
That's it, the lazy checkpoint is created, and has recorded all balances. For instance, at any point in time in the future, if Bob wants to get Carol's balance after the distribution, he needs to:
- TypeScript
- JavaScript
const asset: Asset = await apiBob.assets.getAsset({
ticker: 'ACME',
});
const postDistributionCheckpoint: Checkpoint = await asset.checkpoints.getOne({
id: new BigNumber(postDistributionCheckpointId),
});
const carolAcmeBalanceAtDistribution: BigNumber =
await postDistributionCheckpoint.balance({
identity: carol,
});
const asset = await apiBob.assets.getAsset({
ticker: 'ACME',
});
const postDistributionCheckpoint = await asset.checkpoints.getOne({
id: new BigNumber(postDistributionCheckpointId),
});
const carolAcmeBalanceAtDistribution = await postDistributionCheckpoint.balance(
{
identity: carol,
}
);