Pact Smart Contract Language API JavaScript wrappers
This package is here to help make interaction with both Pact's development server & a ScalableBFT cluster running Pact easy. The API's for each are the same. For information about Pact & ScalableBFT please see kadena.io or github.com/kadena-io/pact.
For example usage, see github.com/kadena-io/pact-todomvc
Import
pact-lang-api.js
should work as expected as a regular node dependency.
<script src="https://cdn.jsdelivr.net/npm/pact-lang-api@4.1.2/pact-lang-api-global.min.js"></script>
will import the library and inject it as a top-level definition called Pact
Functions
Crypto
Converting between binary & hex formats:
Pact.crypto.binToHex(<Uint8Array>) -> string
Pact.crypto.hexToBin(string) -> Uint8Array
Hashing (blake2b):
Pact.crypto.hash(string) -> string
PPK Signing is done via TweetNacl but with ed25519-donna style keys, represented as hex. blake2b is used to hash the message (), and TweetNacl then signs the hash.
Pact.crypto.genKeyPair() -> {"publicKey": <string>, "secretKey": <string>}
Pact.crypto.sign(<string>, keyPair) -> {"hash": <string>, "sig":<string>, "pubKey":<string>}
Pact.crypto.toTweetNaclSecretKey(keyPair) -> <Uint8Array>
Restoring a key pair from secret key.
Pact.crypto.restoreKeyPairFromSecretKey(secretKey) -> {"publicKey": <string>, "secretKey": <string>}
Language Expression Construction
A helper function for constructing native Pact commands.
mkExp
takes in Pact function and its arguments and returns a Pact expression.mkMeta
returns meta information of the tx in object format. This is only important for the txs in public blockchain. txs don't need a meta field in private blockchain.- "sender" represents the gas account, and the tx must be signed with the keyset associated with the gas account. Otherwise, the tx will be rejected.
- "chainId" represents the chain Id that the tx will be sent to.
- "gasPrice" represents the gas price of the tx.
- "gasLimit" represents the gas limit of the tx.
- "creationTime" represents the tx's wait time to be considered a candidate for inclusion into a block in the blockchain. (in seconds)
- "ttl" represents the tx's maximum wait time to be considered a candidate for inclusion into a block in the blockchain. (in seconds)
mkCap
prepares a capability object to be signed with keyPairs using signing API.
Pact.lang.mkExp(<function:string>, *args) -> <string>
ex: mkExp("todos.edit-todo", 1, "bar")
-> '(todos.edit-todo 1 "bar")'
Pact.lang.mkMeta(<sender:string> , <chainId:string>, <gasPrice: number>, <gasLimit: number>, <creationTime: number>, <ttl: number>) -> <meta: object>
ex: mkMeta("Bob", "1", 0.0001, 100, 0, 28800)
-> { "sender": "Bob", "chainId": "1", "gasPrice": 0.0001, "gasLimit": 100, "creationTime": 0, "ttl": 28800 }
Pact.lang.mkCap(<role:string> , <description:string>, <name: string>, <args: object>) -> <cap: object>
ex: mkCap("Coin Transfer", "Capability to transfer designated amount of coin from sender to receiver", "coin.TRANSFER", ["Bob", "Kate", 10])
-> {
"role": "Coin Transfer",
"description": "Capability to transfer designated amount of coin from sender to receiver",
"cap": {
"name": "coin.TRANSFER",
"args": ["Bob", "Kate", 10]
}
}
NB: JSON.stringify
, which is used here, generally converts floating point numbers correctly but fails for high precision scientific numbers < 0; you will need to manually convert them.
e.g. JSON.stringify(.0000001) -> '1e-7'
is incorrect as Pact has infinite precision decimals but does not interpret scientific numbers, so the proper conversion would be JSON.stringify(.0000001) -> '0.0000001'
Simple API Fetch
Simple fetch functions to make API request to a running Pact Server and retrieve the results.
/**
* An execCmd Object to Execute at /send or /local endpoint.
* @typedef {Object} execCmd
* @property type {string} - type of command - "cont" or "exec", default to "exec"
* @property pactCode {string} - pact code to execute in "exec" command - required for "exec"
* @property nonce {string} - nonce value to ensure unique hash - default to current time
* @property envData {object} - JSON of data in command - not required
* @property meta {object} - public meta information, see mkMeta
* @property networkId {string} network identifier of where the cmd is executed.
*/
/**
* A contCmd to Execute at /send endpoint
* @typedef {Object} contCmd
* @property type {string} - type of command - "cont" or "exec", default to "exec"
* @property pactId {string} - pactId the cont command - required for "cont"
* @property nonce {string} - nonce value to ensure unique hash - default to current time
* @property step {number} - the step of the mutli-step transaction - required for "cont"
* @property proof {string} - JSON of SPV proof, required for cross-chain transfer. See `fetchSPV` below
* @property rollback {bool} - Indicates if this continuation is a rollback/cancel - required for "cont"
* @property envData {object} - JSON of data in command - not required
* @property meta {object} - public meta information, see mkMeta
* @property networkId {string} network identifier of where the cmd is executed.
*/
## Make API request to execute a command or commands in the public server and retrieve request keys of the txs.
Pact.fetch.send([<execCmd:object> or <contCmd:object>], <apiHost:string>) -> {"requestKeys": [...]}
ex:
const cmds = [
// create an account with single-sig keyset
{
keyPairs: KEY_PAIR,
pactCode: "(coin.create-account 'account-1 (read-keyset 'account-keyset))",
envData: {
"account-keyset": ["368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca"],
}
},
// create an account with multi-sig keyset
{
keyPairs: KEY_PAIR,
pactCode: "(coin.create-account 'account2 (read-keyset 'account-keyset))",
envData: {
"account-keyset": {
"keys": [
"2d70aa4f697c3a3b8dd6d97745ac074edcfd0eb65c37774cde25135483bea71e",
"4c31dc9ee7f24177f78b6f518012a208326e2af1f37bb0a2405b5056d0cad628"
],
"pred": "keys-any"
}
}
}
]
Pact.fetch.send(cmds, API_HOST)
// Returns the following as a Promise Value
{ "requestKeys": [ "6ue-lrwXaLcDyxDwJ1nuLzOfFtnQ2TaF0_Or_X0KnbE",
"P7qDsrt3evfEjtlQAW_b1ZPS7LpAZynCO8wx99hc5i0" ]}
## Make API request to execute a single command in the local server and retrieve the result of the tx. (Used to execute commands that read DB)
Pact.fetch.local(<execCmd:object>, <apiHost:string>) -> {result}
ex:
const cmd = {
keyPairs: KEY_PAIR,
pactCode: `(coin.details 'account1)`
};
Pact.fetch.local(cmd, API_HOST)
// Returns the following as a Promise Value
{ gas: 0,
result: { status: 'success', data: 'Write succeeded' },
reqKey: 'ZWsF84CuKVq4qxjFrgbBr15EHbhKxaeAP6S6qRTWkmY',
logs: 'wsATyGqckuIvlm89hhd2j4t6RMkCrcwJe_oeCYr7Th8',
metaData:
{ publicMeta:
{ creationTime: 1574809666,
ttl: 28800,
gasLimit: 10000,
chainId: '0',
gasPrice: 1e-9,
sender: 'sender00' },
blockTime: 0,
prevBlockHash: '',
blockHeight: 0 },
continuation: null,
txId: null }
## Make API request to retrieve result of a tx or multiple tx's with request keys.
Pact.fetch.poll({requestKeys: ["..."]}, <apiHost:string>) -> {result}
ex:
const cmd = { requestKeys: [ "6ue-lrwXaLcDyxDwJ1nuLzOfFtnQ2TaF0_Or_X0KnbE",
"P7qDsrt3evfEjtlQAW_b1ZPS7LpAZynCO8wx99hc5i0" ]}
Pact.fetch.poll(cmd, API_HOST)
// Returns the following as a Promise Value
{ 6ue-lrwXaLcDyxDwJ1nuLzOfFtnQ2TaF0_Or_X0KnbE:
{ gas: 6296,
result: { status: 'success', data: 'Write succeeded' },
reqKey: '6ue-lrwXaLcDyxDwJ1nuLzOfFtnQ2TaF0_Or_X0KnbE',
logs: 'IVz-tGIp3TwibAqlq6UGt4yFiJ-d9sqvcbWVTEs_e68',
metaData: null,
continuation: null,
txId: 702717 },
P7qDsrt3evfEjtlQAW_b1ZPS7LpAZynCO8wx99hc5i0:
{ gas: 6296,
result: { status: 'success', data: 'Write succeeded' },
reqKey: 'P7qDsrt3evfEjtlQAW_b1ZPS7LpAZynCO8wx99hc5i0',
logs: 'sSDqe9W36P43WEUfdyBRB7m-t0qQZjVRtu5jdElfMzgs',
metaData: null,
continuation: null,
txId: 702717 }
}
## Make API request to retrieve result of a tx with a request key.
Pact.fetch.listen({listen: "..."}, <apiHost:string>) -> {status: "...", data: "..."}
ex:
const cmd = { listen: "P7qDsrt3evfEjtlQAW_b1ZPS7LpAZynCO8wx99hc5i0" }
Pact.fetch.listen(cmd, API_HOST)
// Returns the following as a Promise Value
{ gas: 6296,
result: { status: 'success', data: 'Write succeeded' },
reqKey: 'P7qDsrt3evfEjtlQAW_b1ZPS7LpAZynCO8wx99hc5i0',
logs: 'IVz-tGIp3TwibAqlq6UGt4yFiJ-d9sqvcbWVTEs_e68',
metaData: null,
continuation: null,
txId: 702717 }
/**
* A SPV Command Object to Execute at /spv endpoint.
* @typedef {Object} spvCmd
* @property requestKey {string} pactId of the SPV transaction
* @property targetChainId {string} chainId of target chain of SPV transaction
*/
## Make API request to retrieve proof of SPV request.
Pact.fetch.spv([<spvCmd:object>], <apiHost:string>) -> "[proof base64url value]"
ex:
const cmd = { requestKey: "CzZzVpxdQHiL7tmqNnAeCB0qX-nXyqFYAystzNlrBhw",
targetChainId: "1" }
Pact.fetch.spv(cmd, API_HOST)
// Returns the following as a Promise Value
[proof base64url value]
Chainweaver Signing API Command
Simple functions to interact with Chainweaver wallet (https://github.com/kadena-io/chainweaver) and its signing API.
* A signingCmd Object to send to signing API
* @typedef {Object} signingCmd - cmd to send to signing API
* @property pactCode {string} - Pact code to execute - required
* @property caps {array or object} - Pact capability to be signed, see mkCap - required
* @property envData {object} - JSON of data in command - optional
* @property sender {string} - sender field in meta, see mkMeta - optional
* @property chainId {string} - chainId field in meta, see mkMeta - optional
* @property gasLimit {number} - gasLimit field in meta, see mkMeta - optional
* @property gasPrice {string} - gasPrice field in meta, see mkMeta - optional
* @property signingPubKey {string} - public key of the signer - optional
* @property networkId {string} - network identifier of where the cmd is executed - optional
* @property nonce {string} - nonce value for ensuring unique hash - optional
**/
## Sends parameters of Pact Command to the Chainweaver signing API and retrieves a signed Pact Command.
Pact.wallet.sign(<signingCmd:object>) -> {<execCmd:object>}
## Sends a signed Pact ExecCmd to a running Pact server and retrieves tx result.
Pact.wallet.sendSigned(<execCmd:object>, <apiHost:string>) -> {"requestKeys": [...]}
Simple API Command
A simplified set of functions for working with the api.
## Creates a command to send as POST to /api/send
Pact.simple.exec.createCommand([keyPair], <nonce: string>, <pactCode: string>, <envData: object>) -> {"cmds":[...]}
## Creates a command to send as POST to /api/local
Pact.simple.exec.createLocalRequest([keyPair], <nonce: string>, <pactCode: string>, <envData: object>) -> {"hash": "...", sigs: [...], cmd: {...} }
## Creates a command to send as POST to /api/poll
Pact.simple.exec.createPollRequest({"cmds": [...]}) -> {"requestKeys": [...]}
## Creates a command to send as POST to /api/listen
Pact.simple.exec.createListenRequest({"cmds": [...]}) -> {"listen": <string>}
Low Level API
Lower level/internal functions to aid in the construction of JSON blobs that the API endpoints expect.
You probably want to use the Pact.simple
functions instead of these.
Pact.api.mkSingleCmd([signatures],{cmd-object}) -> {"hash":<string>, "sigs":[signatures], "cmd":cmd}
Pact.api.mkPublicSend([cmd]) -> {"cmds":[cmd]} \\ send as POST to /api/poll