Last active
December 5, 2022 19:34
-
-
Save tuncatunc/3cfea15d6fddd29c6c0e1e9f8a683c08 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const HELP = `Usage example: \n\nnode transfer-create.js k:{public-key} amount -- Replace {public-key} with an actual key`; | |
import Pact from "pact-lang-api" | |
const NETWORK_ID = 'development'//'testnet04'; | |
const CHAIN_ID = '0'; | |
const API_HOST = `http://localhost:8080/chainweb/0.0/${NETWORK_ID}/chain/${CHAIN_ID}/pact`; | |
const KEY_PAIR = { | |
publicKey: "368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca", | |
secretKey: "251a920c403ae8c8f65f59142316af3c82b631fba46ddea92ee8c95035bd2898" | |
}; | |
const creationTime = () => Math.round(new Date().getTime() / 1000); | |
if (process.argv.length !== 4) { | |
console.info(HELP); | |
process.exit(1); | |
} | |
if (KEY_PAIR.publicKey === "" || KEY_PAIR.secretKey === "") { | |
console.error("Please set a key pair"); | |
process.exit(1); | |
} | |
// transferCreate(KEY_PAIR.publicKey, process.argv[2], process.argv[3]); | |
transferCreate("sender00", process.argv[2], process.argv[3]); | |
async function transferCreate(sender, newAccount, amount) { | |
const cmd = { | |
networkId: NETWORK_ID, | |
keyPairs: [ | |
Object.assign(KEY_PAIR, { | |
clist: [ | |
Pact.lang.mkCap( | |
"GAS", | |
"Capability to allow buying gas", | |
"coin.GAS", | |
[] | |
).cap, | |
Pact.lang.mkCap( | |
"Transfer", | |
"Capability to allow coin transfer", | |
"coin.TRANSFER", | |
[sender, newAccount, { decimal: amount }] | |
).cap | |
] | |
}) | |
], | |
pactCode: `(free.k.transfer-create "${sender}" "${newAccount}" (read-keyset "account-keyset") ${amount})`, | |
envData: { | |
"account-keyset": { | |
keys: [ | |
// Drop the k: | |
newAccount.substr(2) | |
], | |
pred: "keys-all" | |
} | |
}, | |
meta: { | |
creationTime: creationTime(), | |
ttl: 28000, | |
gasLimit: 60000, | |
chainId: CHAIN_ID, | |
gasPrice: 0.0000001, | |
// sender: KEY_PAIR.publicKey | |
sender: "sender00" | |
} | |
}; | |
const response = await Pact.fetch.send(cmd, API_HOST); | |
if (!response.requestKeys) | |
{ | |
console.error(response) | |
return | |
} | |
console.log(`Request key: ${response.requestKeys[0]}`); | |
// console.log("Transaction pending..."); | |
const txResult = await Pact.fetch.listen( | |
{ listen: response.requestKeys[0] }, | |
API_HOST | |
); | |
console.log("Transaction mined!"); | |
console.log(JSON.stringify(txResult, null, 2)); | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(namespace "free") | |
(define-keyset "free.k-admin-keyset" | |
(read-keyset "k-admin-keyset")) | |
(module k GOVERNANCE | |
@doc "K token smart contract" | |
@model | |
[ (defproperty conserves-mass (amount:decimal) | |
(= (column-delta token-table 'balance) 0.0)) | |
(defproperty valid-account-id (accountId:string) | |
(and | |
(>= (length accountId) 3) | |
(<= (length accountId) 256))) | |
] | |
(implements fungible-v2) | |
(implements fungible-xchain-v1) | |
; -------------------------------------------------------------------------- | |
; Schemas and Tables | |
(defschema token-schema | |
@doc " An account, holding a token balance. \ | |
\ \ | |
\ ROW KEY: accountId. " | |
balance:decimal | |
guard:guard | |
) | |
(deftable token-table:{token-schema}) | |
; -------------------------------------------------------------------------- | |
; Capabilities | |
(defcap GOVERNANCE | |
() | |
@doc " Give the admin full access to call and upgrade the module. " | |
(enforce-keyset "free.k-admin-keyset") | |
) | |
(defcap ACCOUNT_GUARD | |
( accountId:string ) | |
@doc " Look up the guard for an account, required to debit from that account. " | |
(enforce-guard (at 'guard (read token-table accountId ['guard]))) | |
) | |
(defcap DEBIT | |
( sender:string ) | |
@doc " Capability to perform debiting operations. " | |
(enforce-guard (at 'guard (read token-table sender ['guard]))) | |
(enforce (!= sender "") "Invalid sender.") | |
) | |
(defcap CREDIT | |
( receiver:string ) | |
@doc " Capability to perform crediting operations. " | |
(enforce (!= receiver "") "Invalid receiver.") | |
) | |
(defcap TRANSFER:bool | |
( sender:string | |
receiver:string | |
amount:decimal ) | |
@doc " Capability to perform transfer between two accounts. " | |
@managed amount TRANSFER-mgr | |
(enforce (!= sender receiver) "Sender cannot be the receiver.") | |
(enforce-unit amount) | |
(enforce (> amount 0.0) "Transfer amount must be positive.") | |
(compose-capability (DEBIT sender)) | |
(compose-capability (CREDIT receiver)) | |
) | |
(defun TRANSFER-mgr:decimal | |
( managed:decimal | |
requested:decimal ) | |
(let ((newbal (- managed requested))) | |
(enforce (>= newbal 0.0) | |
(format "TRANSFER exceeded for balance {}" [managed])) | |
newbal | |
) | |
) | |
(defcap TRANSFER_XCHAIN:bool | |
( sender:string | |
receiver:string | |
amount:decimal | |
target-chain:string | |
) | |
@managed amount TRANSFER_XCHAIN-mgr | |
(enforce-unit amount) | |
(enforce (> amount 0.0) "Cross-chain transfers require a positive amount") | |
(compose-capability (DEBIT sender)) | |
) | |
(defun TRANSFER_XCHAIN-mgr:decimal | |
( managed:decimal | |
requested:decimal | |
) | |
(enforce (>= managed requested) | |
(format "TRANSFER_XCHAIN exceeded for balance {}" [managed])) | |
0.0 | |
) | |
(defcap TRANSFER_XCHAIN_RECD:bool | |
( sender:string | |
receiver:string | |
amount:decimal | |
source-chain:string | |
) | |
@event true | |
) | |
; -------------------------------------------------------------------------- | |
; Constants | |
(defconst INITIAL_SUPPLY:decimal 1000000000.0 | |
" Initial supply of 1 billion tokens. ") | |
(defconst DECIMALS 12 | |
" Specifies the minimum denomination for token transactions. ") | |
(defconst ACCOUNT_ID_CHARSET CHARSET_LATIN1 | |
" Allowed character set for account IDs. ") | |
(defconst ACCOUNT_ID_PROHIBITED_CHARACTER "$") | |
(defconst ACCOUNT_ID_MIN_LENGTH 3 | |
" Minimum character length for account IDs. ") | |
(defconst ACCOUNT_ID_MAX_LENGTH 256 | |
" Maximum character length for account IDs. ") | |
; -------------------------------------------------------------------------- | |
; Utilities | |
(defun validate-account-id | |
( accountId:string ) | |
@doc " Enforce that an account ID meets charset and length requirements. " | |
(let ((accountLength (length accountId))) | |
(enforce | |
(>= accountLength ACCOUNT_ID_MIN_LENGTH) | |
(format | |
"Account ID does not conform to the min length requirement: {}" | |
[accountId])) | |
(enforce | |
(<= accountLength ACCOUNT_ID_MAX_LENGTH) | |
(format | |
"Account ID does not conform to the max length requirement: {}" | |
[accountId])) | |
) | |
(enforce | |
(is-charset ACCOUNT_ID_CHARSET accountId) | |
(format | |
"Account ID does not conform to the required charset: {}" | |
[accountId])) | |
(enforce | |
(not (contains accountId ACCOUNT_ID_PROHIBITED_CHARACTER)) | |
(format "Account ID contained a prohibited character: {}" [accountId])) | |
) | |
;; ; -------------------------------------------------------------------------- | |
;; ; Fungible-v2 Implementation | |
(defun transfer-create:string | |
( sender:string | |
receiver:string | |
receiver-guard:guard | |
amount:decimal ) | |
@doc " Transfer to an account, creating it if it does not exist. " | |
@model [ (property (conserves-mass amount)) | |
(property (> amount 0.0)) | |
(property (valid-account-id sender)) | |
(property (valid-account-id receiver)) | |
(property (!= sender receiver)) ] | |
(with-capability (TRANSFER sender receiver amount) | |
(debit sender amount) | |
(credit receiver receiver-guard amount) | |
) | |
) | |
(defun transfer:string | |
( sender:string | |
receiver:string | |
amount:decimal ) | |
@doc " Transfer to an account, failing if the account does not exist. " | |
@model [ (property (conserves-mass amount)) | |
(property (> amount 0.0)) | |
(property (valid-account-id sender)) | |
(property (valid-account-id receiver)) | |
(property (!= sender receiver)) ] | |
(enforce (!= sender receiver) | |
"sender cannot be the receiver of a transfer") | |
(validate-account-id sender) | |
(validate-account-id receiver) | |
(enforce (> amount 0.0) | |
"transfer amount must be positive") | |
(enforce-unit amount) | |
(with-read token-table receiver | |
{ "guard" := guard } | |
(transfer-create sender receiver guard amount) | |
) | |
) | |
(defun debit | |
( accountId:string | |
amount:decimal ) | |
@doc " Decrease an account balance. Internal use only. " | |
@model [ (property (> amount 0.0)) | |
(property (valid-account-id accountId)) | |
] | |
(validate-account-id accountId) | |
(enforce (> amount 0.0) "Debit amount must be positive.") | |
(enforce-unit amount) | |
(require-capability (DEBIT accountId)) | |
(with-read token-table accountId | |
{ "balance" := balance } | |
(enforce (<= amount balance) "Insufficient funds.") | |
(update token-table accountId | |
{ "balance" : (- balance amount) } | |
) | |
) | |
) | |
(defun credit | |
( accountId:string | |
guard:guard | |
amount:decimal ) | |
@doc " Increase an account balance. Internal use only. " | |
@model [ (property (> amount 0.0)) | |
(property (valid-account-id accountId)) | |
] | |
(validate-account-id accountId) | |
(enforce (> amount 0.0) "Credit amount must be positive.") | |
(enforce-unit amount) | |
(require-capability (CREDIT accountId)) | |
(with-default-read token-table accountId | |
{ "balance" : -1.0 | |
, "guard" : guard | |
} | |
{ "balance" := balance | |
, "guard" := currentGuard | |
} | |
(enforce (= currentGuard guard) "Account guards do not match.") | |
(let ((is-new | |
(if (= balance -1.0) | |
(enforce-reserved accountId guard) | |
false))) | |
(write token-table accountId | |
{ "balance" : (if is-new amount (+ balance amount)) | |
, "guard" : currentGuard | |
} | |
)) | |
) | |
) | |
(defun check-reserved:string (account:string) | |
" Checks ACCOUNT for reserved name and returns type if \ | |
\ found or empty string. Reserved names start with a \ | |
\ single char and colon, e.g. 'c:foo', which would return 'c' as type." | |
(let ((pfx (take 2 account))) | |
(if (= ":" (take -1 pfx)) (take 1 pfx) ""))) | |
(defun enforce-reserved:bool (account:string guard:guard) | |
@doc "Enforce reserved account name protocols." | |
(if (validate-principal guard account) | |
true | |
(let ((r (check-reserved account))) | |
(if (= r "") | |
true | |
(if (= r "k") | |
(enforce false "Single-key account protocol violation") | |
(enforce false | |
(format "Reserved protocol guard violation: {}" [r])) | |
))))) | |
(defschema crosschain-schema | |
@doc " Schema for yielded value in cross-chain transfers " | |
receiver:string | |
receiver-guard:guard | |
amount:decimal | |
source-chain:string | |
) | |
(defpact transfer-crosschain:string | |
( sender:string | |
receiver:string | |
receiver-guard:guard | |
target-chain:string | |
amount:decimal ) | |
@model [ (property (> amount 0.0)) | |
(property (!= receiver "")) | |
(property (valid-account-id sender)) | |
(property (valid-account-id receiver)) | |
] | |
(step | |
(with-capability (TRANSFER_XCHAIN sender receiver amount target-chain) | |
(validate-account-id sender) | |
(validate-account-id receiver) | |
(enforce (!= "" target-chain) "empty target-chain") | |
(enforce (!= (at 'chain-id (chain-data)) target-chain) | |
"cannot run cross-chain transfers to the same chain") | |
(enforce (> amount 0.0) | |
"transfer quantity must be positive") | |
(enforce-unit amount) | |
;; Step 1 - debit sender account on current chain | |
(debit sender amount) | |
(emit-event (TRANSFER sender "" amount)) | |
(let | |
((crosschain-details:object{crosschain-schema} | |
{ "receiver" : receiver | |
, "receiver-guard" : receiver-guard | |
, "amount" : amount | |
, "source-chain" : (at 'chain-id (chain-data)) | |
} | |
)) | |
(yield crosschain-details target-chain) | |
) | |
) | |
) | |
(step | |
(resume | |
{ "receiver" := receiver | |
, "receiver-guard" := receiver-guard | |
, "amount" := amount | |
} | |
;; Step 2 - credit receiver account on target chain | |
(with-capability (CREDIT receiver) | |
(credit receiver receiver-guard amount) | |
) | |
) | |
) | |
) | |
(defun get-balance:decimal | |
( account:string ) | |
(at 'balance (read token-table account ['balance])) | |
) | |
(defun details:object{fungible-v2.account-details} | |
( account:string ) | |
(with-read token-table account | |
{ "balance" := balance | |
, "guard" := guard | |
} | |
{ "account" : account | |
, "balance" : balance | |
, "guard" : guard | |
} | |
) | |
) | |
(defun precision:integer | |
() | |
DECIMALS | |
) | |
(defun enforce-unit:bool | |
( amount:decimal ) | |
@doc " Enforce the minimum denomination for token transactions. " | |
(enforce | |
(= (floor amount DECIMALS) amount) | |
(format "Amount violates minimum denomination: {}" [amount]) | |
) | |
) | |
(defun create-account:string | |
( account:string | |
guard:guard ) | |
@doc " Create a new account. " | |
@model [ (property (valid-account-id account)) ] | |
(validate-account-id account) | |
(enforce-reserved account guard) | |
(insert token-table account | |
{ "balance" : 0.0 | |
, "guard" : guard | |
} | |
) | |
) | |
(defun rotate:string | |
( account:string | |
new-guard:guard ) | |
(with-read token-table account | |
{ "guard" := oldGuard } | |
(enforce-guard oldGuard) | |
(enforce-guard new-guard) | |
(update token-table account | |
{ "guard" : new-guard } | |
) | |
) | |
) | |
) | |
; ---------- | |
; INITIALIZATION | |
; ---------- | |
; | |
; At this point we've established our smart contract: we entered a namespace, | |
; defined a keyset, and implemented a module. Now it's time to initialize data. | |
; | |
; For a typical smart contract, that simply means creating any tables we defined | |
; in the contract. However, more complex contracts may perform other steps, such | |
; as calling functions from the module. | |
; | |
; Tables are defined in modules, but they are created after them. This ensures | |
; that the module can be redefined (ie. upgraded) later without necessarily | |
; having to re-create the table. | |
; | |
; Speaking of: it's a common practice to implement the initialization step as an | |
; 'if' statement that differentiates between an initial deployment and an | |
; upgrade. As with our keyset definition at the beginning of the contract, this | |
; can be done by sending an "upgrade" field with a boolean value as part of the | |
; transaction data | |
(if (read-msg "upgrade") | |
"Upgrade complete" | |
(create-table token-table)) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ node js/k-transfer-create.js k:sender01 33.3 | |
Request key: jDNrbe_RZ7-1vP28OTuMCJPMQMuA4qEyj4V_uwMjKLs | |
Transaction mined! | |
{ | |
"gas": 60000, | |
"result": { | |
"status": "failure", | |
"error": { | |
"callStack": [], | |
"type": "EvalError", | |
"message": "", | |
"info": "" | |
} | |
}, | |
"reqKey": "jDNrbe_RZ7-1vP28OTuMCJPMQMuA4qEyj4V_uwMjKLs", | |
"logs": "i-jRUqeK6E3Tv0QQblEuaRAbn6kVbRmfw90gM0THNaU", | |
"events": [ | |
{ | |
"params": [ | |
"sender00", | |
"k:f89ef46927f506c70b6a58fd322450a936311dc6ac91f4ec3d8ef949608dbf1f", | |
0.006 | |
], | |
"name": "TRANSFER", | |
"module": { | |
"namespace": null, | |
"name": "coin" | |
}, | |
"moduleHash": "rE7DU8jlQL9x_MPYuniZJf5ICBTAEHAIFQCB4blofP4" | |
} | |
], | |
"metaData": { | |
"blockTime": 1670184678668875, | |
"prevBlockHash": "2O9Vt3bTGLCHcrdnNQWIRBx5M0yC17PyGU1XIl8mpoY", | |
"blockHash": "chpXH0bH_cjgO8UIOWgaz7HqEiRmjQludlEWcdD6nHE", | |
"blockHeight": 6377 | |
}, | |
"continuation": null, | |
"txId": null | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment