Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FUSD #76

Closed
wants to merge 14 commits into from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
node_modules
dist
.DS_Store
settings.json
83 changes: 83 additions & 0 deletions docs/flow-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ sidebar_title: FLOW Token
description: How to mint FLOW Token
---

## FLOW

Some actions on the network will require an account to have a certain amount of FLOW (tokens) - transaction and storage fees, account creation, etc.
Framework provides a method to query FLOW balances with `getFlowBalance` and mint new tokens via `mintFlow`.

Expand Down Expand Up @@ -81,3 +83,84 @@ const main = async () => {

main();
```

## FUSD

You can also mint FUSD similarly to how you mint FLOW Token

## `getFUSDBalance(address)`

Returns current FUSD token balance of the specified account.

#### Arguments

| Name | Type | Description |
| --------- | --------------------------------------------------- | ------------------------------- |
| `address` | [Address](https://docs.onflow.org/fcl/reference/api/#address) | address of the account to check |

#### Returns

| Type | Description |
| -------- | ------------------------------------------------------ |
| `string` | UFix64 amount of FUSD tokens stored in account storage |

#### Usage

```javascript
import { init, emulator, getFUSDBalance } from "flow-js-testing";

const main = async () => {
const basePath = path.resolve(__dirname, "../cadence");
const port = 8080;

await init(basePath, { port });
await emulator.start(port);

const Alice = await getAccountAddress("Alice");

const [result, error] = await getFUSDBalance(Alice);
console.log(result, error);

await emulator.stop();
};

main();
```

## `mintFUSD(recipient, amount)`

Sends transaction to mint the specified amount of FUSD and send it to recipient.

> ⚠️ **Required:** Framework shall be initialized with `init` method for this method to work.

#### Arguments

| Name | Type | Description |
| ----------- | --------------------------------------------------- | ---------------------------------------------------------- |
| `recipient` | [Address](https://docs.onflow.org/fcl/reference/api/#address) | address of the account to check |
| `amount` | string | UFix64 amount of FUSD tokens to mint and send to recipient |

#### Usage

```javascript
import { init, emulator, mintFUSD } from "flow-js-testing";

const main = async () => {
const basePath = path.resolve(__dirname, "../cadence");
const port = 8080;

await init(basePath, { port });
await emulator.start(port);

const Alice = await getAccountAddress("Alice");
const amount = "42.0";
const [mintResult, error] = await mintFUSD(Alice);
console.log( mintResult, error );

await emulator.stop();
};

main();
```

Note: in order to transfer flow from one account to the other, you must either run `getFUSDBalance` or `mintFUSD` for both accounts. These functions create and store the FUSD vault. The storage path is `/storage/fusdVault` and the receiver is in `/public/fusdReceiver`
1 change: 1 addition & 0 deletions src/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const defaultsByName = {
FungibleToken: "0xee82856bf20e2aa6",
FlowFees: "0xe5a8b7f23e8b548f",
FlowStorageFees: "0xf8d6e0586b0a20c7",
FUSD: "0xf8d6e0586b0a20c7",
};

/**
Expand Down
16 changes: 3 additions & 13 deletions src/flow-token.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,15 @@
* limitations under the License.
*/

import { defaultsByName } from "./file";
import { replaceImportAddresses } from "./imports";
import { executeScript, sendTransaction } from "./interaction";
import { makeGetBalance, makeMintTransaction } from "./templates";
import { getTokenBalance, mintToken } from "./token";

/**
* Returns current FlowToken balance of account specified by address
* @param {string} address - address of account to check
* @returns {Promise<*>}
*/
export const getFlowBalance = async (address) => {
const raw = await makeGetBalance("FlowToken");
const code = replaceImportAddresses(raw, defaultsByName);
const args = [address];

return executeScript({ code, args });
return await getTokenBalance("FlowToken", address);
};

/**
Expand All @@ -42,8 +35,5 @@ export const getFlowBalance = async (address) => {
* @returns {Promise<*>}
*/
export const mintFlow = async (recipient, amount) => {
const raw = await makeMintTransaction("FlowToken");
const code = replaceImportAddresses(raw, defaultsByName);
const args = [recipient, amount];
return sendTransaction({ code, args });
return await mintToken("FlowToken", recipient, amount);
};
41 changes: 41 additions & 0 deletions src/fusd-token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Flow JS Testing
*
* Copyright 2020-2021 Dapper Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { getTokenBalance, mintNoLimitToken, setupVault } from "./token";

/**
* Returns current FUSD balance of account specified by address
* @param {string} address - address of account to check
* @returns {Promise<*>}
*/
export const getFUSDBalance = async (address) => {
await setupVault("FUSD", address);
return await getTokenBalance("FUSD", address);
};

/**
* Sends transaction to mint specified amount of FUSD and send it to recipient.
* Returns result of the transaction.
* @param {string} recipient - address of recipient account
* @param {string} amount - amount to mint and send
* @returns {Promise<*>}
*/
export const mintFUSD = async (recipient, amount) => {
await setupVault("FUSD", recipient);
return await mintNoLimitToken("FUSD", recipient, amount);
};
2 changes: 2 additions & 0 deletions src/generated/transactions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { registerContractTemplate, registerContract } from "./registerContract";
import { scratchTemplate, scratch } from "./scratch";
import { setBlockOffsetTemplate, setBlockOffset } from "./setBlockOffset";
import { updateContractTemplate, updateContract } from "./updateContract";
import { setupVaultTemplate } from "./setupVault";

export default {
createAccountTemplate,
Expand All @@ -22,6 +23,7 @@ export default {
scratch,
setBlockOffsetTemplate,
setBlockOffset,
setupVaultTemplate,
updateContractTemplate,
updateContract,
};
53 changes: 53 additions & 0 deletions src/generated/transactions/setupVault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// my guess is that this template should be generated. Not sure how to generate
// the transaction file I want, so just creating it here

import {
getEnvironment,
replaceImportAddresses,
reportMissingImports,
} from 'flow-cadut'

export const CODE = `
import FungibleToken from 0xFungibleToken
import ExampleToken from 0xTOKENADDRESS

transaction {
prepare(acct: AuthAccount) {
// if the vault already exists, just exit
let existingVault = acct.borrow<&ExampleToken.Vault>(from: /storage/exampleTokenVault)
if existingVault != nil {
return
}
// Create a new empty Vault object
let vaultA <- ExampleToken.createEmptyVault()

// Store the vault in the account storage
acct.save<@ExampleToken.Vault>(<-vaultA, to: /storage/exampleTokenVault)

// Create a public Receiver capability to the Vault
let ReceiverRef = acct.link<&ExampleToken.Vault{FungibleToken.Receiver}>(
/public/exampleTokenReceiver, target: /storage/exampleTokenVault)

// Create a balance capability
let BalanceRef = acct.link<&ExampleToken.Vault{FungibleToken.Balance}>(
/public/exampleTokenBalance, target: /storage/exampleTokenVault)
}
}
`;

/**
* Method to generate cadence code for mintTokens transaction
* @param {Object.<string, string>} addressMap - contract name as a key and address where it's deployed as value
*/
export const setupVaultTemplate = async (addressMap = {}) => {
const envMap = await getEnvironment();
const fullMap = {
...envMap,
...addressMap,
};

// If there are any missing imports in fullMap it will be reported via console
reportMissingImports(CODE, fullMap, `setupVault =>`)

return replaceImportAddresses(CODE, fullMap);
};
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { set, getConfigValue } from "./config";
export { getTemplate, getScriptCode, getContractCode, getTransactionCode } from "./file";
export { sendTransaction, executeScript } from "./interaction";
export { getFlowBalance, mintFlow } from "./flow-token";
export { getFUSDBalance, mintFUSD } from "./fusd-token";
export { deployContract, deployContractByName } from "./deploy-code";
export { getAccountAddress } from "./account";
export { getServiceAddress, getBlockOffset, setBlockOffset } from "./manager";
Expand Down
22 changes: 17 additions & 5 deletions src/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@ import { defaultsByName } from "./file";

const FlowTokenMap = { ExampleToken: defaultsByName.FlowToken };

const lowerFirst = (name) => {
return name[0].toLowerCase() + name.slice(1);
};
// sets the starting contiguous uppercase characters to lowercase
// e.g. FlowToken -> flowToken, FUSD -> fusd
const lowerUpperStart = (name) => {
const pattern = /^([A-Z]+)/
return name.replace(pattern, (_, uppers) => uppers.toLowerCase())
}

export const makeMintTransaction = async (name) => {
const code = await registry.transactions.mintTokensTemplate(FlowTokenMap);
const pattern = /(ExampleToken)/gi;

return code.replace(pattern, (match) => {
return match === "ExampleToken" ? name : lowerFirst(name);
return match === "ExampleToken" ? name : lowerUpperStart(name);
});
};

Expand All @@ -39,6 +42,15 @@ export const makeGetBalance = async (name) => {
const pattern = /(ExampleToken)/gi;

return code.replace(pattern, (match) => {
return match === "ExampleToken" ? name : lowerFirst(name);
return match === "ExampleToken" ? name : lowerUpperStart(name);
});
};

export const makeSetupVaultTransaction = async (name) => {
const code = await registry.transactions.setupVaultTemplate(FlowTokenMap);
const pattern = /(ExampleToken)/gi;

return code.replace(pattern, (match) => {
return match === "ExampleToken" ? name : lowerUpperStart(name);
});
}
76 changes: 76 additions & 0 deletions src/token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { defaultsByName } from "./file";
import { replaceImportAddresses } from "./imports";
import { executeScript, sendTransaction } from "./interaction";
import { makeGetBalance, makeMintTransaction, makeSetupVaultTransaction } from "./templates";

/**
* Returns current FlowToken balance of account specified by address
* @param {string} tokenName - name of the token to check
* @param {string} address - address of account to check
* @returns {Promise<*>}
*/
export const getTokenBalance = async (tokenName, address, addressMap = {}) => {
const raw = await makeGetBalance(tokenName);
const code = replaceImportAddresses(raw, {
...defaultsByName,
...addressMap
});
const args = [address];

return executeScript({ code, args });
};

/**
* Sends transaction to mint specified amount of FlowToken and send it to recipient.
* Returns result of the transaction.
* @param {string} tokenName - name of the token to mint
* @param {string} recipient - address of recipient account
* @param {string} amount - amount to mint and send
* @returns {Promise<*>}
*/
export const mintToken = async (tokenName, recipient, amount, addressMap = {}) => {
const raw = await makeMintTransaction(tokenName);
const code = replaceImportAddresses(raw, {
...defaultsByName,
...addressMap
});
const args = [recipient, amount];
return sendTransaction({ code, args });
};

/**
* Sends transaction to mint specified amount of custom token and send it to recipient.
* Returns result of the transaction.
* @param {string} tokenName - name of the token to mint
* @param {string} recipient - address of recipient account
* @param {string} amount - amount to mint and send
* @returns {Promise<*>}
*/
export const mintNoLimitToken = async (tokenName, recipient, amount, addressMap = {}) => {
const raw = await makeMintTransaction(tokenName);
const code = replaceImportAddresses(raw, {
...defaultsByName,
...addressMap
}).replace('self.tokenAdmin.createNewMinter(allowedAmount: amount)',
'self.tokenAdmin.createNewMinter()');

const args = [recipient, amount];
return sendTransaction({ code, args });
};

/**
* Creates a token vault and saves it to the account's storage with canonical naming
* @param {string} tokenName - name of the token vault to set up (omit 'Vault')
* @param {string} account address of the account
* @param {object} addressMap additional contract address mappings
* @returns {Promise<*>}
*/
export const setupVault = async (tokenName, account, addressMap = {}) => {
const raw = await makeSetupVaultTransaction(tokenName);
const code = replaceImportAddresses(raw, {
...defaultsByName,
...addressMap
});
const signers = [account];
return sendTransaction({ code, signers });
}
Loading