Example of a Product
A Tracker certificate
This product reflects an open-ended performance certificate on an exchange-traded, underlying asset such a Treasury bill. The product offers the investor a bi-monthly redemption option.
The Settlement token in this example can be any cash token such as USDC which is supported by the Verified Network. The Security token in this example is the fund distribution token which is used to parameterize the investment product. Holders of the Security token can withdraw funds from the investment product, which in this case is a Certificate.
import Web3 from 'web3';
import BigNumber from 'bignumber.js';
import fs from 'fs';
import { AP, APTypes } from '@verified-network/protocol';
import ADDRESS_BOOK from '@verified-network/protocol/ap-chain/addresses.json';
import CERTFTerms from './CERTFTerms.json';
import { keys, rpcURL } from './secret.json';
import SettlementTokenArtifact from '@verified-network/protocol/build/contracts/contracts/tokens/SettlementToken.sol/SettlementToken.json';
(async () => {
const web3 = new Web3(new Web3.providers.HttpProvider(rpcURL));
const SecurityTokenArtifact = JSON.parse(fs.readFileSync('./abi/Security.json', 'utf8'));
const fdt = new web3.eth.Contract(SecurityTokenArtifact.abi, '0xd74109546668c7Fb97a093D9E888D6BF65eadfF0');
// setup accounts
keys.forEach((pk: string) => web3.eth.accounts.wallet.add(web3.eth.accounts.privateKeyToAccount(pk)));
const creator = (await web3.eth.getAccounts())[5]; // creator of the asset
const manager = (await web3.eth.getAccounts())[6]; // manager of the asset
const anyone = (await web3.eth.getAccounts())[2]; // used to make calls that could be made by any address
const holder = (await web3.eth.getAccounts())[3]; // future holder of FDTs created by asset manager
// initialize AP with web3 object and addressBook
const ap = await AP.init(web3, ADDRESS_BOOK);
// Deploy Settlement Token
// @ts-ignore
const settlementToken = await (new web3.eth.Contract(SettlementTokenArtifact.abi)).deploy(
{ data: SettlementTokenArtifact.bytecode }
).send({ from: creator, gas: 2000000 });
// create the term sheet by setting the currency to the Settlement Token contract we just deployed
const terms = { ...CERTFTerms };
// set up ownership
const ownership = {
creatorObligor: manager, // account which has to fulfill investor obligations (such as the initial exchange)
creatorBeneficiary: manager, // account which receives positive cashflows for the investor (such as redemptions)
counterpartyObligor: creator, // account which has to fulfill issuer obligations (such as paying redemptions)
counterpartyBeneficiary: creator, //account which receives positive cashflows for the issuer (such as the principal)
};
// create new CERTF asset
const initializeAssetTx = await ap.contracts.certfActor.methods.initialize(
terms,
[], // optionally pass custom schedule,
ownership,
ap.contracts.certfEngine.options.address,
ap.utils.constants.ZERO_ADDRESS,
ap.utils.constants.ZERO_ADDRESS
).send({ from: creator, gas: 2000000 });
// retrieve the AssetId from the transaction event logs
const assetId = initializeAssetTx.events.InitializedAsset.returnValues.assetId;
console.log('AssetId: ' + assetId);
// get asset terms
console.log('Terms: ', ap.utils.conversion.parseWeb3Response<APTypes.UTerms>(
await ap.contracts.certfRegistry.methods.getTerms(assetId).call())
);
// get asset state
console.log('State: ', ap.utils.conversion.parseWeb3Response<APTypes.UState>(
await ap.contracts.certfRegistry.methods.getState(assetId).call())
);
// get next scheduled event for the asset from the CERTF Registry and decode it
const nextScheduledEvent = await ap.contracts.certfRegistry.methods.getNextScheduledEvent(assetId).call();
const decodedEvent = ap.utils.schedule.decodeEvent(nextScheduledEvent);
// in our case that the IED (Initial Exchange event for transferring the principal from the investor to the issuer)
console.log('Next scheduled event: ', decodedEvent);
//compute pay off using engine
const payoff =
await ap.contracts.certfEngine.methods.computePayoffForEvent(
await ap.contracts.certfRegistry.methods.getTerms(assetId).call(),
await ap.contracts.certfRegistry.methods.getState(assetId).call(),
nextScheduledEvent,
web3.eth.abi.encodeParameter('uint256', decodedEvent.scheduleTime)
).call();
console.log("Pay off: ", payoff);
// approve actor to execute settlement payment (must be called before progressing the asset)
// manager has to give allowance to the actor to transfer the principal to the issuer
await ap.contracts.erc20(terms.currency).methods.approve(
ap.contracts.certfActor.options.address,
payoff
).send({ from: manager, gas: 2000000 });
// progress the asset - can be called by any account
await ap.contracts.certfActor.methods.progress(assetId).send({ from: manager, gas: 2000000 });
const isEventSettled = await ap.contracts.certfRegistry.methods.isEventSettled(
web3.utils.toHex(assetId), nextScheduledEvent
).call();
console.log("Is event settled: ", isEventSettled);
const projectedNextState =
await ap.contracts.certfEngine.methods.computeStateForEvent(
await ap.contracts.certfRegistry.methods.getTerms(assetId).call(),
await ap.contracts.certfRegistry.methods.getState(assetId).call(),
nextScheduledEvent,
web3.utils.toHex(0)
).call();
console.log("Projected next state: ", projectedNextState);
// creator sells 50% of his FDTs to a third party
await fdt.methods.transfer(
holder,
new BigNumber(await fdt.methods.balanceOf(manager).call()).dividedBy(2).toString()
).send({ from: manager, gas: 1000000 });
// set FDT contract as new beneficiary for asset
// in our case the FDT will receive and distribute all future payments
await ap.contracts.certfRegistry.methods.setCreatorBeneficiary(
assetId,
fdt.options.address
).send({ from: manager, gas: 2000000 });
// approve actor to execute settlement payment
// debtor has to give allowance to the Actor to transfer the first payment
await settlementToken.methods.approve(
ap.contracts.certfActor.options.address,
'25479452054794518000'
).send({ from: creator, gas: 2000000 });
// update internal balances in the FDT (can be called by any account)
await fdt.methods.updateFundsReceived().send({ from: holder, gas: 2000000 })
// check withdrawable funds for fractional owner after calling updateFundsReceived
const withdrawableAmount = await fdt.methods.withdrawableFundsOf(holder).call()
// Holder received 50% of the first interst payment
console.log('Withdrawable Balance of Holder: ' + withdrawableAmount.toString());
})();
The Certificate's terms that need to be specified by an application user is in the following format.
{
"contractType": "CERTF",
"calendar": "MF",
"contractRole": "BUY",
"dayCountConvention": "AA",
"businessDayConvention": "CSF",
"endOfMonthConvention": "EOM",
"couponType": "NOC",
"currency": "0x1c36690810ad06fb15552657c7a8ff653eb46f76",
"settlementCurrency": null,
"issueDate": "2020-06-18T00:00:00.000Z",
"statusDate": "2020-06-18T00:00:00.000Z",
"initialExchangeDate": null,
"maturityDate": null,
"cycleAnchorDateOfRedemption": "2020-07-18T00:00:00.000Z",
"cycleAnchorDateOfTermination": null,
"cycleAnchorDateOfCoupon": null,
"nominalPrice": "1000",
"issuePrice": "987.14",
"quantity": "1000",
"denominationRatio": "1",
"couponRate": "0",
"gracePeriod": "10D",
"delinquencyPeriod": "90D",
"settlementPeriod": "2D",
"fixingPeriod": "0D",
"redemptionRecordPeriod": "1D",
"cycleOfRedemption": "1M-",
"cycleOfTermination": "0D-",
"cycleOfCoupon": "0D-",
"contractStructure": {
"contractReference": [
{
"object": "0x43483232337276325f5246445f5241",
"object2": "0x756e646566696e6564",
"type": "MOC",
"role": "UDL"
},
{
"object": "0x43483232337276325f5246445f5154",
"object2": "0x756e646566696e6564",
"type": "MOC",
"role": "UDL"
}
]
}
}
Last updated