diff --git a/.gitignore b/.gitignore index c504d501..d08af3e7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,9 @@ **/bin/ */.gradle/ */.idea/* -*/.vscode/* +**/.vscode/* **/out/ **/logs/ +**/node_modules/ corda-nodeinfo/corda-nodeinfo.iml *~* diff --git a/Accounts/README.md b/Accounts/README.md index ab4c69b3..f304320a 100644 --- a/Accounts/README.md +++ b/Accounts/README.md @@ -1,14 +1,21 @@ -## samples-java/Accounts +## Accounts CorDapp Samples -This folder features Corda Accounts sample projects. +This folder features Corda Accounts sample projects. Learn more about [Accounts](https://training.corda.net/libraries/accounts-lib/). ### [Supply Chain](./supplychain): -This CorDapp mimics a supply chain transaction, where the deal is incorporated among different teams in the companies on both side of the trade. + +This CorDapp mimics a supply chain transaction, where the deal is incorporated among different teams in the companies on both side of the trade. +

+ Corda +

### [Tic Tac Thor](./tictacthor): -This CorDapp recreates the game of Tic Tac Toe via Corda. It primarilly demonstrates how you can have linear state transactions between cross-node accounts. +This CorDapp recreates the game of Tic Tac Toe via Corda. It primarily demonstrates how you can have LinearState transactions between cross-node accounts. +

+ Corda +

-### [Worldcup Ticket Booking](./worldcupticketbooking): -This CorDapp demonstrates an ticket booking system with a collaboration of Corda Account Libray and TokenSDK. +### [World Cup Ticket Booking](./worldcupticketbooking): +This sample shows you how to integrate accounts and tokens. This sample talks about a scenario where typically when the Cricket season starts, BCCI (Board of Control for Cricket) starts selling tickets. As of now there are multiple dealers whom the BCCI issues tickets and further these dealers sell tickets to their client. We are trying to simulate similar functionality maintaining the entire issuance and selling of the tickets on Corda Platform. \ No newline at end of file diff --git a/Accounts/constants.properties b/Accounts/constants.properties index 0b0f28b2..adcdb361 100644 --- a/Accounts/constants.properties +++ b/Accounts/constants.properties @@ -1,12 +1,12 @@ cordaReleaseGroup=net.corda cordaCoreReleaseGroup=net.corda -cordaVersion=4.5-RC02 -cordaCoreVersion=4.5-RC02 -gradlePluginsVersion=5.0.10 +cordaVersion=4.10 +cordaCoreVersion=4.10 +gradlePluginsVersion=5.0.12 kotlinVersion=1.2.71 junitVersion=4.12 quasarVersion=0.7.10 -log4jVersion =2.11.2 -platformVersion=5 +log4jVersion =2.17.1 +platformVersion=12 slf4jVersion=1.7.25 nettyVersion=4.1.22.Final diff --git a/Accounts/supplychain/Business Flow.png b/Accounts/supplychain/Business_Flow.png similarity index 100% rename from Accounts/supplychain/Business Flow.png rename to Accounts/supplychain/Business_Flow.png diff --git a/Accounts/supplychain/README.md b/Accounts/supplychain/README.md index a3cde403..87168170 100644 --- a/Accounts/supplychain/README.md +++ b/Accounts/supplychain/README.md @@ -1,18 +1,24 @@ # Accounts_SupplyChain -For More information regarding the Accounts Library, please read at: https://github.com/corda/accounts/blob/master/docs.md +For more information regarding the Accounts Library, please read at: https://github.com/corda/accounts/blob/master/docs.md This sample describes a mock/simple supply chain business flow.

Corda

-From the above chart, you can see the flow is going back and forth between different parties' accounts. Please follow the instruction below to experience the Accounts library. -# Setting up +From the above chart, you can see the flow is going back and forth between different parties' accounts. Please follow the instruction below to experience the [Accounts](https://training.corda.net/libraries/accounts-lib/) library. + + +## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). + +## Runnning the nodes Go into the project directory and build the project ``` -./gradlew clean deployNodes +./gradlew clean build deployNodes ``` Run the project ``` @@ -20,7 +26,7 @@ Run the project ``` Now, you should have four Corda terminals opened automatically. -# Shell Instructions (Part 1) - Creating & Sharing Accounts +## Shell Instructions (Part 1) - Creating & Sharing Accounts Go to the Buyer's node terminal and paste in the following code: (You can select all 7 lines and copy to the terminal all at once) ``` flow start CreateNewAccount acctName: BuyerProcurement @@ -32,7 +38,7 @@ flow start ShareAccountTo acctNameShared: BuyerFinance, shareTo: Seller flow start ShareAccountTo acctNameShared: BuyerWarehouse, shareTo: ShippingCo flow start ShareAccountTo acctNameShared: BuyerWarehouse, shareTo: Seller ``` -This is creating 3 accounts under Buyer's node and sharing with their specific conterpartie's node or account. +This is creating 3 accounts under Buyer's node and sharing with their specific counterparty's node or account. Go to the Seller's node terminal and paste in the following code: ``` @@ -44,14 +50,14 @@ flow start ShareAccountTo acctNameShared: SellerSales, shareTo: Buyer flow start ShareAccountTo acctNameShared: SellerFinance, shareTo: Buyer flow start ShareAccountTo acctNameShared: SellerInventory, shareTo: ShippingCo ``` -This is creating 3 accounts under Seller's node and sharing with their specific conterpartie's node or account. +This is creating 3 accounts under Seller's node and sharing with their specific counterparty's node or account. -[Optional]: You can run a vaultQuery to see the accoutnInfo that been stored at each node by using: +[Optional]: You can run a vaultQuery to see the [AccountInfo](https://training.corda.net/libraries/accounts-lib/#design) that been stored at each node by using: ``` run vaultQuery contractStateType: com.r3.corda.lib.accounts.contracts.states.AccountInfo ``` -# Shell Instructions (Part 2) - Executing Business Flows -## Step 1: Seller's sales team send inovice of $500 to Buyer's procurement team +## Shell Instructions (Part 2) - Executing Business Flows +### Step 1: Seller's sales team send invoice for $500 to Buyer's procurement team navigate to Seller's node terminal and run ``` flow start SendInvoice whoAmI: SellerSales, whereTo: BuyerProcurement, amount: 500 @@ -62,42 +68,42 @@ flow start ViewInboxByAccount acctname: BuyerProcurement ``` You see that the invoice state amount 500 is returned. You can also replace the BuyerProcurement with BuyerWarehouse to see that the non-relevant accounts has no visiblity about the invoice state. -## Step 2: Buyer's procurement team will send an internal message to Buyer's Buyer's finance team +### Step 2: Buyer's procurement team will send an internal message to Buyer's Buyer's finance team Navigate to Buyer's node terminal and type in: ``` flow start InternalMessage fromWho: BuyerProcurement, whereTo: BuyerFinance, message: Send 500 to SellerFinance ``` [Optional verification]: run ```flow start ViewInboxByAccount acctname: BuyerFinance``` at Buyer' node terminal -## Step 3: Buyer's finance team send a payment to Seller's finance team -Navigatie to Buyer's node terminal and type in: +### Step 3: Buyer's finance team send a payment to Seller's finance team +Navigate to Buyer's node terminal and type in: ``` flow start SendPayment whoAmI: BuyerFinance, whereTo: SellerFinance, amount: 500 ``` [Optional verification]: run ```flow start ViewInboxByAccount acctname: SellerFinance``` at Seller's node terminal -## Step 4: Seller's finance team send an internal message to Seller's inventory team to instruct them to send the cargo +### Step 4: Seller's finance team send an internal message to Seller's inventory team to instruct them to send the cargo Navigate to Seller's node terminal and type in ``` flow start InternalMessage fromWho: SellerFinance, whereTo: SellerInventory, message: send Cargo to Buyer ``` [Optional verification]: run ```flow start ViewInboxByAccount acctname: SellerInventory``` at Seller's node terminal -## step 5: Seller's inventory team send a shipping work order for shipping company +### step 5: Seller's inventory team send a shipping work order for shipping company Navigate to Seller's node terminal and type in ``` flow start SendShippingRequest whoAmI: SellerInventory, whereTo: BuyerWarehouse, shipper: ShippingCo, Cargo: 10 boxes of Books ``` -[Optional verification]: run ```run vaultQuery contractStateType: com.accounts_SupplyChain.states.ShippingRequestState``` at ShippingCo's node terminal +[Optional verification]: run ```run vaultQuery contractStateType: ShippingRequestState``` at ShippingCo's node terminal -## Step 6: Shipping company sends the cargo to Buyer's warehouse +### Step 6: Shipping company sends the cargo to Buyer's warehouse Navigate to ShippingCo's node terminal and type in ``` flow start SendCargo pickupFrom: SellerInventory, shipTo: BuyerWarehouse, cargo: Books ``` [Optional verification]: run ```flow start ViewInboxByAccount acctname: BuyerWarehouse``` at Buyer's node terminal -## Now, the entire business chain is completed. +### Now, the entire business chain is completed. diff --git a/Accounts/supplychain/build.gradle b/Accounts/supplychain/build.gradle index 3efc9205..4d70ff49 100644 --- a/Accounts/supplychain/build.gradle +++ b/Accounts/supplychain/build.gradle @@ -3,7 +3,6 @@ buildscript { file("$projectDir/../constants.properties").withInputStream { constants.load(it) } ext { - corda_release_group = constants.getProperty("cordaReleaseGroup") corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") corda_release_version = constants.getProperty("cordaVersion") @@ -15,9 +14,11 @@ buildscript { log4j_version = constants.getProperty("log4jVersion") slf4j_version = constants.getProperty("slf4jVersion") corda_platform_version = constants.getProperty("platformVersion").toInteger() - //accounts - accounts_release_version = '1.0' + + //account accounts_release_group = 'com.r3.corda.lib.accounts' + accounts_release_version = '1.0' + //CI confidential_id_release_group = "com.r3.corda.lib.ci" confidential_id_release_version = "1.0" } @@ -25,8 +26,7 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -42,12 +42,14 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev' } - maven { url "https://repo.gradle.org/gradle/libs-releases-local" } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://jitpack.io' } + //SDK lib + maven { url 'https://download.corda.net/maven/corda-lib' } + //Gradle Plugins + maven { url 'https://repo.gradle.org/gradle/libs-releases' } } tasks.withType(JavaCompile) { @@ -62,9 +64,6 @@ allprojects { } } - - - apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'net.corda.plugins.cordformation' apply plugin: 'net.corda.plugins.quasar-utils' @@ -90,6 +89,8 @@ dependencies { cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaDriver "net.corda:corda-shell:4.10" + //accounts cordapp "$accounts_release_group:accounts-contracts:$accounts_release_version" @@ -97,29 +98,6 @@ dependencies { cordapp "$accounts_release_group:accounts-workflows:$accounts_release_version" } -cordapp { - info { - name "CorDapp supplychain" - vendor "Corda Open Source" - targetPlatformVersion corda_platform_version - minimumPlatformVersion corda_platform_version - } -} - -task ganache { - subprojects { - if (it.project.name != "clients") { - dependsOn jar - doLast { - copy { - from "${buildDir}/libs" - into "${rootDir}/build/libs" - } - } - } - } -} - task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { nodeDefaults { projectCordapp { @@ -131,6 +109,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { cordapp project(":contracts") cordapp project(":workflows") + runSchemaMigration = true } node { name "O=Notary,L=London,C=GB" diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/InvoiceStateContract.java b/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/InvoiceStateContract.java deleted file mode 100644 index e0f94689..00000000 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/InvoiceStateContract.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.supplychain.contracts; - -import net.corda.core.contracts.CommandData; -import net.corda.core.contracts.Contract; -import net.corda.core.transactions.LedgerTransaction; - -// ************ -// * Contract * -// ************ -public class InvoiceStateContract implements Contract { - // This is used to identify our contract when building a transaction. - public static final String ID = "com.supplychain.contracts.InvoiceStateContract"; - - // A transaction is valid if the verify() function of the contract of all the transaction's input and output states - // does not throw an exception. - @Override - public void verify(LedgerTransaction tx) {} - - // Used to indicate the transaction's intent. - public interface Commands extends CommandData { - class Create implements Commands {} - } -} \ No newline at end of file diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/InvoiceState.java b/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/InvoiceState.java deleted file mode 100644 index 164111f6..00000000 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/InvoiceState.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.supplychain.states; - -import com.supplychain.contracts.InvoiceStateContract; -import net.corda.core.contracts.BelongsToContract; -import net.corda.core.contracts.ContractState; -import net.corda.core.identity.AbstractParty; -import net.corda.core.identity.AnonymousParty; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -// ********* -// * State * -// ********* -@BelongsToContract(InvoiceStateContract.class) -public class InvoiceState implements ContractState { - - private int amount; - private AnonymousParty sender; - private AnonymousParty recipient; - private UUID invoiceID; - private List participants; - - public InvoiceState(int amount, AnonymousParty sender, AnonymousParty recipient, UUID invoiceID) { - this.amount = amount; - this.sender = sender; - this.recipient = recipient; - this.invoiceID = invoiceID; - this.participants = new ArrayList(); - participants.add(recipient); - participants.add(sender); - } - - public int getAmount() { - return amount; - } - - public void setAmount(int amount) { - this.amount = amount; - } - - public AnonymousParty getSender() { - return sender; - } - - public void setSender(AnonymousParty sender) { - this.sender = sender; - } - - public AnonymousParty getRecipient() { - return recipient; - } - - public void setRecipient(AnonymousParty recipient) { - this.recipient = recipient; - } - - public UUID getInvoiceID() { - return invoiceID; - } - - public void setInvoiceID(UUID invoiceID) { - this.invoiceID = invoiceID; - } - - @Override - public List getParticipants() { - return this.participants; - } -} \ No newline at end of file diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/CargoStateContract.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/CargoStateContract.java similarity index 82% rename from Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/CargoStateContract.java rename to Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/CargoStateContract.java index 741765fe..6a2a704c 100644 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/CargoStateContract.java +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/CargoStateContract.java @@ -1,4 +1,4 @@ -package com.supplychain.contracts; +package net.corda.samples.supplychain.contracts; import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; @@ -9,7 +9,7 @@ // ************ public class CargoStateContract implements Contract { // This is used to identify our contract when building a transaction. - public static final String ID = "com.supplychain.contracts.CargoStateContract"; + public static final String ID = "net.corda.samples.supplychain.contracts.CargoStateContract"; // A transaction is valid if the verify() function of the contract of all the transaction's input and output states // does not throw an exception. diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/InternalMessageStateContract.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/InternalMessageStateContract.java similarity index 81% rename from Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/InternalMessageStateContract.java rename to Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/InternalMessageStateContract.java index 6550d8a6..bc99bb7b 100644 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/InternalMessageStateContract.java +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/InternalMessageStateContract.java @@ -1,4 +1,4 @@ -package com.supplychain.contracts; +package net.corda.samples.supplychain.contracts; import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; @@ -9,7 +9,7 @@ // ************ public class InternalMessageStateContract implements Contract { // This is used to identify our contract when building a transaction. - public static final String ID = "com.supplychain.contracts.InternalMessageStateContract"; + public static final String ID = "net.corda.samples.supplychain.contracts.InternalMessageStateContract"; // A transaction is valid if the verify() function of the contract of all the transaction's input and output states // does not throw an exception. diff --git a/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/InvoiceStateContract.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/InvoiceStateContract.java new file mode 100644 index 00000000..4336423f --- /dev/null +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/InvoiceStateContract.java @@ -0,0 +1,56 @@ +package net.corda.samples.supplychain.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.ContractState; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.supplychain.states.InvoiceState; + +import java.util.List; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class InvoiceStateContract implements Contract { + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.supplychain.contracts.InvoiceStateContract"; + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(LedgerTransaction tx) throws IllegalArgumentException { + /* + * We can use the requireSingleCommand function to extract command data from transaction. + * However, it is possible to have multiple commands in a signle transaction. + */ + final CommandWithParties command = requireSingleCommand(tx.getCommands(), InvoiceStateContract.Commands.class); + + List inputs = tx.getInputStates(); + List outputs = tx.getOutputStates(); + + if (command.getValue() instanceof InvoiceStateContract.Commands.Create) { + + // Using Corda DSL function requireThat to replicate conditions-checks + requireThat(require -> { + require.using("No inputs should be consumed when creating a new Invoice State.", inputs.isEmpty()); + require.using("Transaction must have exactly one output.", outputs.size() == 1); + InvoiceState output = (InvoiceState) outputs.get(0); + require.using("Invoice amount must be a valid number (Greater than zero)", output.getAmount() > 0); + return null; + }); + + } else { + throw new IllegalArgumentException("Command not found!"); + } + + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class Create implements Commands {} + } +} \ No newline at end of file diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/PaymentStateContract.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/PaymentStateContract.java similarity index 82% rename from Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/PaymentStateContract.java rename to Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/PaymentStateContract.java index acbe7986..81a7b16d 100644 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/PaymentStateContract.java +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/PaymentStateContract.java @@ -1,4 +1,4 @@ -package com.supplychain.contracts; +package net.corda.samples.supplychain.contracts; import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; @@ -9,7 +9,7 @@ // ************ public class PaymentStateContract implements Contract { // This is used to identify our contract when building a transaction. - public static final String ID = "com.supplychain.contracts.PaymentStateContract"; + public static final String ID = "net.corda.samples.supplychain.contracts.PaymentStateContract"; // A transaction is valid if the verify() function of the contract of all the transaction's input and output states // does not throw an exception. diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/ShippingRequestStateContract.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/ShippingRequestStateContract.java similarity index 81% rename from Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/ShippingRequestStateContract.java rename to Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/ShippingRequestStateContract.java index e9dcb906..ab0c0cfa 100644 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/contracts/ShippingRequestStateContract.java +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/contracts/ShippingRequestStateContract.java @@ -1,4 +1,4 @@ -package com.supplychain.contracts; +package net.corda.samples.supplychain.contracts; import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; @@ -9,7 +9,7 @@ // ************ public class ShippingRequestStateContract implements Contract { // This is used to identify our contract when building a transaction. - public static final String ID = "com.supplychain.contracts.ShippingRequestStateContract"; + public static final String ID = "net.corda.samples.supplychain.contracts.ShippingRequestStateContract"; // A transaction is valid if the verify() function of the contract of all the transaction's input and output states // does not throw an exception. diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/CargoState.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/CargoState.java similarity index 93% rename from Accounts/supplychain/contracts/src/main/java/com/supplychain/states/CargoState.java rename to Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/CargoState.java index 13e71b3f..38ac721a 100644 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/CargoState.java +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/CargoState.java @@ -1,6 +1,6 @@ -package com.supplychain.states; +package net.corda.samples.supplychain.states; -import com.supplychain.contracts.CargoStateContract; +import net.corda.samples.supplychain.contracts.CargoStateContract; import net.corda.core.contracts.BelongsToContract; import net.corda.core.contracts.ContractState; import net.corda.core.identity.AbstractParty; diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/InternalMessageState.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/InternalMessageState.java similarity index 91% rename from Accounts/supplychain/contracts/src/main/java/com/supplychain/states/InternalMessageState.java rename to Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/InternalMessageState.java index 29c5713e..5d7a34a3 100644 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/InternalMessageState.java +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/InternalMessageState.java @@ -1,6 +1,6 @@ -package com.supplychain.states; +package net.corda.samples.supplychain.states; -import com.supplychain.contracts.InternalMessageStateContract; +import net.corda.samples.supplychain.contracts.InternalMessageStateContract; import net.corda.core.contracts.BelongsToContract; import net.corda.core.contracts.ContractState; import net.corda.core.identity.AbstractParty; diff --git a/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/InvoiceState.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/InvoiceState.java new file mode 100644 index 00000000..699cc532 --- /dev/null +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/InvoiceState.java @@ -0,0 +1,71 @@ +package net.corda.samples.supplychain.states; + +import net.corda.samples.supplychain.contracts.InvoiceStateContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.AnonymousParty; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +// ********* +// * State * +// ********* +@BelongsToContract(InvoiceStateContract.class) +public class InvoiceState implements ContractState { + + private int amount; + private AnonymousParty sender; + private AnonymousParty recipient; + private UUID invoiceID; + private List participants; + + public InvoiceState(int amount, AnonymousParty sender, AnonymousParty recipient, UUID invoiceID) { + this.amount = amount; + this.sender = sender; + this.recipient = recipient; + this.invoiceID = invoiceID; + this.participants = new ArrayList(); + participants.add(recipient); + participants.add(sender); + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + public AnonymousParty getSender() { + return sender; + } + + public void setSender(AnonymousParty sender) { + this.sender = sender; + } + + public AnonymousParty getRecipient() { + return recipient; + } + + public void setRecipient(AnonymousParty recipient) { + this.recipient = recipient; + } + + public UUID getInvoiceID() { + return invoiceID; + } + + public void setInvoiceID(UUID invoiceID) { + this.invoiceID = invoiceID; + } + + @Override + public List getParticipants() { + return this.participants; + } +} \ No newline at end of file diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/PaymentState.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/PaymentState.java similarity index 92% rename from Accounts/supplychain/contracts/src/main/java/com/supplychain/states/PaymentState.java rename to Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/PaymentState.java index 9e75a41b..00d3531e 100644 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/PaymentState.java +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/PaymentState.java @@ -1,6 +1,6 @@ -package com.supplychain.states; +package net.corda.samples.supplychain.states; -import com.supplychain.contracts.PaymentStateContract; +import net.corda.samples.supplychain.contracts.PaymentStateContract; import net.corda.core.contracts.BelongsToContract; import net.corda.core.contracts.ContractState; import net.corda.core.identity.AbstractParty; diff --git a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/ShippingRequestState.java b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/ShippingRequestState.java similarity index 93% rename from Accounts/supplychain/contracts/src/main/java/com/supplychain/states/ShippingRequestState.java rename to Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/ShippingRequestState.java index 3ff5ee22..ebfd2cf1 100644 --- a/Accounts/supplychain/contracts/src/main/java/com/supplychain/states/ShippingRequestState.java +++ b/Accounts/supplychain/contracts/src/main/java/net/corda/samples/supplychain/states/ShippingRequestState.java @@ -1,6 +1,6 @@ -package com.supplychain.states; +package net.corda.samples.supplychain.states; -import com.supplychain.contracts.ShippingRequestStateContract; +import net.corda.samples.supplychain.contracts.ShippingRequestStateContract; import net.corda.core.contracts.BelongsToContract; import net.corda.core.contracts.ContractState; import net.corda.core.identity.AbstractParty; diff --git a/Accounts/supplychain/contracts/src/test/java/com/supplychain/contracts/ContractTests.java b/Accounts/supplychain/contracts/src/test/java/com/supplychain/contracts/ContractTests.java deleted file mode 100644 index 63beb3ba..00000000 --- a/Accounts/supplychain/contracts/src/test/java/com/supplychain/contracts/ContractTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.supplychain.contracts; - -import net.corda.testing.node.MockServices; -import org.junit.Test; - -public class ContractTests { - private final MockServices ledgerServices = new MockServices(); - - @Test - public void dummyTest() { - - } -} \ No newline at end of file diff --git a/Accounts/supplychain/contracts/src/test/java/net/corda/samples/supplychain/contracts/ContractTests.java b/Accounts/supplychain/contracts/src/test/java/net/corda/samples/supplychain/contracts/ContractTests.java new file mode 100644 index 00000000..f40dd8f7 --- /dev/null +++ b/Accounts/supplychain/contracts/src/test/java/net/corda/samples/supplychain/contracts/ContractTests.java @@ -0,0 +1,38 @@ +package net.corda.samples.supplychain.contracts; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.supplychain.states.InvoiceState; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.UUID; + +import static net.corda.testing.node.NodeTestUtils.ledger; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + TestIdentity Operator = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + TestIdentity Operator2 = new TestIdentity(new CordaX500Name("Bob", "TestLand", "US")); + + @Test + public void InvoiceAmountMustBeGreaterThanZero() { + InvoiceState tokenPass = new InvoiceState(10, new AnonymousParty(Operator.getPublicKey()),new AnonymousParty(Operator2.getPublicKey()),new UniqueIdentifier().getId()); + InvoiceState tokenfail = new InvoiceState(-1, new AnonymousParty(Operator.getPublicKey()),new AnonymousParty(Operator2.getPublicKey()),new UniqueIdentifier().getId()); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.output(InvoiceStateContract.ID, tokenfail); + tx.command(Operator.getPublicKey(), new InvoiceStateContract.Commands.Create()); // Wrong type. + return tx.fails(); + }); + l.transaction(tx -> { + tx.output(InvoiceStateContract.ID, tokenPass); + tx.command(Operator.getPublicKey(), new InvoiceStateContract.Commands.Create()); // Wrong type. + return tx.verifies(); + }); + return null; + }); + } +} \ No newline at end of file diff --git a/Accounts/supplychain/contracts/src/test/java/net/corda/samples/supplychain/contracts/StateTests.java b/Accounts/supplychain/contracts/src/test/java/net/corda/samples/supplychain/contracts/StateTests.java new file mode 100644 index 00000000..5031f6be --- /dev/null +++ b/Accounts/supplychain/contracts/src/test/java/net/corda/samples/supplychain/contracts/StateTests.java @@ -0,0 +1,17 @@ +package net.corda.samples.supplychain.contracts; + +import net.corda.samples.supplychain.states.InvoiceState; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class StateTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void hasAmountFieldOfCorrectType() throws NoSuchFieldException { + // Does the message field exist? + InvoiceState.class.getDeclaredField("amount"); + // Is the message field of the correct type? + assert(InvoiceState.class.getDeclaredField("amount").getType().equals(int.class)); + } +} \ No newline at end of file diff --git a/Accounts/supplychain/gradle.properties b/Accounts/supplychain/gradle.properties index 834d4552..6b3850f8 100644 --- a/Accounts/supplychain/gradle.properties +++ b/Accounts/supplychain/gradle.properties @@ -1,3 +1,3 @@ -name=Test +name=Supplychain Cordapp group=com.supplychain -version=0.1 \ No newline at end of file +version=1.0 \ No newline at end of file diff --git a/Accounts/supplychain/gradle/wrapper/gradle-wrapper.properties b/Accounts/supplychain/gradle/wrapper/gradle-wrapper.properties index 2a2a3a8e..ae01072d 100644 --- a/Accounts/supplychain/gradle/wrapper/gradle-wrapper.properties +++ b/Accounts/supplychain/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/Accounts/supplychain/repositories.gradle b/Accounts/supplychain/repositories.gradle index 2874c2ab..9797c0ea 100644 --- a/Accounts/supplychain/repositories.gradle +++ b/Accounts/supplychain/repositories.gradle @@ -1,8 +1,7 @@ repositories { mavenLocal() mavenCentral() - jcenter() maven { url 'https://jitpack.io' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Accounts/supplychain/workflows/src/integrationTest/java/com/supplychain/DriverBasedTest.java b/Accounts/supplychain/workflows/src/integrationTest/java/com/supplychain/DriverBasedTest.java deleted file mode 100644 index a6bc2380..00000000 --- a/Accounts/supplychain/workflows/src/integrationTest/java/com/supplychain/DriverBasedTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.supplychain; - -import com.google.common.collect.ImmutableList; -import net.corda.core.concurrent.CordaFuture; -import net.corda.core.identity.CordaX500Name; -import net.corda.testing.core.TestIdentity; -import net.corda.testing.driver.DriverParameters; -import net.corda.testing.driver.NodeHandle; -import net.corda.testing.driver.NodeParameters; -import org.junit.Test; - -import java.util.List; - -import static net.corda.testing.driver.Driver.driver; -import static org.junit.Assert.assertEquals; - -public class DriverBasedTest { - private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); - private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); - - @Test - public void nodeTest() { - driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { - // Start a pair of nodes and wait for them both to be ready. - List> handleFutures = ImmutableList.of( - dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), - dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) - ); - - try { - NodeHandle partyAHandle = handleFutures.get(0).get(); - NodeHandle partyBHandle = handleFutures.get(1).get(); - - // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the - // nodes have started and can communicate. - - // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault - // and other important metrics to ensure that your CorDapp is working as intended. - assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); - assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); - } catch (Exception e) { - throw new RuntimeException("Caught exception during test: ", e); - } - - return null; - }); - } -} \ No newline at end of file diff --git a/Accounts/supplychain/workflows/src/integrationTest/java/net/corda/samples/supplychain/DriverBasedTest.java b/Accounts/supplychain/workflows/src/integrationTest/java/net/corda/samples/supplychain/DriverBasedTest.java new file mode 100644 index 00000000..0a6262ef --- /dev/null +++ b/Accounts/supplychain/workflows/src/integrationTest/java/net/corda/samples/supplychain/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.supplychain; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/CreateNewAccount.java b/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/CreateNewAccount.java deleted file mode 100644 index eb3d275c..00000000 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/CreateNewAccount.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.supplychain.accountUtilities; - -import com.r3.corda.lib.accounts.contracts.states.AccountInfo; -import com.r3.corda.lib.accounts.workflows.*; -import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; -import net.corda.core.contracts.StateAndRef; -import net.corda.core.flows.*; -import co.paralleluniverse.fibers.Suspendable; -import com.r3.corda.lib.accounts.workflows.services.AccountService; -import net.corda.core.flows.FlowLogic;; -import net.corda.core.flows.StartableByRPC; - -import java.util.UUID; - -@StartableByRPC -@StartableByService -@InitiatingFlow -public class CreateNewAccount extends FlowLogic{ - - private String acctName; - - public CreateNewAccount(String acctName) { - this.acctName = acctName; - } - - - @Override - public String call() throws FlowException { - StateAndRef newAccount = null; - try { - newAccount = getServiceHub().cordaService(KeyManagementBackedAccountService.class).createAccount(acctName).get(); - } catch (Exception e) { - e.printStackTrace(); - } - AccountInfo acct = newAccount.getState().getData(); - return "" + acct.getName() + " team's account was created. UUID is : " + acct.getIdentifier(); - } -} diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/NewKeyForAccount.java b/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/NewKeyForAccount.java deleted file mode 100644 index 83e2c5e6..00000000 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/NewKeyForAccount.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.supplychain.accountUtilities; - -import co.paralleluniverse.fibers.Suspendable; -import net.corda.core.flows.FlowException; -import net.corda.core.flows.FlowLogic; -import net.corda.core.flows.StartableByRPC; -import net.corda.core.flows.StartableByService; -import net.corda.core.identity.PartyAndCertificate; -import java.util.*; - -@StartableByRPC -@StartableByService -public class NewKeyForAccount extends FlowLogic{ - - private final UUID accountID; - - public NewKeyForAccount(UUID accountID) { - this.accountID = accountID; - } - - @Override - @Suspendable - public PartyAndCertificate call() throws FlowException { - return getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false, accountID); - } -} \ No newline at end of file diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/ShareAccountTo.java b/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/ShareAccountTo.java deleted file mode 100644 index c828a0c9..00000000 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/ShareAccountTo.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.supplychain.accountUtilities; - -import co.paralleluniverse.fibers.Suspendable; -import com.r3.corda.lib.accounts.contracts.states.AccountInfo; -import com.r3.corda.lib.accounts.workflows.flows.AccountInfoByName; -import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; -import net.corda.core.contracts.StateAndRef; -import net.corda.core.flows.FlowException; -import net.corda.core.flows.FlowLogic; -import net.corda.core.flows.StartableByRPC; -import net.corda.core.flows.StartableByService; -import net.corda.core.identity.Party; -import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; -import java.util.*; - -@StartableByRPC -@StartableByService -public class ShareAccountTo extends FlowLogic{ - - private final Party shareTo; - private final String acctNameShared; - - public ShareAccountTo(String acctNameShared, Party shareTo) { - this.acctNameShared = acctNameShared; - this.shareTo = shareTo; - } - - @Override - @Suspendable - public String call() throws FlowException { - List> allmyAccounts = getServiceHub().cordaService(KeyManagementBackedAccountService.class).ourAccounts(); - StateAndRef SharedAccount = allmyAccounts.stream() - .filter(it -> it.getState().getData().getName().equals(acctNameShared)) - .findAny().get(); - - subFlow(new ShareAccountInfo(SharedAccount, Arrays.asList(shareTo))); - return "Shared " + acctNameShared + " with " + shareTo.getName().getOrganisation(); - } -} \ No newline at end of file diff --git a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/CreateNewAccount.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/CreateNewAccount.java new file mode 100644 index 00000000..1e79bf3f --- /dev/null +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/CreateNewAccount.java @@ -0,0 +1,38 @@ +package net.corda.samples.supplychain.accountUtilities; + +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.*; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import net.corda.core.flows.FlowLogic;; +import net.corda.core.flows.StartableByRPC; + +import java.util.UUID; + +@StartableByRPC +@StartableByService +@InitiatingFlow +public class CreateNewAccount extends FlowLogic{ + + private String acctName; + + public CreateNewAccount(String acctName) { + this.acctName = acctName; + } + + + @Override + public String call() throws FlowException { + StateAndRef newAccount = null; + try { + newAccount = getServiceHub().cordaService(KeyManagementBackedAccountService.class).createAccount(acctName).get(); + } catch (Exception e) { + e.printStackTrace(); + } + AccountInfo acct = newAccount.getState().getData(); + return "" + acct.getName() + " team's account was created. UUID is : " + acct.getIdentifier(); + } +} diff --git a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/NewKeyForAccount.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/NewKeyForAccount.java new file mode 100644 index 00000000..1d057192 --- /dev/null +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/NewKeyForAccount.java @@ -0,0 +1,26 @@ +package net.corda.samples.supplychain.accountUtilities; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.flows.StartableByService; +import net.corda.core.identity.PartyAndCertificate; +import java.util.*; + +@StartableByRPC +@StartableByService +public class NewKeyForAccount extends FlowLogic{ + + private final UUID accountID; + + public NewKeyForAccount(UUID accountID) { + this.accountID = accountID; + } + + @Override + @Suspendable + public PartyAndCertificate call() throws FlowException { + return getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false, accountID); + } +} \ No newline at end of file diff --git a/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/ShareAccountTo.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/ShareAccountTo.java new file mode 100644 index 00000000..415dbcf5 --- /dev/null +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/ShareAccountTo.java @@ -0,0 +1,39 @@ +package net.corda.samples.supplychain.accountUtilities; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.flows.AccountInfoByName; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.flows.StartableByService; +import net.corda.core.identity.Party; +import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; +import java.util.*; + +@StartableByRPC +@StartableByService +public class ShareAccountTo extends FlowLogic{ + + private final Party shareTo; + private final String acctNameShared; + + public ShareAccountTo(String acctNameShared, Party shareTo) { + this.acctNameShared = acctNameShared; + this.shareTo = shareTo; + } + + @Override + @Suspendable + public String call() throws FlowException { + List> allmyAccounts = getServiceHub().cordaService(KeyManagementBackedAccountService.class).ourAccounts(); + StateAndRef SharedAccount = allmyAccounts.stream() + .filter(it -> it.getState().getData().getName().equals(acctNameShared)) + .findAny().get(); + + subFlow(new ShareAccountInfo(SharedAccount, Arrays.asList(shareTo))); + return "Shared " + acctNameShared + " with " + shareTo.getName().getOrganisation(); + } +} \ No newline at end of file diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/ViewInboxByAccount.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/ViewInboxByAccount.java similarity index 88% rename from Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/ViewInboxByAccount.java rename to Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/ViewInboxByAccount.java index d293539d..b97062bb 100644 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/accountUtilities/ViewInboxByAccount.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/accountUtilities/ViewInboxByAccount.java @@ -1,19 +1,14 @@ -package com.supplychain.accountUtilities; +package net.corda.samples.supplychain.accountUtilities; -import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; -import com.r3.corda.lib.accounts.workflows.flows.AccountInfoByName; import com.r3.corda.lib.accounts.workflows.services.AccountService; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; -import com.supplychain.states.*; -import net.corda.core.contracts.StateAndRef; import net.corda.core.flows.FlowException; import net.corda.core.flows.FlowLogic; import net.corda.core.flows.StartableByRPC; import net.corda.core.flows.StartableByService; -import net.corda.core.identity.Party; -import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.samples.supplychain.states.*; import java.util.*; import java.util.stream.Collectors; diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/InternalMessage.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/InternalMessage.java similarity index 87% rename from Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/InternalMessage.java rename to Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/InternalMessage.java index c7118533..eadf9f59 100644 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/InternalMessage.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/InternalMessage.java @@ -1,4 +1,4 @@ -package com.supplychain.flows; +package net.corda.samples.supplychain.flows; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; @@ -6,11 +6,10 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; -import com.supplychain.accountUtilities.NewKeyForAccount; -import com.supplychain.contracts.InternalMessageStateContract; -import com.supplychain.contracts.InvoiceStateContract; -import com.supplychain.states.InternalMessageState; -import com.supplychain.states.InvoiceState; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; +import net.corda.samples.supplychain.contracts.InternalMessageStateContract; +import net.corda.samples.supplychain.states.InternalMessageState; import net.corda.core.crypto.TransactionSignature; import net.corda.core.flows.*; import net.corda.core.identity.AnonymousParty; @@ -22,8 +21,6 @@ import java.security.PublicKey; import java.util.Arrays; import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; // ****************** @@ -84,7 +81,11 @@ public String call() throws FlowException { //generating State for transfer InternalMessageState output = new InternalMessageState(message,new AnonymousParty(myKey),targetAcctAnonymousParty); - TransactionBuilder txbuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0)) + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) .addCommand(new InternalMessageStateContract.Commands.Create(),Arrays.asList(targetAcctAnonymousParty.getOwningKey(),myKey)); progressTracker.setCurrentStep(SIGNING_TRANSACTION); diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendCargo.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendCargo.java similarity index 85% rename from Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendCargo.java rename to Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendCargo.java index cd2e3ce6..d51f2527 100644 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendCargo.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendCargo.java @@ -1,4 +1,4 @@ -package com.supplychain.flows; +package net.corda.samples.supplychain.flows; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; @@ -6,15 +6,17 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; -import com.supplychain.accountUtilities.NewKeyForAccount; -import com.supplychain.contracts.CargoStateContract; -import com.supplychain.states.CargoState; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; +import net.corda.samples.supplychain.contracts.CargoStateContract; +import net.corda.samples.supplychain.states.CargoState; import net.corda.core.crypto.TransactionSignature; import net.corda.core.flows.*; import net.corda.core.identity.AnonymousParty; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; + import java.security.PublicKey; import java.util.Arrays; import java.util.List; @@ -55,7 +57,12 @@ public String call() throws FlowException { //generating State for transfer CargoState output = new CargoState(new AnonymousParty(myKey),targetAcctAnonymousParty,cargo,getOurIdentity()); - TransactionBuilder txbuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0)) + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) .addCommand(new CargoStateContract.Commands.Create(), Arrays.asList(targetAcctAnonymousParty.getOwningKey(),getOurIdentity().getOwningKey())); diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendInvoice.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendInvoice.java similarity index 85% rename from Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendInvoice.java rename to Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendInvoice.java index 4c3b3eb8..5d17627c 100644 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendInvoice.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendInvoice.java @@ -1,4 +1,4 @@ -package com.supplychain.flows; +package net.corda.samples.supplychain.flows; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; @@ -6,15 +6,17 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; -import com.supplychain.accountUtilities.NewKeyForAccount; -import com.supplychain.contracts.InvoiceStateContract; -import com.supplychain.states.InvoiceState; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; +import net.corda.samples.supplychain.contracts.InvoiceStateContract; +import net.corda.samples.supplychain.states.InvoiceState; import net.corda.core.crypto.TransactionSignature; import net.corda.core.flows.*; import net.corda.core.identity.AnonymousParty; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; + import java.security.PublicKey; import java.util.Arrays; import java.util.List; @@ -55,7 +57,12 @@ public String call() throws FlowException { //generating State for transfer InvoiceState output = new InvoiceState(amount,new AnonymousParty(myKey),targetAcctAnonymousParty, UUID.randomUUID()); - TransactionBuilder txbuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0)) + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) .addCommand(new InvoiceStateContract.Commands.Create(), Arrays.asList(targetAcctAnonymousParty.getOwningKey(),myKey)); diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendPayment.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendPayment.java similarity index 84% rename from Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendPayment.java rename to Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendPayment.java index e5309882..9a65d6df 100644 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendPayment.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendPayment.java @@ -1,4 +1,4 @@ -package com.supplychain.flows; +package net.corda.samples.supplychain.flows; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; @@ -6,18 +6,20 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; -import com.supplychain.accountUtilities.NewKeyForAccount; -import com.supplychain.contracts.PaymentStateContract; -import com.supplychain.states.PaymentState; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; +import net.corda.samples.supplychain.contracts.PaymentStateContract; +import net.corda.samples.supplychain.states.PaymentState; import net.corda.core.crypto.TransactionSignature; import net.corda.core.flows.*; import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; + import java.security.PublicKey; import java.util.Arrays; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; @@ -54,7 +56,12 @@ public String call() throws FlowException { //generating State for transfer PaymentState output = new PaymentState(amount,new AnonymousParty(myKey),targetAcctAnonymousParty); - TransactionBuilder txbuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0)) + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) .addCommand(new PaymentStateContract.Commands.Create(), Arrays.asList(targetAcctAnonymousParty.getOwningKey(),myKey)); diff --git a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendShippingRequest.java b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java similarity index 88% rename from Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendShippingRequest.java rename to Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java index 56ddb99d..962770ee 100644 --- a/Accounts/supplychain/workflows/src/main/java/com/supplychain/flows/SendShippingRequest.java +++ b/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java @@ -1,17 +1,14 @@ -package com.supplychain.flows; +package net.corda.samples.supplychain.flows; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; import com.r3.corda.lib.accounts.workflows.services.AccountService; -import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; -import com.supplychain.accountUtilities.NewKeyForAccount; -import com.supplychain.contracts.InvoiceStateContract; -import com.supplychain.contracts.ShippingRequestStateContract; -import com.supplychain.states.InvoiceState; -import com.supplychain.states.PaymentState; -import com.supplychain.states.ShippingRequestState; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.supplychain.accountUtilities.NewKeyForAccount; +import net.corda.samples.supplychain.contracts.ShippingRequestStateContract; +import net.corda.samples.supplychain.states.ShippingRequestState; import net.corda.core.crypto.TransactionSignature; import net.corda.core.flows.*; import net.corda.core.identity.AnonymousParty; @@ -23,7 +20,6 @@ import java.security.PublicKey; import java.util.Arrays; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; @@ -87,7 +83,12 @@ public String call() throws FlowException { //generating State for transfer progressTracker.setCurrentStep(GENERATING_TRANSACTION); ShippingRequestState output = new ShippingRequestState(new AnonymousParty(myKey),whereTo,shipper,cargo); - TransactionBuilder txbuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0)) + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + TransactionBuilder txbuilder = new TransactionBuilder(notary) .addOutputState(output) .addCommand(new ShippingRequestStateContract.Commands.Create(), Arrays.asList(shipper.getOwningKey(),myKey)); diff --git a/Accounts/supplychain/workflows/src/test/java/com/supplychain/ContractTests.java b/Accounts/supplychain/workflows/src/test/java/com/supplychain/ContractTests.java deleted file mode 100644 index e0a55a0d..00000000 --- a/Accounts/supplychain/workflows/src/test/java/com/supplychain/ContractTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.supplychain; - -import net.corda.testing.node.MockServices; -import org.junit.Test; - -public class ContractTests { - private final MockServices ledgerServices = new MockServices(); - - @Test - public void dummyTest() { - - } -} \ No newline at end of file diff --git a/Accounts/supplychain/workflows/src/test/java/com/supplychain/FlowTests.java b/Accounts/supplychain/workflows/src/test/java/com/supplychain/FlowTests.java deleted file mode 100644 index ea71fc74..00000000 --- a/Accounts/supplychain/workflows/src/test/java/com/supplychain/FlowTests.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.supplychain; - -import com.google.common.collect.ImmutableList; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.StartedMockNode; -import net.corda.testing.node.TestCordapp; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class FlowTests { - private final MockNetwork network = new MockNetwork(new MockNetworkParameters(ImmutableList.of( - TestCordapp.findCordapp("com.supplychain.contracts"), - TestCordapp.findCordapp("com.supplychain.flows") - ))); - private final StartedMockNode a = network.createNode(); - private final StartedMockNode b = network.createNode(); - - public FlowTests() { - } - - @Before - public void setup() { - network.runNetwork(); - } - - @After - public void tearDown() { - network.stopNodes(); - } - - @Test - public void dummyTest() { - - } -} diff --git a/Accounts/supplychain/workflows/src/test/java/com/supplychain/NodeDriver.java b/Accounts/supplychain/workflows/src/test/java/com/supplychain/NodeDriver.java deleted file mode 100644 index ef32c93e..00000000 --- a/Accounts/supplychain/workflows/src/test/java/com/supplychain/NodeDriver.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.supplychain; - -import com.google.common.collect.ImmutableList; -import net.corda.core.identity.CordaX500Name; -import net.corda.testing.driver.DriverParameters; -import net.corda.testing.driver.NodeParameters; -import net.corda.testing.node.User; -import com.google.common.collect.ImmutableSet; - -import java.util.List; -import static net.corda.testing.driver.Driver.driver; - -/** - * Allows you to run your nodes through an IDE (as opposed to using deployNodes). Do not use in a production - * environment. - */ -public class NodeDriver { - public static void main(String[] args) { - final List rpcUsers = - ImmutableList.of(new User("user1", "test", ImmutableSet.of("ALL"))); - - driver(new DriverParameters().withStartNodesInProcess(true).withWaitForAllNodesToFinish(true), dsl -> { - try { - dsl.startNode(new NodeParameters() - .withProvidedName(new CordaX500Name("PartyA", "London", "GB")) - .withRpcUsers(rpcUsers)).get(); - dsl.startNode(new NodeParameters() - .withProvidedName(new CordaX500Name("PartyB", "New York", "US")) - .withRpcUsers(rpcUsers)).get(); - } catch (Throwable e) { - System.err.println("Encountered exception in node startup: " + e.getMessage()); - e.printStackTrace(); - } - - return null; - } - ); - } -} diff --git a/Accounts/supplychain/workflows/src/test/java/net/corda/samples/supplychain/FlowTests.java b/Accounts/supplychain/workflows/src/test/java/net/corda/samples/supplychain/FlowTests.java new file mode 100644 index 00000000..5332c1fb --- /dev/null +++ b/Accounts/supplychain/workflows/src/test/java/net/corda/samples/supplychain/FlowTests.java @@ -0,0 +1,99 @@ +package net.corda.samples.supplychain; + +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.NetworkParameters; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.samples.supplychain.accountUtilities.CreateNewAccount; +import net.corda.samples.supplychain.accountUtilities.ShareAccountTo; +import net.corda.samples.supplychain.flows.SendInvoice; +import net.corda.samples.supplychain.states.InvoiceState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; +import java.time.Instant; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + + private NetworkParameters testNetworkParameters = + new NetworkParameters(4, Arrays.asList(), 10485760, (10485760 * 5), Instant.now(),1, new LinkedHashMap<>()); + + @Before + public void setup() { + + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.supplychain.contracts"), + TestCordapp.findCordapp("net.corda.samples.supplychain.flows"), + TestCordapp.findCordapp("com.r3.corda.lib.accounts.contracts"), + TestCordapp.findCordapp("com.r3.corda.lib.accounts.workflows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void AccountCreation() throws ExecutionException, InterruptedException { + CreateNewAccount createAcct = new CreateNewAccount("TestAccountA"); + Future future = a.startFlow(createAcct); + network.runNetwork(); + AccountService accountService = a.getServices().cordaService(KeyManagementBackedAccountService.class); + List> myAccount = accountService.accountInfo("TestAccountA"); + assert (myAccount.size() != 0); + } + + @Test + public void InvoiceFlowTest() throws ExecutionException, InterruptedException { + CreateNewAccount createAcct = new CreateNewAccount("TestAccountA"); + Future future = a.startFlow(createAcct); + network.runNetwork(); + ShareAccountTo shareAToB = new ShareAccountTo("TestAccountA",b.getInfo().getLegalIdentities().get(0)); + Future future2 = a.startFlow(shareAToB); + network.runNetwork(); + + CreateNewAccount createAcct2 = new CreateNewAccount("TestAccountB"); + Future future3 = b.startFlow(createAcct2); + network.runNetwork(); + + ShareAccountTo shareBToA = new ShareAccountTo("TestAccountB",a.getInfo().getLegalIdentities().get(0)); + Future future4 = b.startFlow(shareBToA); + network.runNetwork(); + + SendInvoice invoiceflow = new SendInvoice("TestAccountA","TestAccountB",20); + Future future5 = a.startFlow(invoiceflow); + network.runNetwork(); + + //retrieve + AccountService accountService = b.getServices().cordaService(KeyManagementBackedAccountService.class); + AccountInfo myAccount = accountService.accountInfo("TestAccountB").get(0).getState().getData(); + QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria() + .withExternalIds(Arrays.asList(myAccount.getIdentifier().getId())); + InvoiceState storedState = b.getServices().getVaultService().queryBy(InvoiceState.class,criteria).getStates() + .get(0).getState().getData(); + assert (storedState.getAmount() == 20); + } + + +} diff --git a/Accounts/tictacthor/README.md b/Accounts/tictacthor/README.md index df523102..5c388345 100644 --- a/Accounts/tictacthor/README.md +++ b/Accounts/tictacthor/README.md @@ -1,39 +1,45 @@ -# Tic Tac Thor -This CorDapp recreates the game of Tic Tac Toe via Corda. It primarilly demonstrates how you can have linear state transactions between cross-node accounts. +# Tic Tac Thor +This CorDapp recreates the game of Tic Tac Toe via Corda. It primarily demonstrates how you can have [LinearState](https://docs.corda.net/docs/corda-os/api-states.html#linearstate) transactions between cross-node accounts. +

Corda

+ +## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). + ## Running the sample Deploy and run the nodes by: ``` -./gradlew deployNodes +./gradlew clean build deployNodes ./build/nodes/runnodes ``` -Then you will need to also start the spring server for the Cordapp by running the following commands seperately: +Then you will need to also start the spring server for the CorDapp by running the following commands separately: `./gradlew bootRunDevRel`will have the DevRel server running on 8080 port , and `./gradlew bootRunSOE`will start the Solution Engineering server on 8090 port -## Operating the Cordapp -Now go to postman and excute the following in order: (All of the API are POST request!) +## Operating the CorDapp +Now go to postman and execute the following in order: (All the APIs are POST requests!) 1. Create an account on DevRel node: `http://localhost:8080/createAccount/PeterLi` 2. Create an account on SoE node: `http://localhost:8090/createAccount/DavidWinner` 3. Peter now requests game with David: `http://localhost:8080/requestGameWith/PeterLi/SolutionEng/DavidWinner` -4. David has to accept the challenege: `http://localhost:8090/acceptGameInvite/DavidWinner/DevRel/PeterLi` +4. David has to accept the challenge: `http://localhost:8090/acceptGameInvite/DavidWinner/DevRel/PeterLi` 5. Game Starts, and Peter makes the first move: `http://localhost:8080/startGameAndFirstMove/PeterLi/DavidWinner/0` 6. David's turn: `http://localhost:8090/submitMove/DavidWinner/PeterLi/4` API Syntax: `http://localhost:8080/submitMove/WHO-AM-I/MY-COUNTERPART/POSITION` -From here, you can start play the game by changing the very last number from the `submitMove`API call. The game board is representated by an 1-D array: What we just ran can transfer into a tic-tac-toe game board like the one we see on the right. +From here, you can start play the game by changing the very last number from the `submitMove`API call. The game board is represented by an 1-D array: What we just ran can transfer into a tic-tac-toe game board like the one we see on the right. ``` │0│1│2│ │O│ │ │ │3│4│5│ -> │ │X│ │ │6│7│8│ │ │ │ │ ``` -The Game will automatically end when one player win the game. +The Game will automatically end when one player wins the game. You can also run `run vaultQuery contractStateType: net.corda.samples.tictacthor.states.BoardState` at any given time to see the board games stored in vault. -now if you want to fast fard the game, Play the following moves in order: +now if you want to fast-forward the game, Play the following moves in order: According to syntax: we should have `http://localhost:8080/submitMove/PeterLi/DavidWinner/3` for the first move below. ``` * Peter: 3 │O│ │ │ @@ -48,7 +54,7 @@ We can play a bit more about the accounts. Now let's create two accounts, a new * Create an account on SoE node: `http://localhost:8090/createAccount/ThorG` Now, try to have Anthony play a game with Thor while start a new game between Peter and David. It worked! -One key feature about account is that, each account's data is segregated, meaning each account will not be able to see other account's data. In this sample cordapp, the game is queried by account name. Therefore, we see that each account only knows about the game that he participated. Account Peter doesn't know anything about the game between Thor and Anthony. +One key feature about account is that, each account's data is segregated, meaning that it can be enforced that each account will not be able to see other account's data. In this sample CorDapp, the game is queried by account name. Therefore, we see that each account only knows about the game that he participated. Account Peter doesn't know anything about the game between Thor and Anthony. ## Credit This project is inspired and evolved from a simple [tic-tac-toe](https://github.com/thorgilman/tictactoe) game on Corda by Thor Gilman. diff --git a/Accounts/tictacthor/build.gradle b/Accounts/tictacthor/build.gradle index 9fd8aeca..b33c32b0 100644 --- a/Accounts/tictacthor/build.gradle +++ b/Accounts/tictacthor/build.gradle @@ -15,9 +15,11 @@ buildscript { log4j_version = constants.getProperty("log4jVersion") slf4j_version = constants.getProperty("slf4jVersion") corda_platform_version = constants.getProperty("platformVersion").toInteger() + //springboot spring_boot_version = '2.0.2.RELEASE' spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + //account accounts_release_group = 'com.r3.corda.lib.accounts' accounts_release_version = '1.0' @@ -29,12 +31,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev' } - maven { url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } - // Corda dependencies for the patched Quasar version - maven { url "https://software.r3.com/artifactory/corda-dependencies" } // access to the patched Quasar version + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -52,14 +50,14 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } - // Can be removed post-release - used to get nightly snapshot build. - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } - maven { url "https://repo.gradle.org/gradle/libs-releases-local" } + //SDK lib + maven { url 'https://download.corda.net/maven/corda-lib' } + //Gradle Plugins + maven { url 'https://repo.gradle.org/gradle/libs-releases' } } tasks.withType(JavaCompile) { @@ -102,6 +100,7 @@ dependencies { cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaDriver "net.corda:corda-shell:4.10" //Account lib cordapp "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" @@ -109,30 +108,6 @@ dependencies { cordapp "$accounts_release_group:accounts-workflows:$accounts_release_version" } -cordapp { - info { - name "CorDapp tictacthor" - vendor "Corda Open Source" - targetPlatformVersion corda_platform_version - minimumPlatformVersion corda_platform_version - } -} - -task ganache { - subprojects { - if (it.project.name != "clients") { - dependsOn jar - doLast { - copy { - from "${buildDir}/libs" - into "${rootDir}/build/libs" - } - } - } - } -} - - task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { nodeDefaults { projectCordapp { @@ -143,6 +118,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { cordapp("$confidential_id_release_group:ci-workflows:$confidential_id_release_version") cordapp("$accounts_release_group:accounts-contracts:$accounts_release_version") cordapp("$accounts_release_group:accounts-workflows:$accounts_release_version") + runSchemaMigration = true } node { name "O=Notary,L=London,C=GB" @@ -172,15 +148,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { } rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] } -// node { -// name "O=Observer,L=Chicago,C=US,CN=John" -// p2pPort 10011 -// rpcSettings { -// address("localhost:10010") -// adminAddress("localhost:10050") -// } -// rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] -// } } task installQuasar(type: Copy) { diff --git a/Accounts/tictacthor/clients/build.gradle b/Accounts/tictacthor/clients/build.gradle index b17168b1..bbba1aea 100644 --- a/Accounts/tictacthor/clients/build.gradle +++ b/Accounts/tictacthor/clients/build.gradle @@ -8,6 +8,12 @@ sourceSets { } } +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + dependencies { // Corda dependencies. compile "$corda_release_group:corda-rpc:$corda_release_version" @@ -27,17 +33,17 @@ dependencies { } springBoot { - mainClassName = "com.tictacthor.webserver.Server" + mainClassName = "net.corda.samples.tictacthor.webserver" } task bootRunDevRel(type: JavaExec, dependsOn: jar) { classpath = sourceSets.main.runtimeClasspath - main = 'com.tictacthor.webserver.Starter' + main = 'net.corda.samples.tictacthor.webserver.Starter' args '--server.port=8080', '--config.rpc.host=localhost', '--config.rpc.port=10006', '--config.rpc.username=user1', '--config.rpc.password=test' } task bootRunSOE(type: JavaExec, dependsOn: jar) { classpath = sourceSets.main.runtimeClasspath - main = 'com.tictacthor.webserver.Starter' + main = 'net.corda.samples.tictacthor.webserver.Starter' args '--server.port=8090', '--config.rpc.host=localhost', '--config.rpc.port=10009', '--config.rpc.username=user1', '--config.rpc.password=test' } \ No newline at end of file diff --git a/Accounts/tictacthor/clients/src/main/java/com/tictacthor/Client.java b/Accounts/tictacthor/clients/src/main/java/com/tictacthor/Client.java deleted file mode 100644 index 892d7a17..00000000 --- a/Accounts/tictacthor/clients/src/main/java/com/tictacthor/Client.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.tictacthor; - -import net.corda.client.rpc.CordaRPCClient; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.node.NodeInfo; -import net.corda.core.utilities.NetworkHostAndPort; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -import static net.corda.core.utilities.NetworkHostAndPort.parse; - -/** - * Connects to a Corda node via RPC and performs RPC operations on the node. - * - * The RPC connection is configured using command line arguments. - */ -public class Client { - private static final Logger logger = LoggerFactory.getLogger(Client.class); - - public static void main(String[] args) { - // Create an RPC connection to the node. - if (args.length != 3) throw new IllegalArgumentException("Usage: Client "); - final NetworkHostAndPort nodeAddress = parse(args[0]); - final String rpcUsername = args[1]; - final String rpcPassword = args[2]; - final CordaRPCClient client = new CordaRPCClient(nodeAddress); - final CordaRPCOps proxy = client.start(rpcUsername, rpcPassword).getProxy(); - - // Interact with the node. - // For example, here we print the nodes on the network. - final List nodes = proxy.networkMapSnapshot(); - logger.info("{}", nodes); - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/Controller.java b/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/Controller.java deleted file mode 100644 index 77ab2a0a..00000000 --- a/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/Controller.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.tictacthor.webserver; - - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.tictacthor.accountUtilities.CreateNewAccount; -import com.tictacthor.accountUtilities.ShareAccountTo; -import com.tictacthor.accountUtilities.myGame; -import com.tictacthor.flows.EndGameFlow; -import com.tictacthor.flows.StartGameFlow; -import com.tictacthor.flows.SubmitTurnFlow; -import com.tictacthor.states.BoardState; -import net.corda.client.jackson.JacksonSupport; -import net.corda.core.contracts.UniqueIdentifier; -import net.corda.core.identity.CordaX500Name; -import net.corda.core.identity.Party; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.node.NodeInfo; -import java.util.*; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x500.style.BCStyle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.stream.Collectors; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; - -/** - * Define your API endpoints here. - */ -@RestController -@RequestMapping("/") // The paths for HTTP requests are relative to this base path. -public class Controller { - private static final Logger logger = LoggerFactory.getLogger(RestController.class); - private final CordaRPCOps proxy; - private final CordaX500Name me; - - public Controller(NodeRPCConnection rpc) { - this.proxy = rpc.proxy; - this.me = proxy.nodeInfo().getLegalIdentities().get(0).getName(); - - } - - /** Helpers for filtering the network map cache. */ - public String toDisplayString(X500Name name){ - return BCStyle.INSTANCE.toString(name); - } - - private boolean isNotary(NodeInfo nodeInfo) { - return !proxy.notaryIdentities() - .stream().filter(el -> nodeInfo.isLegalIdentity(el)) - .collect(Collectors.toList()).isEmpty(); - } - - private boolean isMe(NodeInfo nodeInfo){ - return nodeInfo.getLegalIdentities().get(0).getName().equals(me); - } - - private boolean isNetworkMap(NodeInfo nodeInfo){ - return nodeInfo.getLegalIdentities().get(0).getName().getOrganisation().equals("Network Map Service"); - } - - @Configuration - class Plugin { - @Bean - public ObjectMapper registerModule() { - return JacksonSupport.createNonRpcMapper(); - } - } - - @GetMapping(value = "/me",produces = APPLICATION_JSON_VALUE) - private HashMap whoami(){ - HashMap myMap = new HashMap<>(); - myMap.put("me", me.toString()); - return myMap; - } - - @PostMapping(value = "createAccount/{acctName}") - private ResponseEntity createAccount(@PathVariable String acctName){ - try{ - String result = proxy.startTrackedFlowDynamic(CreateNewAccount.class,acctName).getReturnValue().get(); - return ResponseEntity.status(HttpStatus.CREATED).body("Account "+acctName+" Created"); - - }catch (Exception e) { - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(e.getMessage()); - } - } - - @PostMapping(value = "requestGameWith/{whoAmI}/{team}/{competeWith}") - private ResponseEntity requestGameWith(@PathVariable String whoAmI,@PathVariable String team, @PathVariable String competeWith){ - Set matchingPasties = proxy.partiesFromName(team,false); - try{ - - Iterator iter = matchingPasties.iterator(); - String result = proxy.startTrackedFlowDynamic(ShareAccountTo.class,whoAmI,iter.next()).getReturnValue().get(); - return ResponseEntity.status(HttpStatus.CREATED).body("Game Request has Sent. When "+competeWith+" accepts your challenge, the game will start!"); - - }catch (Exception e) { - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(e.getMessage()); - } - } - - @PostMapping(value = "acceptGameInvite/{whoAmI}/{team}/{competeWith}") - private ResponseEntity acceptGameInvite(@PathVariable String whoAmI,@PathVariable String team, @PathVariable String competeWith){ - Set matchingPasties = proxy.partiesFromName(team,false); - try{ - - Iterator iter = matchingPasties.iterator(); - String result = proxy.startTrackedFlowDynamic(ShareAccountTo.class,whoAmI,iter.next()).getReturnValue().get(); - return ResponseEntity.status(HttpStatus.CREATED).body("I, "+whoAmI+" accepts "+competeWith+"'s challenge. Let's play!"); - - }catch (Exception e) { - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(e.getMessage()); - } - } - - @PostMapping(value = "startGameAndFirstMove/{whoAmI}/{competeWith}/{position}") - private ResponseEntity startGameAndFirstMove(@PathVariable String whoAmI, - @PathVariable String competeWith, - @PathVariable String position){ - int x = -1; - int y = -1; - int pos = Integer.parseInt(position); - if(pos == 0) { - x=0;y=0; - }else if(pos == 1){ - x=0;y=1; - }else if(pos == 2){ - x=0;y=2; - }else if(pos == 3){ - x=1;y=0; - }else if(pos == 4){ - x=1;y=1; - }else if(pos == 5){ - x=1;y=2; - }else if(pos == 6){ - x=2;y=0; - }else if(pos == 7){ - x=2;y=1; - }else if(pos == 8){ - x=2;y=2; - } - try{ - UniqueIdentifier gameId = proxy.startTrackedFlowDynamic(StartGameFlow.class,whoAmI,competeWith).getReturnValue().get(); - String submitTurn = proxy.startTrackedFlowDynamic(SubmitTurnFlow.class, gameId, whoAmI,competeWith,x,y).getReturnValue().get(); - return ResponseEntity.status(HttpStatus.CREATED).body("Game Id Created: "+gameId+", "+whoAmI+" made the first move on position ["+x+","+y+"]."); - - }catch (Exception e) { - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(e.getMessage()); - } - } - - @PostMapping(value = "submitMove/{whoAmI}/{competeWith}/{position}") - private ResponseEntity submitMove(@PathVariable String whoAmI, - @PathVariable String competeWith, - @PathVariable String position) { - int x = -1; - int y = -1; - int pos = Integer.parseInt(position); - if(pos == 0) { - x=0;y=0; - }else if(pos == 1){ - x=0;y=1; - }else if(pos == 2){ - x=0;y=2; - }else if(pos == 3){ - x=1;y=0; - }else if(pos == 4){ - x=1;y=1; - }else if(pos == 5){ - x=1;y=2; - }else if(pos == 6){ - x=2;y=0; - }else if(pos == 7){ - x=2;y=1; - }else if(pos == 8){ - x=2;y=2; - } - try{ - UniqueIdentifier gameId = proxy.startTrackedFlowDynamic(myGame.class,whoAmI).getReturnValue().get().getLinearId(); - String submitTurn = proxy.startTrackedFlowDynamic(SubmitTurnFlow.class, gameId, whoAmI,competeWith,x,y).getReturnValue().get(); - - if(isGameOver(whoAmI)){ - proxy.startTrackedFlowDynamic(EndGameFlow.class, gameId, whoAmI,competeWith).getReturnValue().get(); - return ResponseEntity.status(HttpStatus.CREATED).body(""+whoAmI+" made the move on position ["+x+","+y+"], and Game Over"); - }else{ - return ResponseEntity.status(HttpStatus.CREATED).body(""+whoAmI+"+ made the move on position ["+x+","+y+"]"); - } - }catch (Exception e) { - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(e.getMessage()); - } - } - - private Boolean isGameOver(String whoAmI){ - //If the game is over, the Status should be a null variable - //So if the status returned is GAME_IN_PROGRESS, it means the game is not over. - try{ - BoardState.Status gameStatus = proxy.startTrackedFlowDynamic(myGame.class,whoAmI).getReturnValue().get().getStatus(); - return gameStatus != BoardState.Status.GAME_IN_PROGRESS; - }catch (Exception e) { - throw new IllegalArgumentException("No board"); - } - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/NodeRPCConnection.java b/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/NodeRPCConnection.java deleted file mode 100644 index bea1e18a..00000000 --- a/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/NodeRPCConnection.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.tictacthor.webserver; - -import net.corda.client.rpc.CordaRPCClient; -import net.corda.client.rpc.CordaRPCConnection; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.utilities.NetworkHostAndPort; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -/** - * Wraps an RPC connection to a Corda node. - * - * The RPC connection is configured using command line arguments. - */ -@Component -public class NodeRPCConnection implements AutoCloseable { - // The host of the node we are connecting to. - @Value("${config.rpc.host}") - private String host; - // The RPC port of the node we are connecting to. - @Value("${config.rpc.username}") - private String username; - // The username for logging into the RPC client. - @Value("${config.rpc.password}") - private String password; - // The password for logging into the RPC client. - @Value("${config.rpc.port}") - private int rpcPort; - - private CordaRPCConnection rpcConnection; - CordaRPCOps proxy; - - @PostConstruct - public void initialiseNodeRPCConnection() { - NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); - CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); - rpcConnection = rpcClient.start(username, password); - proxy = rpcConnection.getProxy(); - } - - @PreDestroy - public void close() { - rpcConnection.notifyServerAndClose(); - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/Starter.java b/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/Starter.java deleted file mode 100644 index 826b5d06..00000000 --- a/Accounts/tictacthor/clients/src/main/java/com/tictacthor/webserver/Starter.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.tictacthor.webserver; - -import org.springframework.boot.Banner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -import static org.springframework.boot.WebApplicationType.SERVLET; - -/** - * Our Spring Boot application. - */ -@SpringBootApplication -public class Starter { - /** - * Starts our Spring Boot application. - */ - public static void main(String[] args) { - SpringApplication app = new SpringApplication(Starter.class); - app.setBannerMode(Banner.Mode.OFF); - app.setWebApplicationType(SERVLET); - app.run(args); - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/Client.java b/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/Client.java new file mode 100644 index 00000000..5a74c8cd --- /dev/null +++ b/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/Client.java @@ -0,0 +1,36 @@ +package net.corda.samples.tictacthor; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; +import net.corda.core.utilities.NetworkHostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static net.corda.core.utilities.NetworkHostAndPort.parse; + +/** + * Connects to a Corda node via RPC and performs RPC operations on the node. + * + * The RPC connection is configured using command line arguments. + */ +public class Client { + private static final Logger logger = LoggerFactory.getLogger(Client.class); + + public static void main(String[] args) { + // Create an RPC connection to the node. + if (args.length != 3) throw new IllegalArgumentException("Usage: Client "); + final NetworkHostAndPort nodeAddress = parse(args[0]); + final String rpcUsername = args[1]; + final String rpcPassword = args[2]; + final CordaRPCClient client = new CordaRPCClient(nodeAddress); + final CordaRPCOps proxy = client.start(rpcUsername, rpcPassword).getProxy(); + + // Interact with the node. + // For example, here we print the nodes on the network. + final List nodes = proxy.networkMapSnapshot(); + logger.info("{}", nodes); + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/Controller.java b/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/Controller.java new file mode 100644 index 00000000..eaa2c30c --- /dev/null +++ b/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/Controller.java @@ -0,0 +1,218 @@ +package net.corda.samples.tictacthor.webserver; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.corda.samples.tictacthor.accountUtilities.CreateNewAccount; +import net.corda.samples.tictacthor.accountUtilities.ShareAccountTo; +import net.corda.samples.tictacthor.accountUtilities.myGame; +import net.corda.samples.tictacthor.flows.EndGameFlow; +import net.corda.samples.tictacthor.flows.StartGameFlow; +import net.corda.samples.tictacthor.flows.SubmitTurnFlow; +import net.corda.samples.tictacthor.states.BoardState; +import net.corda.client.jackson.JacksonSupport; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; +import java.util.*; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.stream.Collectors; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * Define your API endpoints here. + */ +@RestController +@RequestMapping("/") // The paths for HTTP requests are relative to this base path. +public class Controller { + private static final Logger logger = LoggerFactory.getLogger(RestController.class); + private final CordaRPCOps proxy; + private final CordaX500Name me; + + public Controller(NodeRPCConnection rpc) { + this.proxy = rpc.proxy; + this.me = proxy.nodeInfo().getLegalIdentities().get(0).getName(); + + } + + /** Helpers for filtering the network map cache. */ + public String toDisplayString(X500Name name){ + return BCStyle.INSTANCE.toString(name); + } + + private boolean isNotary(NodeInfo nodeInfo) { + return !proxy.notaryIdentities() + .stream().filter(el -> nodeInfo.isLegalIdentity(el)) + .collect(Collectors.toList()).isEmpty(); + } + + private boolean isMe(NodeInfo nodeInfo){ + return nodeInfo.getLegalIdentities().get(0).getName().equals(me); + } + + private boolean isNetworkMap(NodeInfo nodeInfo){ + return nodeInfo.getLegalIdentities().get(0).getName().getOrganisation().equals("Network Map Service"); + } + + @Configuration + class Plugin { + @Bean + public ObjectMapper registerModule() { + return JacksonSupport.createNonRpcMapper(); + } + } + + @GetMapping(value = "/me",produces = APPLICATION_JSON_VALUE) + private HashMap whoami(){ + HashMap myMap = new HashMap<>(); + myMap.put("me", me.toString()); + return myMap; + } + + @PostMapping(value = "createAccount/{acctName}") + private ResponseEntity createAccount(@PathVariable String acctName){ + try{ + String result = proxy.startTrackedFlowDynamic(CreateNewAccount.class,acctName).getReturnValue().get(); + return ResponseEntity.status(HttpStatus.CREATED).body("Account "+acctName+" Created"); + + }catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } + + @PostMapping(value = "requestGameWith/{whoAmI}/{team}/{competeWith}") + private ResponseEntity requestGameWith(@PathVariable String whoAmI,@PathVariable String team, @PathVariable String competeWith){ + Set matchingPasties = proxy.partiesFromName(team,false); + try{ + + Iterator iter = matchingPasties.iterator(); + String result = proxy.startTrackedFlowDynamic(ShareAccountTo.class,whoAmI,iter.next()).getReturnValue().get(); + return ResponseEntity.status(HttpStatus.CREATED).body("Game Request has Sent. When "+competeWith+" accepts your challenge, the game will start!"); + + }catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } + + @PostMapping(value = "acceptGameInvite/{whoAmI}/{team}/{competeWith}") + private ResponseEntity acceptGameInvite(@PathVariable String whoAmI,@PathVariable String team, @PathVariable String competeWith){ + Set matchingPasties = proxy.partiesFromName(team,false); + try{ + + Iterator iter = matchingPasties.iterator(); + String result = proxy.startTrackedFlowDynamic(ShareAccountTo.class,whoAmI,iter.next()).getReturnValue().get(); + return ResponseEntity.status(HttpStatus.CREATED).body("I, "+whoAmI+" accepts "+competeWith+"'s challenge. Let's play!"); + + }catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } + + @PostMapping(value = "startGameAndFirstMove/{whoAmI}/{competeWith}/{position}") + private ResponseEntity startGameAndFirstMove(@PathVariable String whoAmI, + @PathVariable String competeWith, + @PathVariable String position){ + int x = -1; + int y = -1; + int pos = Integer.parseInt(position); + if(pos == 0) { + x=0;y=0; + }else if(pos == 1){ + x=0;y=1; + }else if(pos == 2){ + x=0;y=2; + }else if(pos == 3){ + x=1;y=0; + }else if(pos == 4){ + x=1;y=1; + }else if(pos == 5){ + x=1;y=2; + }else if(pos == 6){ + x=2;y=0; + }else if(pos == 7){ + x=2;y=1; + }else if(pos == 8){ + x=2;y=2; + } + try{ + UniqueIdentifier gameId = proxy.startTrackedFlowDynamic(StartGameFlow.class,whoAmI,competeWith).getReturnValue().get(); + String submitTurn = proxy.startTrackedFlowDynamic(SubmitTurnFlow.class, gameId, whoAmI,competeWith,x,y).getReturnValue().get(); + return ResponseEntity.status(HttpStatus.CREATED).body("Game Id Created: "+gameId+", "+whoAmI+" made the first move on position ["+x+","+y+"]."); + + }catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } + + @PostMapping(value = "submitMove/{whoAmI}/{competeWith}/{position}") + private ResponseEntity submitMove(@PathVariable String whoAmI, + @PathVariable String competeWith, + @PathVariable String position) { + int x = -1; + int y = -1; + int pos = Integer.parseInt(position); + if(pos == 0) { + x=0;y=0; + }else if(pos == 1){ + x=0;y=1; + }else if(pos == 2){ + x=0;y=2; + }else if(pos == 3){ + x=1;y=0; + }else if(pos == 4){ + x=1;y=1; + }else if(pos == 5){ + x=1;y=2; + }else if(pos == 6){ + x=2;y=0; + }else if(pos == 7){ + x=2;y=1; + }else if(pos == 8){ + x=2;y=2; + } + try{ + UniqueIdentifier gameId = proxy.startTrackedFlowDynamic(myGame.class,whoAmI).getReturnValue().get().getLinearId(); + String submitTurn = proxy.startTrackedFlowDynamic(SubmitTurnFlow.class, gameId, whoAmI,competeWith,x,y).getReturnValue().get(); + + if(isGameOver(whoAmI)){ + proxy.startTrackedFlowDynamic(EndGameFlow.class, gameId, whoAmI,competeWith).getReturnValue().get(); + return ResponseEntity.status(HttpStatus.CREATED).body(""+whoAmI+" made the move on position ["+x+","+y+"], and Game Over"); + }else{ + return ResponseEntity.status(HttpStatus.CREATED).body(""+whoAmI+"+ made the move on position ["+x+","+y+"]"); + } + }catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } + + private Boolean isGameOver(String whoAmI){ + //If the game is over, the Status should be a null variable + //So if the status returned is GAME_IN_PROGRESS, it means the game is not over. + try{ + BoardState.Status gameStatus = proxy.startTrackedFlowDynamic(myGame.class,whoAmI).getReturnValue().get().getStatus(); + return gameStatus != BoardState.Status.GAME_IN_PROGRESS; + }catch (Exception e) { + throw new IllegalArgumentException("No board"); + } + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/NodeRPCConnection.java b/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/NodeRPCConnection.java new file mode 100644 index 00000000..b1c1362e --- /dev/null +++ b/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/NodeRPCConnection.java @@ -0,0 +1,48 @@ +package net.corda.samples.tictacthor.webserver; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/** + * Wraps an RPC connection to a Corda node. + * + * The RPC connection is configured using command line arguments. + */ +@Component +public class NodeRPCConnection implements AutoCloseable { + // The host of the node we are connecting to. + @Value("${config.rpc.host}") + private String host; + // The RPC port of the node we are connecting to. + @Value("${config.rpc.username}") + private String username; + // The username for logging into the RPC client. + @Value("${config.rpc.password}") + private String password; + // The password for logging into the RPC client. + @Value("${config.rpc.port}") + private int rpcPort; + + private CordaRPCConnection rpcConnection; + CordaRPCOps proxy; + + @PostConstruct + public void initialiseNodeRPCConnection() { + NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); + CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); + rpcConnection = rpcClient.start(username, password); + proxy = rpcConnection.getProxy(); + } + + @PreDestroy + public void close() { + rpcConnection.notifyServerAndClose(); + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/Starter.java b/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/Starter.java new file mode 100644 index 00000000..7b1e329c --- /dev/null +++ b/Accounts/tictacthor/clients/src/main/java/net/corda/samples/tictacthor/webserver/Starter.java @@ -0,0 +1,23 @@ +package net.corda.samples.tictacthor.webserver; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import static org.springframework.boot.WebApplicationType.SERVLET; + +/** + * Our Spring Boot application. + */ +@SpringBootApplication +public class Starter { + /** + * Starts our Spring Boot application. + */ + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Starter.class); + app.setBannerMode(Banner.Mode.OFF); + app.setWebApplicationType(SERVLET); + app.run(args); + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/contracts/build.gradle b/Accounts/tictacthor/contracts/build.gradle index 818da95d..1ce77e8d 100644 --- a/Accounts/tictacthor/contracts/build.gradle +++ b/Accounts/tictacthor/contracts/build.gradle @@ -32,6 +32,6 @@ dependencies { cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" cordaRuntime "$corda_release_group:corda:$corda_release_version" testCompile "$corda_release_group:corda-node-driver:$corda_release_version" - // Token Account dependencies. + // Account dependencies. cordaCompile "$accounts_release_group:accounts-contracts:$accounts_release_version" } \ No newline at end of file diff --git a/Accounts/tictacthor/contracts/src/main/java/com/tictacthor/contracts/BoardContract.java b/Accounts/tictacthor/contracts/src/main/java/com/tictacthor/contracts/BoardContract.java deleted file mode 100644 index a39c46c7..00000000 --- a/Accounts/tictacthor/contracts/src/main/java/com/tictacthor/contracts/BoardContract.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.tictacthor.contracts; - -import com.tictacthor.states.BoardState; -import kotlin.Pair; -import net.corda.core.contracts.CommandData; -import net.corda.core.contracts.CommandWithParties; -import net.corda.core.contracts.Contract; -import net.corda.core.transactions.LedgerTransaction; -import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; - -// ************ -// * Contract * -// ************ -public class BoardContract implements Contract { - // This is used to identify our contract when building a transaction. - public static final String ID = "com.tictacthor.contracts.BoardContract"; - - // A transaction is valid if the verify() function of the contract of all the transaction's input and output states - // does not throw an exception. - @Override - public void verify(LedgerTransaction tx) { - - /* We can use the requireSingleCommand function to extract command data from transaction. - * However, it is possible to have multiple commands in a signle transaction.*/ - final CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.class); - final Commands commandData = command.getValue(); - - } - - // Used to indicate the transaction's intent. - public interface Commands extends CommandData { - //In our hello-world app, We will only have one command. - class StartGame implements Commands {} - class SubmitTurn implements Commands {} - class EndGame implements Commands {} - - } - - - public static class BoardUtils{ - public static Boolean isGameOver(char[][] board){ - return (board[0][0] == board [0][1] && board[0][0] == board [0][2] && (board[0][0] == 'X' || board[0][0] == 'O')) || - (board[0][0] == board [1][1] && board[0][0] == board [2][2]&& (board[0][0] == 'X' || board[0][0] == 'O')) || - (board[0][0] == board [1][0] && board[0][0] == board [2][0]&& (board[0][0] == 'X' || board[0][0] == 'O')) || - (board[2][0] == board [2][1] && board[2][0] == board [2][2]&& (board[2][0] == 'X' || board[2][0] == 'O')) || - (board[2][0] == board [1][1] && board[0][0] == board [0][2]&& (board[2][0] == 'X' || board[2][0] == 'O')) || - (board[0][2] == board [1][2] && board[0][2] == board [2][2]&& (board[0][2] == 'X' || board[0][2] == 'O')) || - (board[0][1] == board [1][1] && board[0][1] == board [2][1]&& (board[0][1] == 'X' || board[0][1] == 'O')) || - (board[1][0] == board [1][1] && board[1][0] == board [1][2]&& (board[1][0] == 'X' || board[1][0] == 'O')); - } - - } -} diff --git a/Accounts/tictacthor/contracts/src/main/java/net/corda/samples/tictacthor/contracts/BoardContract.java b/Accounts/tictacthor/contracts/src/main/java/net/corda/samples/tictacthor/contracts/BoardContract.java new file mode 100644 index 00000000..31afd2d9 --- /dev/null +++ b/Accounts/tictacthor/contracts/src/main/java/net/corda/samples/tictacthor/contracts/BoardContract.java @@ -0,0 +1,74 @@ +package net.corda.samples.tictacthor.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.ContractState; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.tictacthor.states.BoardState; + +import java.util.List; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class BoardContract implements Contract { + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.tictacthor.contracts.BoardContract"; + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(LedgerTransaction tx) { + final CommandWithParties command = requireSingleCommand(tx.getCommands(), BoardContract.Commands.class); + + List inputs = tx.getInputStates(); + List outputs = tx.getOutputStates(); + + if (command.getValue() instanceof BoardContract.Commands.StartGame) { + + // Using Corda DSL function requireThat to replicate conditions-checks + requireThat(require -> { + require.using("No inputs should be consumed when creating a new Invoice State.", inputs.isEmpty()); + require.using("Transaction must have exactly one output.", outputs.size() == 1); + BoardState output = (BoardState) outputs.get(0); + require.using("Output board must have status GAME_IN_PROGRESS", output.getStatus() == BoardState.Status.GAME_IN_PROGRESS); + require.using("You cannot play a game with yourself.", output.getPlayerO() != output.getPlayerX()); + return null; + }); + + } else if (command.getValue() instanceof BoardContract.Commands.SubmitTurn){ + + }else if (command.getValue() instanceof BoardContract.Commands.EndGame){ + + }else{ + throw new IllegalArgumentException("Command not found!"); + } + + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + //In our hello-world app, We will only have one command. + class StartGame implements Commands {} + class SubmitTurn implements Commands {} + class EndGame implements Commands {} + } + + public static class BoardUtils{ + public static Boolean isGameOver(char[][] board){ + return (board[0][0] == board [0][1] && board[0][0] == board [0][2] && (board[0][0] == 'X' || board[0][0] == 'O')) || + (board[0][0] == board [1][1] && board[0][0] == board [2][2]&& (board[0][0] == 'X' || board[0][0] == 'O')) || + (board[0][0] == board [1][0] && board[0][0] == board [2][0]&& (board[0][0] == 'X' || board[0][0] == 'O')) || + (board[2][0] == board [2][1] && board[2][0] == board [2][2]&& (board[2][0] == 'X' || board[2][0] == 'O')) || + (board[2][0] == board [1][1] && board[0][0] == board [0][2]&& (board[2][0] == 'X' || board[2][0] == 'O')) || + (board[0][2] == board [1][2] && board[0][2] == board [2][2]&& (board[0][2] == 'X' || board[0][2] == 'O')) || + (board[0][1] == board [1][1] && board[0][1] == board [2][1]&& (board[0][1] == 'X' || board[0][1] == 'O')) || + (board[1][0] == board [1][1] && board[1][0] == board [1][2]&& (board[1][0] == 'X' || board[1][0] == 'O')); + } + + } +} diff --git a/Accounts/tictacthor/contracts/src/main/java/com/tictacthor/states/BoardState.java b/Accounts/tictacthor/contracts/src/main/java/net/corda/samples/tictacthor/states/BoardState.java similarity index 93% rename from Accounts/tictacthor/contracts/src/main/java/com/tictacthor/states/BoardState.java rename to Accounts/tictacthor/contracts/src/main/java/net/corda/samples/tictacthor/states/BoardState.java index 23c0fc90..51dce4ec 100644 --- a/Accounts/tictacthor/contracts/src/main/java/com/tictacthor/states/BoardState.java +++ b/Accounts/tictacthor/contracts/src/main/java/net/corda/samples/tictacthor/states/BoardState.java @@ -1,7 +1,9 @@ -package com.tictacthor.states; +package net.corda.samples.tictacthor.states; -import com.tictacthor.contracts.BoardContract; -import javafx.util.Pair; +import net.corda.samples.tictacthor.contracts.BoardContract; +//import javafx.util.Pair; + +import kotlin.Pair; import net.corda.core.contracts.BelongsToContract; import net.corda.core.contracts.LinearState; import net.corda.core.contracts.UniqueIdentifier; @@ -91,14 +93,14 @@ public char[][] deepCopy(){ } public BoardState returnNewBoardAfterMove(Pair pos, AnonymousParty me, AnonymousParty competitor){ - if((pos.getKey() > 2) ||(pos.getValue()> 2)){ + if((pos.getFirst() > 2) ||(pos.getSecond()> 2)){ throw new IllegalStateException("Invalid board index."); } char[][] newborad = this.deepCopy(); if(isPlayerXTurn){ - newborad[pos.getKey()][pos.getValue()] = 'X'; + newborad[pos.getFirst()][pos.getSecond()] = 'X'; }else{ - newborad[pos.getKey()][pos.getValue()] = 'O'; + newborad[pos.getFirst()][pos.getSecond()] = 'O'; } if(BoardContract.BoardUtils.isGameOver(newborad)){ BoardState b = new BoardState(this.playerO,this.playerX,me,competitor,!this.isPlayerXTurn,this.linearId, newborad,Status.GAME_OVER); @@ -111,7 +113,6 @@ public BoardState returnNewBoardAfterMove(Pair pos, AnonymousPa //getter setter - public UniqueIdentifier getPlayerO() { return playerO; } diff --git a/Accounts/tictacthor/contracts/src/test/java/com/tictacthor/contracts/ContractTests.java b/Accounts/tictacthor/contracts/src/test/java/com/tictacthor/contracts/ContractTests.java deleted file mode 100644 index f05ab9c3..00000000 --- a/Accounts/tictacthor/contracts/src/test/java/com/tictacthor/contracts/ContractTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.tictacthor.contracts; - -import net.corda.testing.node.MockServices; -import org.junit.Test; - -public class ContractTests { - private final MockServices ledgerServices = new MockServices(); - - @Test - public void dummyTest() { - - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/contracts/src/test/java/net/corda/samples/tictacthor/contracts/ContractTests.java b/Accounts/tictacthor/contracts/src/test/java/net/corda/samples/tictacthor/contracts/ContractTests.java new file mode 100644 index 00000000..94f0d1ae --- /dev/null +++ b/Accounts/tictacthor/contracts/src/test/java/net/corda/samples/tictacthor/contracts/ContractTests.java @@ -0,0 +1,41 @@ +package net.corda.samples.tictacthor.contracts; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.tictacthor.states.BoardState; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.UUID; + +import static net.corda.testing.node.NodeTestUtils.ledger; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(); + TestIdentity Operator = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + TestIdentity Operator2 = new TestIdentity(new CordaX500Name("Bob", "TestLand", "US")); + + @Test + public void GameCanOnlyCreatedWhenTwoDifferentPlayerPresented() { + UniqueIdentifier playerX = new UniqueIdentifier(); + BoardState tokenPass = new BoardState(new UniqueIdentifier(),new UniqueIdentifier(), + new AnonymousParty(Operator.getPublicKey()),new AnonymousParty(Operator2.getPublicKey())); + BoardState tokenfail = new BoardState(playerX,playerX, + new AnonymousParty(Operator.getPublicKey()),new AnonymousParty(Operator2.getPublicKey())); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.output(BoardContract.ID, tokenfail); + tx.command(Operator.getPublicKey(), new BoardContract.Commands.StartGame()); // Wrong type. + return tx.fails(); + }); + l.transaction(tx -> { + tx.output(BoardContract.ID, tokenPass); + tx.command(Operator.getPublicKey(), new BoardContract.Commands.StartGame()); // Wrong type. + return tx.verifies(); + }); + return null; + }); + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/contracts/src/test/java/net/corda/samples/tictacthor/contracts/StateTests.java b/Accounts/tictacthor/contracts/src/test/java/net/corda/samples/tictacthor/contracts/StateTests.java new file mode 100644 index 00000000..00602130 --- /dev/null +++ b/Accounts/tictacthor/contracts/src/test/java/net/corda/samples/tictacthor/contracts/StateTests.java @@ -0,0 +1,18 @@ +package net.corda.samples.tictacthor.contracts; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.samples.tictacthor.states.BoardState; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class StateTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void hasFieldOfCorrectType() throws NoSuchFieldException { + // Does the message field exist? + BoardState.class.getDeclaredField("playerO"); + // Is the message field of the correct type? + assert(BoardState.class.getDeclaredField("playerO").getType().equals(UniqueIdentifier.class)); + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/gradle.properties b/Accounts/tictacthor/gradle.properties index ec9fcb16..47e31d39 100644 --- a/Accounts/tictacthor/gradle.properties +++ b/Accounts/tictacthor/gradle.properties @@ -1,3 +1,3 @@ -name=Test +name=Tictacthor Cordapp group=com.tictacthor -version=0.1 \ No newline at end of file +version=1.0 \ No newline at end of file diff --git a/Accounts/tictacthor/gradle/wrapper/gradle-wrapper.properties b/Accounts/tictacthor/gradle/wrapper/gradle-wrapper.properties index 2a2a3a8e..ae01072d 100644 --- a/Accounts/tictacthor/gradle/wrapper/gradle-wrapper.properties +++ b/Accounts/tictacthor/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/Accounts/tictacthor/repositories.gradle b/Accounts/tictacthor/repositories.gradle index 2874c2ab..8be7b630 100644 --- a/Accounts/tictacthor/repositories.gradle +++ b/Accounts/tictacthor/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Accounts/tictacthor/runServers.sh b/Accounts/tictacthor/runServers.sh new file mode 100644 index 00000000..0807fb1f --- /dev/null +++ b/Accounts/tictacthor/runServers.sh @@ -0,0 +1,3 @@ +#!/bin/sh +./gradlew bootRunDevRel & +./gradlew bootRunSOE diff --git a/Accounts/tictacthor/workflows/build.gradle b/Accounts/tictacthor/workflows/build.gradle index 5d34b3e5..e6adce24 100644 --- a/Accounts/tictacthor/workflows/build.gradle +++ b/Accounts/tictacthor/workflows/build.gradle @@ -57,6 +57,7 @@ dependencies { // CorDapp dependencies. cordapp project(":contracts") + //Account dependencies cordaCompile "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" cordaCompile "$accounts_release_group:accounts-workflows:$accounts_release_version" diff --git a/Accounts/tictacthor/workflows/src/integrationTest/java/com/tictacthor/DriverBasedTest.java b/Accounts/tictacthor/workflows/src/integrationTest/java/com/tictacthor/DriverBasedTest.java deleted file mode 100644 index b27be9da..00000000 --- a/Accounts/tictacthor/workflows/src/integrationTest/java/com/tictacthor/DriverBasedTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.tictacthor; - -import com.google.common.collect.ImmutableList; -import net.corda.core.concurrent.CordaFuture; -import net.corda.core.identity.CordaX500Name; -import net.corda.testing.core.TestIdentity; -import net.corda.testing.driver.DriverParameters; -import net.corda.testing.driver.NodeHandle; -import net.corda.testing.driver.NodeParameters; -import org.junit.Test; - -import java.util.List; - -import static net.corda.testing.driver.Driver.driver; -import static org.junit.Assert.assertEquals; - -public class DriverBasedTest { - private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); - private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); - - @Test - public void nodeTest() { - driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { - // Start a pair of nodes and wait for them both to be ready. - List> handleFutures = ImmutableList.of( - dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), - dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) - ); - - try { - NodeHandle partyAHandle = handleFutures.get(0).get(); - NodeHandle partyBHandle = handleFutures.get(1).get(); - - // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the - // nodes have started and can communicate. - - // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault - // and other important metrics to ensure that your CorDapp is working as intended. - assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); - assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); - } catch (Exception e) { - throw new RuntimeException("Caught exception during test: ", e); - } - - return null; - }); - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/integrationTest/java/net/corda/samples/tictacthor/DriverBasedTest.java b/Accounts/tictacthor/workflows/src/integrationTest/java/net/corda/samples/tictacthor/DriverBasedTest.java new file mode 100644 index 00000000..ac0c2c26 --- /dev/null +++ b/Accounts/tictacthor/workflows/src/integrationTest/java/net/corda/samples/tictacthor/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.tictacthor; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/CreateNewAccount.java b/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/CreateNewAccount.java deleted file mode 100644 index 3ea3e355..00000000 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/CreateNewAccount.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.tictacthor.accountUtilities; - -import com.r3.corda.lib.accounts.contracts.states.AccountInfo; -import com.r3.corda.lib.accounts.workflows.*; -import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; -import net.corda.core.contracts.StateAndRef; -import net.corda.core.flows.*; -import co.paralleluniverse.fibers.Suspendable; -import com.r3.corda.lib.accounts.workflows.services.AccountService; -import net.corda.core.flows.FlowLogic;; -import net.corda.core.flows.StartableByRPC; - -import java.util.UUID; - -@StartableByRPC -@StartableByService -@InitiatingFlow -public class CreateNewAccount extends FlowLogic{ - - private String acctName; - - public CreateNewAccount(String acctName) { - this.acctName = acctName; - } - - - @Override - public String call() throws FlowException { - StateAndRef newAccount = null; - try { - newAccount = getServiceHub().cordaService(KeyManagementBackedAccountService.class).createAccount(acctName).get(); - } catch (Exception e) { - e.printStackTrace(); - } - AccountInfo acct = newAccount.getState().getData(); - return "" + acct.getName() + " team's account was created. UUID is : " + acct.getIdentifier(); - } -} diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/NewKeyForAccount.java b/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/NewKeyForAccount.java deleted file mode 100644 index 8e5f24b2..00000000 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/NewKeyForAccount.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.tictacthor.accountUtilities; - -import co.paralleluniverse.fibers.Suspendable; -import net.corda.core.flows.FlowException; -import net.corda.core.flows.FlowLogic; -import net.corda.core.flows.StartableByRPC; -import net.corda.core.flows.StartableByService; -import net.corda.core.identity.PartyAndCertificate; -import java.util.*; - -@StartableByRPC -@StartableByService -public class NewKeyForAccount extends FlowLogic{ - - private final UUID accountID; - - public NewKeyForAccount(UUID accountID) { - this.accountID = accountID; - } - - @Override - @Suspendable - public PartyAndCertificate call() throws FlowException { - return getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false, accountID); - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/ShareAccountTo.java b/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/ShareAccountTo.java deleted file mode 100644 index 401bef21..00000000 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/ShareAccountTo.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tictacthor.accountUtilities; - -import co.paralleluniverse.fibers.Suspendable; -import com.r3.corda.lib.accounts.contracts.states.AccountInfo; -import com.r3.corda.lib.accounts.workflows.flows.AccountInfoByName; -import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; -import net.corda.core.contracts.StateAndRef; -import net.corda.core.flows.FlowException; -import net.corda.core.flows.FlowLogic; -import net.corda.core.flows.StartableByRPC; -import net.corda.core.flows.StartableByService; -import net.corda.core.identity.Party; -import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; -import java.util.*; - -@StartableByRPC -@StartableByService -public class ShareAccountTo extends FlowLogic{ - - private final Party shareTo; - private final String acctNameShared; - - public ShareAccountTo(String acctNameShared, Party shareTo) { - this.acctNameShared = acctNameShared; - this.shareTo = shareTo; - } - - @Override - @Suspendable - public String call() throws FlowException { - List> allmyAccounts = getServiceHub().cordaService(KeyManagementBackedAccountService.class).ourAccounts(); - StateAndRef SharedAccount = allmyAccounts.stream() - .filter(it -> it.getState().getData().getName().equals(acctNameShared)) - .findAny().get(); - - subFlow(new ShareAccountInfo(SharedAccount, Arrays.asList(shareTo))); - return "Shared " + acctNameShared + " with " + shareTo.getName().getOrganisation(); - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/StartGameFlow.java b/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/StartGameFlow.java deleted file mode 100644 index 8f0af663..00000000 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/StartGameFlow.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.tictacthor.flows; - -import co.paralleluniverse.fibers.Suspendable; -import com.r3.corda.lib.accounts.contracts.states.AccountInfo; -import com.r3.corda.lib.accounts.workflows.services.AccountService; -import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; -import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; -import com.sun.istack.NotNull; -import com.tictacthor.accountUtilities.NewKeyForAccount; -import com.tictacthor.contracts.BoardContract; -import com.tictacthor.states.BoardState; -import net.corda.core.contracts.StateAndRef; -import net.corda.core.contracts.UniqueIdentifier; -import net.corda.core.crypto.TransactionSignature; -import net.corda.core.flows.*; -import net.corda.core.identity.AnonymousParty; -import net.corda.core.node.services.vault.QueryCriteria; -import net.corda.core.transactions.SignedTransaction; -import net.corda.core.transactions.TransactionBuilder; -import net.corda.core.utilities.ProgressTracker; -import java.security.PublicKey; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -// ****************** -// * Initiator flow * -// ****************** -@InitiatingFlow -@StartableByRPC -public class StartGameFlow extends FlowLogic { - - private final ProgressTracker progressTracker = tracker(); - - private static final ProgressTracker.Step GENERATING_TRANSACTION = new ProgressTracker.Step("Generating a HeartState transaction"); - private static final ProgressTracker.Step SIGNING_TRANSACTION = new ProgressTracker.Step("Signing transaction with our private key."); - private static final ProgressTracker.Step FINALISING_TRANSACTION = new ProgressTracker.Step("Recording transaction") { - @Override - public ProgressTracker childProgressTracker() { - return FinalityFlow.tracker(); - } - }; - - private static ProgressTracker tracker() { - return new ProgressTracker( - GENERATING_TRANSACTION, - SIGNING_TRANSACTION, - FINALISING_TRANSACTION - ); - } - - @Override - public ProgressTracker getProgressTracker() { - return progressTracker; - } - - //private variables - private String whoAmI ; - private String whereTo; - - //public constructor - public StartGameFlow(String whoAmI, String whereTo){ - this.whoAmI = whoAmI; - this.whereTo = whereTo; - } - - @Suspendable - @Override - public UniqueIdentifier call() throws FlowException { - //grab account service - AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); - //grab the account information - AccountInfo myAccount = accountService.accountInfo(whoAmI).get(0).getState().getData(); - PublicKey myKey = subFlow(new NewKeyForAccount(myAccount.getIdentifier().getId())).getOwningKey(); - - AccountInfo targetAccount = accountService.accountInfo(whereTo).get(0).getState().getData(); - AnonymousParty targetAcctAnonymousParty = subFlow(new RequestKeyForAccount(targetAccount)); - - //check if this account is in another game - QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withExternalIds(Arrays.asList(myAccount.getIdentifier().getId())); - List> results = getServiceHub().getVaultService().queryBy(BoardState.class,criteria).getStates(); - if(results.size() != 0){ - throw new IllegalArgumentException("You are in another game"); - } - - progressTracker.setCurrentStep(GENERATING_TRANSACTION); - //generating State for transfer - BoardState initialBoardState = new BoardState(myAccount.getIdentifier(), - targetAccount.getIdentifier(), - new AnonymousParty(myKey), - targetAcctAnonymousParty); - - TransactionBuilder txbuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0)) - .addOutputState(initialBoardState) - .addCommand(new BoardContract.Commands.StartGame(),Arrays.asList(myKey,targetAcctAnonymousParty.getOwningKey())); - - - progressTracker.setCurrentStep(SIGNING_TRANSACTION); - //self verify and sign Transaction - txbuilder.verify(getServiceHub()); - SignedTransaction locallySignedTx = getServiceHub().signInitialTransaction(txbuilder,Arrays.asList(getOurIdentity().getOwningKey(),myKey)); - - //Collect sigs - FlowSession sessionForAccountToSendTo = initiateFlow(targetAccount.getHost()); - List accountToMoveToSignature = (List) subFlow(new CollectSignatureFlow(locallySignedTx, - sessionForAccountToSendTo,targetAcctAnonymousParty.getOwningKey())); - SignedTransaction signedByCounterParty = locallySignedTx.withAdditionalSignatures(accountToMoveToSignature); - progressTracker.setCurrentStep(FINALISING_TRANSACTION); - //Finalize - subFlow(new FinalityFlow(signedByCounterParty, - Arrays.asList(sessionForAccountToSendTo).stream().filter(it -> it.getCounterparty() != getOurIdentity()).collect(Collectors.toList()))); - return initialBoardState.getLinearId(); - } -} - - -@InitiatedBy(StartGameFlow.class) -class StartGameFlowResponder extends FlowLogic { - //private variable - private FlowSession counterpartySession; - - //Constructor - public StartGameFlowResponder(FlowSession counterpartySession) { - this.counterpartySession = counterpartySession; - } - - @Override - @Suspendable - public Void call() throws FlowException { - subFlow(new SignTransactionFlow(counterpartySession) { - @Override - protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { - // Custom Logic to validate transaction. - } - }); - subFlow(new ReceiveFinalityFlow(counterpartySession)); - return null; - } -} - diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/SyncGame.java b/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/SyncGame.java deleted file mode 100644 index cd0c7f7a..00000000 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/SyncGame.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.tictacthor.flows; - -import co.paralleluniverse.fibers.Suspendable; -import com.r3.corda.lib.accounts.contracts.states.AccountInfo; -import com.r3.corda.lib.accounts.workflows.flows.AccountInfoByName; -import com.r3.corda.lib.accounts.workflows.services.AccountService; -import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; -import com.tictacthor.states.BoardState; -import net.corda.core.contracts.StateAndRef; -import net.corda.core.contracts.UniqueIdentifier; -import net.corda.core.flows.FlowException; -import net.corda.core.flows.FlowLogic; -import net.corda.core.flows.StartableByRPC; -import net.corda.core.flows.StartableByService; -import net.corda.core.identity.Party; -import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; -import net.corda.core.node.services.Vault; -import net.corda.core.node.services.vault.QueryCriteria; -import com.r3.corda.lib.accounts.workflows.flows.ShareStateAndSyncAccounts; - -import java.util.*; -import java.util.stream.Collectors; - -@StartableByRPC -@StartableByService -public class SyncGame extends FlowLogic{ - private String gameId; - private Party party; - - public SyncGame(String gameId, Party party) { - this.gameId = gameId; - this.party = party; - } - - @Override - @Suspendable - public String call() throws FlowException { - - UUID id = UUID.fromString(gameId); - QueryCriteria.LinearStateQueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria() - .withUuid(Arrays.asList(id)).withStatus(Vault.StateStatus.UNCONSUMED); - try { - StateAndRef inputBoardStateAndRef = getServiceHub().getVaultService().queryBy(BoardState.class,queryCriteria).getStates().get(0); - subFlow(new ShareStateAndSyncAccounts(inputBoardStateAndRef,party)); - - }catch (Exception e){ - throw new FlowException("GameState with id "+gameId+" not found."); - } - return "Game synced"; - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/CreateNewAccount.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/CreateNewAccount.java new file mode 100644 index 00000000..e4a2c7c2 --- /dev/null +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/CreateNewAccount.java @@ -0,0 +1,38 @@ +package net.corda.samples.tictacthor.accountUtilities; + +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.*; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import net.corda.core.flows.FlowLogic;; +import net.corda.core.flows.StartableByRPC; + +import java.util.UUID; + +@StartableByRPC +@StartableByService +@InitiatingFlow +public class CreateNewAccount extends FlowLogic{ + + private String acctName; + + public CreateNewAccount(String acctName) { + this.acctName = acctName; + } + + + @Override + public String call() throws FlowException { + StateAndRef newAccount = null; + try { + newAccount = getServiceHub().cordaService(KeyManagementBackedAccountService.class).createAccount(acctName).get(); + } catch (Exception e) { + e.printStackTrace(); + } + AccountInfo acct = newAccount.getState().getData(); + return "" + acct.getName() + " team's account was created. UUID is : " + acct.getIdentifier(); + } +} diff --git a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/NewKeyForAccount.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/NewKeyForAccount.java new file mode 100644 index 00000000..d94f3bf2 --- /dev/null +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/NewKeyForAccount.java @@ -0,0 +1,26 @@ +package net.corda.samples.tictacthor.accountUtilities; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.flows.StartableByService; +import net.corda.core.identity.PartyAndCertificate; +import java.util.*; + +@StartableByRPC +@StartableByService +public class NewKeyForAccount extends FlowLogic{ + + private final UUID accountID; + + public NewKeyForAccount(UUID accountID) { + this.accountID = accountID; + } + + @Override + @Suspendable + public PartyAndCertificate call() throws FlowException { + return getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false, accountID); + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/ShareAccountTo.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/ShareAccountTo.java new file mode 100644 index 00000000..0132589b --- /dev/null +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/ShareAccountTo.java @@ -0,0 +1,39 @@ +package net.corda.samples.tictacthor.accountUtilities; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.flows.AccountInfoByName; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.flows.StartableByService; +import net.corda.core.identity.Party; +import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; +import java.util.*; + +@StartableByRPC +@StartableByService +public class ShareAccountTo extends FlowLogic{ + + private final Party shareTo; + private final String acctNameShared; + + public ShareAccountTo(String acctNameShared, Party shareTo) { + this.acctNameShared = acctNameShared; + this.shareTo = shareTo; + } + + @Override + @Suspendable + public String call() throws FlowException { + List> allmyAccounts = getServiceHub().cordaService(KeyManagementBackedAccountService.class).ourAccounts(); + StateAndRef SharedAccount = allmyAccounts.stream() + .filter(it -> it.getState().getData().getName().equals(acctNameShared)) + .findAny().get(); + + subFlow(new ShareAccountInfo(SharedAccount, Arrays.asList(shareTo))); + return "Shared " + acctNameShared + " with " + shareTo.getName().getOrganisation(); + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/ViewMyAccounts.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/ViewMyAccounts.java similarity index 95% rename from Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/ViewMyAccounts.java rename to Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/ViewMyAccounts.java index aba3cfe4..fc48ac7d 100644 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/ViewMyAccounts.java +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/ViewMyAccounts.java @@ -1,4 +1,4 @@ -package com.tictacthor.accountUtilities; +package net.corda.samples.tictacthor.accountUtilities; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/myGame.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/myGame.java similarity index 79% rename from Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/myGame.java rename to Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/myGame.java index b8f6cc6e..046751c5 100644 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/accountUtilities/myGame.java +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/accountUtilities/myGame.java @@ -1,22 +1,17 @@ -package com.tictacthor.accountUtilities; +package net.corda.samples.tictacthor.accountUtilities; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; -import com.r3.corda.lib.accounts.workflows.flows.AccountInfoByName; import com.r3.corda.lib.accounts.workflows.services.AccountService; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; -import com.tictacthor.states.BoardState; -import net.corda.core.contracts.StateAndRef; +import net.corda.samples.tictacthor.states.BoardState; import net.corda.core.flows.FlowException; import net.corda.core.flows.FlowLogic; import net.corda.core.flows.StartableByRPC; import net.corda.core.flows.StartableByService; -import net.corda.core.identity.Party; -import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; import net.corda.core.node.services.vault.QueryCriteria; import java.util.*; -import java.util.stream.Collectors; @StartableByRPC @StartableByService diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/EndGameFlow.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/EndGameFlow.java similarity index 89% rename from Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/EndGameFlow.java rename to Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/EndGameFlow.java index 4cbac2ba..3f9d6ab6 100644 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/EndGameFlow.java +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/EndGameFlow.java @@ -1,4 +1,4 @@ -package com.tictacthor.flows; +package net.corda.samples.tictacthor.flows; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; @@ -6,19 +6,22 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; -import com.tictacthor.accountUtilities.NewKeyForAccount; -import com.tictacthor.contracts.BoardContract; -import com.tictacthor.states.BoardState; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.tictacthor.accountUtilities.NewKeyForAccount; +import net.corda.samples.tictacthor.contracts.BoardContract; +import net.corda.samples.tictacthor.states.BoardState; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.crypto.TransactionSignature; import net.corda.core.flows.*; import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.Party; import net.corda.core.node.services.Vault; import net.corda.core.node.services.vault.QueryCriteria; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; import net.corda.core.utilities.ProgressTracker; + import java.security.PublicKey; import java.util.Arrays; import java.util.List; @@ -90,7 +93,12 @@ public String call() throws FlowException { progressTracker.setCurrentStep(GENERATING_TRANSACTION); //generating State for transfer - TransactionBuilder txbuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0)) + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + Party notary = inputBoardStateAndRef.getState().getNotary(); + + TransactionBuilder txbuilder = new TransactionBuilder(notary) .addInputState(inputBoardStateAndRef) .addCommand(new BoardContract.Commands.EndGame(),Arrays.asList(myKey,targetAcctAnonymousParty.getOwningKey())); diff --git a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/StartGameFlow.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/StartGameFlow.java new file mode 100644 index 00000000..76ba4367 --- /dev/null +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/StartGameFlow.java @@ -0,0 +1,147 @@ +package net.corda.samples.tictacthor.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import com.sun.istack.NotNull; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.tictacthor.accountUtilities.NewKeyForAccount; +import net.corda.samples.tictacthor.contracts.BoardContract; +import net.corda.samples.tictacthor.states.BoardState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.crypto.TransactionSignature; +import net.corda.core.flows.*; +import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.Party; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.core.utilities.ProgressTracker; + +import java.security.PublicKey; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +// ****************** +// * Initiator flow * +// ****************** +@InitiatingFlow +@StartableByRPC +public class StartGameFlow extends FlowLogic { + + private final ProgressTracker progressTracker = tracker(); + + private static final ProgressTracker.Step GENERATING_TRANSACTION = new ProgressTracker.Step("Generating a HeartState transaction"); + private static final ProgressTracker.Step SIGNING_TRANSACTION = new ProgressTracker.Step("Signing transaction with our private key."); + private static final ProgressTracker.Step FINALISING_TRANSACTION = new ProgressTracker.Step("Recording transaction") { + @Override + public ProgressTracker childProgressTracker() { + return FinalityFlow.tracker(); + } + }; + + private static ProgressTracker tracker() { + return new ProgressTracker( + GENERATING_TRANSACTION, + SIGNING_TRANSACTION, + FINALISING_TRANSACTION + ); + } + + @Override + public ProgressTracker getProgressTracker() { + return progressTracker; + } + + //private variables + private String whoAmI ; + private String whereTo; + + //public constructor + public StartGameFlow(String whoAmI, String whereTo){ + this.whoAmI = whoAmI; + this.whereTo = whereTo; + } + + @Suspendable + @Override + public UniqueIdentifier call() throws FlowException { + //grab account service + AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); + //grab the account information + AccountInfo myAccount = accountService.accountInfo(whoAmI).get(0).getState().getData(); + PublicKey myKey = subFlow(new NewKeyForAccount(myAccount.getIdentifier().getId())).getOwningKey(); + + AccountInfo targetAccount = accountService.accountInfo(whereTo).get(0).getState().getData(); + AnonymousParty targetAcctAnonymousParty = subFlow(new RequestKeyForAccount(targetAccount)); + + //check if this account is in another game + QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withExternalIds(Arrays.asList(myAccount.getIdentifier().getId())); + List> results = getServiceHub().getVaultService().queryBy(BoardState.class,criteria).getStates(); + if(results.size() != 0){ + throw new IllegalArgumentException("You are in another game"); + } + + progressTracker.setCurrentStep(GENERATING_TRANSACTION); + //generating State for transfer + BoardState initialBoardState = new BoardState(myAccount.getIdentifier(), + targetAccount.getIdentifier(), + new AnonymousParty(myKey), + targetAcctAnonymousParty); + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + TransactionBuilder txbuilder = new TransactionBuilder(notary) + .addOutputState(initialBoardState) + .addCommand(new BoardContract.Commands.StartGame(),Arrays.asList(myKey,targetAcctAnonymousParty.getOwningKey())); + + + progressTracker.setCurrentStep(SIGNING_TRANSACTION); + //self verify and sign Transaction + txbuilder.verify(getServiceHub()); + SignedTransaction locallySignedTx = getServiceHub().signInitialTransaction(txbuilder,Arrays.asList(getOurIdentity().getOwningKey(),myKey)); + + //Collect sigs + FlowSession sessionForAccountToSendTo = initiateFlow(targetAccount.getHost()); + List accountToMoveToSignature = (List) subFlow(new CollectSignatureFlow(locallySignedTx, + sessionForAccountToSendTo,targetAcctAnonymousParty.getOwningKey())); + SignedTransaction signedByCounterParty = locallySignedTx.withAdditionalSignatures(accountToMoveToSignature); + progressTracker.setCurrentStep(FINALISING_TRANSACTION); + //Finalize + subFlow(new FinalityFlow(signedByCounterParty, + Arrays.asList(sessionForAccountToSendTo).stream().filter(it -> it.getCounterparty() != getOurIdentity()).collect(Collectors.toList()))); + return initialBoardState.getLinearId(); + } +} + + +@InitiatedBy(StartGameFlow.class) +class StartGameFlowResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public StartGameFlowResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public Void call() throws FlowException { + subFlow(new SignTransactionFlow(counterpartySession) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // Custom Logic to validate transaction. + } + }); + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } +} + diff --git a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/SubmitTurnFlow.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SubmitTurnFlow.java similarity index 92% rename from Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/SubmitTurnFlow.java rename to Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SubmitTurnFlow.java index 4bd5b464..477f3a6a 100644 --- a/Accounts/tictacthor/workflows/src/main/java/com/tictacthor/flows/SubmitTurnFlow.java +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SubmitTurnFlow.java @@ -1,4 +1,4 @@ -package com.tictacthor.flows; +package net.corda.samples.tictacthor.flows; import co.paralleluniverse.fibers.Suspendable; import com.r3.corda.lib.accounts.contracts.states.AccountInfo; @@ -6,24 +6,26 @@ import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; import com.sun.istack.NotNull; -import com.tictacthor.accountUtilities.NewKeyForAccount; -import com.tictacthor.contracts.BoardContract; -import com.tictacthor.states.BoardState; -import javafx.util.Pair; +import net.corda.samples.tictacthor.accountUtilities.NewKeyForAccount; +import net.corda.samples.tictacthor.contracts.BoardContract; +import net.corda.samples.tictacthor.states.BoardState; +//import javafx.util.Pair; +import kotlin.Pair; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.crypto.TransactionSignature; import net.corda.core.flows.*; import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.Party; import net.corda.core.node.services.Vault; import net.corda.core.node.services.vault.QueryCriteria; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; import net.corda.core.utilities.ProgressTracker; + import java.security.PublicKey; import java.util.Arrays; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; // ****************** @@ -102,7 +104,11 @@ public String call() throws FlowException { progressTracker.setCurrentStep(GENERATING_TRANSACTION); //generating State for transfer BoardState outputBoardState = inputBoardState.returnNewBoardAfterMove(new Pair<>(x,y),new AnonymousParty(myKey), targetAcctAnonymousParty); - TransactionBuilder txbuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0)) + + // Obtain a reference to a notary we wish to use. + Party notary = inputBoardStateAndRef.getState().getNotary(); + + TransactionBuilder txbuilder = new TransactionBuilder(notary) .addInputState(inputBoardStateAndRef) .addOutputState(outputBoardState) .addCommand(new BoardContract.Commands.SubmitTurn(),Arrays.asList(myKey,targetAcctAnonymousParty.getOwningKey())); diff --git a/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SyncGame.java b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SyncGame.java new file mode 100644 index 00000000..2703fc3a --- /dev/null +++ b/Accounts/tictacthor/workflows/src/main/java/net/corda/samples/tictacthor/flows/SyncGame.java @@ -0,0 +1,44 @@ +package net.corda.samples.tictacthor.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.samples.tictacthor.states.BoardState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.flows.StartableByService; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import com.r3.corda.lib.accounts.workflows.flows.ShareStateAndSyncAccounts; + +import java.util.*; + +@StartableByRPC +@StartableByService +public class SyncGame extends FlowLogic{ + private String gameId; + private Party party; + + public SyncGame(String gameId, Party party) { + this.gameId = gameId; + this.party = party; + } + + @Override + @Suspendable + public String call() throws FlowException { + + UUID id = UUID.fromString(gameId); + QueryCriteria.LinearStateQueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(id)).withStatus(Vault.StateStatus.UNCONSUMED); + try { + StateAndRef inputBoardStateAndRef = getServiceHub().getVaultService().queryBy(BoardState.class,queryCriteria).getStates().get(0); + subFlow(new ShareStateAndSyncAccounts(inputBoardStateAndRef,party)); + + }catch (Exception e){ + throw new FlowException("GameState with id "+gameId+" not found."); + } + return "Game synced"; + } +} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/ContractTests.java b/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/ContractTests.java deleted file mode 100644 index 8f97c2d1..00000000 --- a/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/ContractTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.tictacthor; - -import net.corda.testing.node.MockServices; -import org.junit.Test; - -public class ContractTests { - private final MockServices ledgerServices = new MockServices(); - - @Test - public void dummyTest() { - - } -} \ No newline at end of file diff --git a/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/FlowTests.java b/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/FlowTests.java deleted file mode 100644 index 4590d2db..00000000 --- a/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/FlowTests.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.tictacthor; - -import com.google.common.collect.ImmutableList; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.StartedMockNode; -import net.corda.testing.node.TestCordapp; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class FlowTests { - private final MockNetwork network = new MockNetwork(new MockNetworkParameters(ImmutableList.of( - TestCordapp.findCordapp("com.tictacthor.contracts"), - TestCordapp.findCordapp("com.tictacthor.flows") - ))); - private final StartedMockNode a = network.createNode(); - private final StartedMockNode b = network.createNode(); - - public FlowTests() { - } - - @Before - public void setup() { - network.runNetwork(); - } - - @After - public void tearDown() { - network.stopNodes(); - } - - @Test - public void dummyTest() { - - } -} diff --git a/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/NodeDriver.java b/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/NodeDriver.java deleted file mode 100644 index 3a4a2613..00000000 --- a/Accounts/tictacthor/workflows/src/test/java/com/tictacthor/NodeDriver.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tictacthor; - -import com.google.common.collect.ImmutableList; -import net.corda.core.identity.CordaX500Name; -import net.corda.testing.driver.DriverParameters; -import net.corda.testing.driver.NodeParameters; -import net.corda.testing.node.User; -import com.google.common.collect.ImmutableSet; - -import java.util.List; -import static net.corda.testing.driver.Driver.driver; - -/** - * Allows you to run your nodes through an IDE (as opposed to using deployNodes). Do not use in a production - * environment. - */ -public class NodeDriver { - public static void main(String[] args) { - final List rpcUsers = - ImmutableList.of(new User("user1", "test", ImmutableSet.of("ALL"))); - - driver(new DriverParameters().withStartNodesInProcess(true).withWaitForAllNodesToFinish(true), dsl -> { - try { - dsl.startNode(new NodeParameters() - .withProvidedName(new CordaX500Name("PartyA", "London", "GB")) - .withRpcUsers(rpcUsers)).get(); - dsl.startNode(new NodeParameters() - .withProvidedName(new CordaX500Name("PartyB", "New York", "US")) - .withRpcUsers(rpcUsers)).get(); - } catch (Throwable e) { - System.err.println("Encountered exception in node startup: " + e.getMessage()); - e.printStackTrace(); - } - - return null; - } - ); - } -} diff --git a/Accounts/tictacthor/workflows/src/test/java/net/corda/samples/tictacthor/FlowTests.java b/Accounts/tictacthor/workflows/src/test/java/net/corda/samples/tictacthor/FlowTests.java new file mode 100644 index 00000000..71f10d38 --- /dev/null +++ b/Accounts/tictacthor/workflows/src/test/java/net/corda/samples/tictacthor/FlowTests.java @@ -0,0 +1,95 @@ +package net.corda.samples.tictacthor; + +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.NetworkParameters; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.samples.tictacthor.accountUtilities.CreateNewAccount; +import net.corda.samples.tictacthor.accountUtilities.ShareAccountTo; +import net.corda.samples.tictacthor.flows.StartGameFlow; +import net.corda.samples.tictacthor.states.BoardState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; +import java.time.Instant; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + + private NetworkParameters testNetworkParameters = + new NetworkParameters(4, Arrays.asList(), 10485760, (10485760 * 5), Instant.now(),1, new LinkedHashMap<>()); + + @Before + public void setup() { + + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.tictacthor.contracts"), + TestCordapp.findCordapp("net.corda.samples.tictacthor.flows"), + TestCordapp.findCordapp("com.r3.corda.lib.accounts.contracts"), + TestCordapp.findCordapp("com.r3.corda.lib.accounts.workflows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void AccountCreation() throws ExecutionException, InterruptedException { + CreateNewAccount createAcct = new CreateNewAccount("TestAccountA"); + Future future = a.startFlow(createAcct); + network.runNetwork(); + AccountService accountService = a.getServices().cordaService(KeyManagementBackedAccountService.class); + List> myAccount = accountService.accountInfo("TestAccountA"); + assert (myAccount.size() != 0); + } + + @Test + public void CreateGameTest() throws ExecutionException, InterruptedException { + CreateNewAccount createAcct = new CreateNewAccount("TestAccountA"); + Future future = a.startFlow(createAcct); + network.runNetwork(); + ShareAccountTo shareAToB = new ShareAccountTo("TestAccountA",b.getInfo().getLegalIdentities().get(0)); + Future future2 = a.startFlow(shareAToB); + network.runNetwork(); + + CreateNewAccount createAcct2 = new CreateNewAccount("TestAccountB"); + Future future3 = b.startFlow(createAcct2); + network.runNetwork(); + + ShareAccountTo shareBToA = new ShareAccountTo("TestAccountB",a.getInfo().getLegalIdentities().get(0)); + Future future4 = b.startFlow(shareBToA); + network.runNetwork(); + + StartGameFlow startGame = new StartGameFlow("TestAccountA","TestAccountB"); + Future future5 = a.startFlow(startGame); + network.runNetwork(); + + AccountService accountService = b.getServices().cordaService(KeyManagementBackedAccountService.class); + AccountInfo myAccount = accountService.accountInfo("TestAccountB").get(0).getState().getData(); + QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withExternalIds(Arrays.asList(myAccount.getIdentifier().getId())); + List> storedGame = b.getServices().getVaultService().queryBy(BoardState.class,criteria).getStates(); + assert (storedGame.size() != 0); + + } + + +} diff --git a/Accounts/worldcupticketbooking/README.md b/Accounts/worldcupticketbooking/README.md index e74cf618..324533db 100644 --- a/Accounts/worldcupticketbooking/README.md +++ b/Accounts/worldcupticketbooking/README.md @@ -1,9 +1,9 @@ -# T20 Cricket World Cup Ticket Booking Cordapp +# T20 Cricket World Cup Ticket Booking CorDapp ## Introduction This sample shows you how to integrate accounts and tokens. This sample talks about a scenario where typically when the Cricket season starts, BCCI (Board of Control for Cricket) starts selling tickets. -As of now there are multiple dealers whom the BCCI issues tickets and further these dealers sell tickets to their client. We are trying to simulate similar functionality maintaining the entore issuance and selling +As of now there are multiple dealers whom the BCCI issues tickets and further these dealers sell tickets to their client. We are trying to simulate similar functionality maintaining the entire issuance and selling of the tickets on Corda Platform. ## Flow logic of the sample application @@ -11,10 +11,10 @@ of the tickets on Corda Platform. Nodes: * BCCI node: source of ticket creation. All the tickets in the market are created by this node. -* Bank Node: souce of money. All the money that is transacted between each individual is issued by this node. +* Bank Node: source of money. All the money that is transacted between each individual is issued by this node. * Dealer1 Node: Dealer Agency 1 and it includes - * agent1 account: who will get ticket from the BCCI and sell to others. (To demostrate same node account token transaction.) - * buyer1 account: who will get the ticket from agent1 and later sell to buyer3 (To demostrate cross node account token transaction) + * agent1 account: who will get ticket from the BCCI and sell to others. (To demonstrate same node account token transaction.) + * buyer1 account: who will get the ticket from agent1 and later sell to buyer3 (To demonstrate cross node account token transaction) * buyer2 account: who will buy the ticket from buyer3. * Dealer2 Node: Dealer Agency 2 and it includes * buyer3 account: who will get the ticket from buyer1 and sell to buyer2 @@ -23,41 +23,49 @@ Nodes: Corda

+## Pre-Requisites + +For development environment setup, please refer to: [Setup Guide](https://docs.r3.com/en/platform/corda/4.9/community/getting-set-up.html). + +## Running the sample +Deploy and run the nodes by: +``` +./gradlew clean build deployNodes +./build/nodes/runnodes +``` ### Step 1 +Run the below flow in Dealer1's interactive node shell. This will create the agent1, buyer1 and buyer2 accounts on the Dealer1 node and share this account info with BCCI, Bank, and Dealer2 node respectively. ``` flow start CreateAndShareAccountFlow accountName: agent1, partyToShareAccountInfoToList: [BCCI, Dealer2] flow start CreateAndShareAccountFlow accountName: buyer1, partyToShareAccountInfoToList: [Bank, Dealer2] flow start CreateAndShareAccountFlow accountName: buyer2, partyToShareAccountInfoToList: [Bank, Dealer2] ``` -Run the above flow on the Dealer1 node. This will create the agent1, buyer1 and buyer2 accounts on the Dealer1 node and share this account info with BCCI, Bank, and Dealer2 node respecticely. -Then let's go to the Dealer2 node and create buyer3 account: +Then let's go to the Dealer2 interactive node shell and create buyer3 account: ``` flow start CreateAndShareAccountFlow accountName: buyer3, partyToShareAccountInfoToList: [Bank, Dealer1] ``` -Run the below query to confirm if accounts are created on Dealer1 node. Also run the above query on Bank and BCCI node to confirm if account info is shared with these nodes. +Run the below query to confirm if accounts are created on Dealer1 node. Also run the below query on Bank and BCCI's interactive node shell to confirm if account info is shared with these nodes. run vaultQuery contractStateType : com.r3.corda.lib.accounts.contracts.states.AccountInfo - - ### Step 2 +Run the below command on the Bank's interactive node shell, which will issue 20 USD to buyer1 account. ``` start IssueCashFlow accountName : buyer1 , currency : USD , amount : 10 start IssueCashFlow accountName : buyer3 , currency : USD , amount : 20 start IssueCashFlow accountName : buyer2 , currency : USD , amount : 50 ``` -Run the above command on the Bank node, which will issue 20 USD to buyer1 account. ### Step 3 ``` flow start QuerybyAccount whoAmI: buyer1 ``` -You can check balance of buyer1 account at Dealer1's node -[Option] You can also run the below command to confirm if 20 USD fungible tokens are stored at Dealer1's node. The current holder field in the output will be an AnonymousParty which specifies an account. +You can check balance of buyer1 account at Dealer1's interactive node shell. +[Option] You can also run the below command to confirm if 20 USD fungible tokens are stored at Dealer1's node. The current holder field in the output will be an [AnonymousParty](https://docs.r3.com/en/platform/corda/4.9/community/api-identity.html) which specifies an account. ``` run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken ``` @@ -65,25 +73,27 @@ run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.Fung ### Step 4 +Run the below flow on BCCI's node. BCCI node will create base token type for the T20 Ticket for the match MumbaiIndians Vs RajasthanRoyals. The ticket ID returned from this flow will be needed in the next steps. + start CreateT20CricketTicketTokenFlow ticketTeam : MumbaiIndiansVsRajasthanRoyals -Run the above flow on BCCI's node. BCCI node will create base token type for the T20 Ticket for the match MumbaiIndians Vs RajasthanRoyals. The ticket ID returned from this flow will be needed in the next steps. -You can see your ticket state generated via vault query at the BCCI'd node: +You can see your ticket state generated via vault query at the BCCI's node: run vaultQuery contractStateType : com.t20worldcup.states.T20CricketTicket ### Step 5 +Run the below flow on BCCI's node to issue a non-fungible token based off the token type which we created in Step5. You will need to replace the `` with the uuid returned from step 6. This token will be issued by the BCCI node to agent1 account on Dealer1 node. + start IssueNonFungibleTicketFlow tokenId : , dealerAccountName : agent1 -Run the above flow on BCCI's node to issue a non fungible token based off the token type which we created in Step5. You will need to replace the `` with the uuid returned from step 6. This token will be issued by the BCCI node to dealer1 account on Dealer1 node. Switching to the Dealer1's node, you can run the following code to confirm if the token has been issued to the dealer1 account. ``` flow start QuerybyAccount whoAmI: agent1 ``` -You can also look for the acutal state that is recoreded by: +You can also look for the actual state that is recorded by: run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.NonFungibleToken Note that, the current holder it will be a key representing the account. @@ -94,10 +104,10 @@ Note that, the current holder it will be a key representing the account. flow start DVPAccountsOnSameNode tokenId: , buyerAccountName: buyer1, sellerAccountName: agent1, costOfTicket: 5, currency: USD ``` -This is the DVP flow where the buyer(buyer1 account on Dealer1 node) account will pay cash to seller account(agent1 account on Dealer1 node), and the seller accountwill transfer the ticket token to the buyer. Again, replace the `` with the uuid generated in step 6. +This is the DVP flow where the buyer(buyer1 account on Dealer1 node) account will pay cash to seller account(agent1 account on Dealer1 node), and the seller account will transfer the ticket token to the buyer. Again, replace the `` with the uuid generated in step 6. ### Step 7 -Now lets continue the flow logic to intiate an ticket sale between buyer1 and buyer3. Go to Dealer2 node and run the following code: +Now let's continue the flow logic to initiate a ticket sale between buyer1 and buyer3. Go to Dealer2 node and run the following code: ``` flow start DVPAccountsHostedOnDifferentNodes tokenId: , buyerAccountName: buyer3, sellerAccountName: buyer1, costOfTicket: 10, currency: USD @@ -121,9 +131,77 @@ At Dealer2 node flow start QuerybyAccount whoAmI: buyer3 ``` -Confirm who owns the FungibleToken (cash) and NonFungibleToken (ticket) again by running this on Dealer1's node. +Confirm who owns the [FungibleToken](https://training.corda.net/libraries/tokens-sdk/#fungibletoken) (cash) and [NonFungibleToken](https://training.corda.net/libraries/tokens-sdk/#nonfungibletoken) (ticket) again by running this on Dealer1's node. + + +## Transfer tokens from one account to other + +For someone who is looking into how to only transfer tokens from one account to other use below steps. + +### Step 1 +Run the below flow on the Dealer1 node. This will create the agent1, buyer1 and buyer2 accounts on the Dealer1 node and share this account info with BCCI, Bank, and Dealer2 node respectively. + +``` +flow start CreateAndShareAccountFlow accountName: agent1, partyToShareAccountInfoToList: [BCCI, Dealer2] +flow start CreateAndShareAccountFlow accountName: buyer1, partyToShareAccountInfoToList: [Bank, Dealer2] +flow start CreateAndShareAccountFlow accountName: buyer2, partyToShareAccountInfoToList: [Bank, Dealer2] +``` + +Then let's go to the Dealer2 node and create buyer3 account: +``` +flow start CreateAndShareAccountFlow accountName: buyer3, partyToShareAccountInfoToList: [Bank, Dealer1] +``` + +Run the below query to confirm if accounts are created on Dealer1 node. Also run the below query on Bank and BCCI node to confirm if account info is shared with these nodes. + + run vaultQuery contractStateType : com.r3.corda.lib.accounts.contracts.states.AccountInfo + + +### Step 2 + +Run the below command on the Bank node, which will issue 77 USD to buyer1 account. + +``` +start IssueCashFlow accountName : buyer1 , currency : USD , amount : 77 +``` + +### Step 3 +``` +flow start QuerybyAccount whoAmI: buyer1 +``` +You can check balance of buyer1 account at Dealer1's node +[Option] You can also run the below command to confirm if 20 USD fungible tokens are stored at Dealer1's node. The current holder field in the output will be an AnonymousParty which specifies an account. +``` +run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken +``` + +### Step 4 + + start MoveTokensBetweenAccounts buyerAccountName : buyer1, sellerAccountName : buyer3 , currency : USD , costOfTicket : 10 + +This will move tokens from account buyer1 to account buyer3 + + + +### Step 5 +``` +flow start QuerybyAccount whoAmI: buyer1 +``` +You can check balance of buyer1 account at Dealer1's node +``` +run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken +``` + +### Step 6 +``` +flow start QuerybyAccount whoAmI: buyer3 +``` +You can check balance of buyer3 account at Dealer2's node +``` +run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken +``` ## Further Reading For accounts visit https://github.com/corda/accounts. diff --git a/Accounts/worldcupticketbooking/build.gradle b/Accounts/worldcupticketbooking/build.gradle index fa9a0277..e567053f 100644 --- a/Accounts/worldcupticketbooking/build.gradle +++ b/Accounts/worldcupticketbooking/build.gradle @@ -36,10 +36,10 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } - // maven { url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev' } - maven { url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } + + maven { url 'https://download.corda.net/maven/corda-releases' } + // maven { url 'http://software.r3.com/artifactory/corda-lib-dev' } + maven { url 'http://software.r3.com/artifactory/corda-lib' } } dependencies { @@ -57,12 +57,12 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } // Can be removed post-release - used to get nightly snapshot build. - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev' } + maven { url 'https://download.corda.net/maven/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib-dev' } // maven { url 'https://jitpack.io' } maven { url "https://repo.gradle.org/gradle/libs-releases-local" } } @@ -107,6 +107,7 @@ dependencies { cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaDriver "net.corda:corda-shell:4.10" //accounts cordapp "$accounts_release_group:accounts-contracts:$accounts_release_version" @@ -163,6 +164,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { cordapp("$tokens_release_group:tokens-workflows:$tokens_release_version") cordapp("$tokens_release_group:tokens-money:$tokens_release_version") cordapp("$tokens_release_group:tokens-selection:$tokens_release_version") + runSchemaMigration = true } node { name "O=Notary,L=London,C=GB" diff --git a/Accounts/worldcupticketbooking/clients/build.gradle b/Accounts/worldcupticketbooking/clients/build.gradle index bfed0e1f..c6be5398 100644 --- a/Accounts/worldcupticketbooking/clients/build.gradle +++ b/Accounts/worldcupticketbooking/clients/build.gradle @@ -8,6 +8,12 @@ sourceSets { } } +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + dependencies { // Corda dependencies. compile "$corda_release_group:corda-rpc:$corda_release_version" diff --git a/Accounts/worldcupticketbooking/repositories.gradle b/Accounts/worldcupticketbooking/repositories.gradle index 64f5d567..072b616b 100644 --- a/Accounts/worldcupticketbooking/repositories.gradle +++ b/Accounts/worldcupticketbooking/repositories.gradle @@ -1,10 +1,10 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } - maven { url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev' } - maven { url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } + maven { url 'http://software.r3.com/artifactory/corda-lib-dev' } + maven { url 'http://software.r3.com/artifactory/corda-lib' } } diff --git a/Accounts/worldcupticketbooking/workflows/build.gradle b/Accounts/worldcupticketbooking/workflows/build.gradle index 15f078d8..b2f724d5 100644 --- a/Accounts/worldcupticketbooking/workflows/build.gradle +++ b/Accounts/worldcupticketbooking/workflows/build.gradle @@ -65,9 +65,9 @@ dependencies { // Token SDK dependencies. - cordaCompile "$tokens_release_group:tokens-money:$tokens_release_version" cordaCompile "$tokens_release_group:tokens-workflows:$tokens_release_version" cordaCompile "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-money:$tokens_release_version" cordapp "$tokens_release_group:tokens-selection:$tokens_release_version" } diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateT20CricketTicketTokenFlow.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateT20CricketTicketTokenFlow.java index e6acc18d..f75f0d25 100644 --- a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateT20CricketTicketTokenFlow.java +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/CreateT20CricketTicketTokenFlow.java @@ -11,6 +11,7 @@ import net.corda.core.flows.StartableByRPC; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; +import net.corda.core.identity.CordaX500Name; /** * This flow should be run by BCCI node. BCCI node will take care of issuing the base token type for the Ipl ticket. The token type will be craeted on the BCCI node @@ -30,8 +31,9 @@ public CreateT20CricketTicketTokenFlow(String ticketTeam) { @Override @Suspendable public String call() throws FlowException { - //get the notary - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); //create token type by passing in the name of the ipl match. specify the maintainer as BCCI UniqueIdentifier id = new UniqueIdentifier(); diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsHostedOnDifferentNodes.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsHostedOnDifferentNodes.java index 86238873..39243ab4 100644 --- a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsHostedOnDifferentNodes.java +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsHostedOnDifferentNodes.java @@ -93,7 +93,7 @@ public String call() throws FlowException { //buyer will create generate a move tokens state and send this state with new holder(seller) to seller Amount amount = new Amount(costOfTicket, FiatCurrency.Companion.getInstance(currency)); - //Buyer Query for token balance. + //Buyer Query for token balance. QueryCriteria queryCriteria = QueryUtilitiesKt.heldTokenAmountCriteria(this.getInstance(currency), buyerAccount).and(QueryUtilitiesKt.sumTokenCriteria()); List sum = getServiceHub().getVaultService().queryBy(FungibleToken.class, queryCriteria).component5(); if(sum.size() == 0) @@ -106,7 +106,7 @@ public String call() throws FlowException { //the tokens to move to new account which is the seller account Pair> partyAndAmount = new Pair(sellerAccount, amount); - + //let's use the DatabaseTokenSelection to get the tokens from the db DatabaseTokenSelection tokenSelection = new DatabaseTokenSelection( getServiceHub(), @@ -115,7 +115,7 @@ public String call() throws FlowException { DatabaseSelectionConfigKt.RETRY_CAP_DEFAULT, DatabaseSelectionConfigKt.PAGE_SIZE_DEFAULT ); - + //call generateMove which gives us 2 stateandrefs with tokens having new owner as seller. Pair>, List> inputsAndOutputs = tokenSelection.generateMove(Arrays.asList(partyAndAmount), buyerAccount, new TokenQueryBy(), getRunId().getUuid()); @@ -141,7 +141,7 @@ public String call() throws FlowException { //this is the handler for synckeymapping called by seller. seller must also have created some keys not known to us - buyer subFlow(new SyncKeyMappingFlowHandler(sellerSession)); - //recieve the data from counter session in tx formatt. + //recieve the data from counter session in tx formatt. subFlow(new SignTransactionFlow(sellerSession) { @Override protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { @@ -194,7 +194,7 @@ public Void call() throws FlowException { AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo)); AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo)); - //query for all tickets + //query for all tickets QueryCriteria queryCriteriaForSellerTicketType = new QueryCriteria.VaultQueryCriteria() .withExternalIds(Arrays.asList(sellerAccountInfo.getIdentifier().getId())) .withStatus(Vault.StateStatus.UNCONSUMED); @@ -228,7 +228,8 @@ public Void call() throws FlowException { //get the pointer pointer to the T20CricketTicket TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + // Obtain a reference to a notary we wish to use. + Party notary = stateAndRef.getState().getNotary(); TransactionBuilder transactionBuilder = new TransactionBuilder(notary); diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsOnSameNode.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsOnSameNode.java index bb75b0a5..e71721e7 100644 --- a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsOnSameNode.java +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/DVPAccountsOnSameNode.java @@ -104,7 +104,8 @@ public String call() throws FlowException { //get the pointer pointer to the T20CricketTicket TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass()); - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + // Obtain a reference to a notary we wish to use. + Party notary = stateAndRef.getState().getNotary(); //create a transactionBuilder TransactionBuilder transactionBuilder = new TransactionBuilder(notary); diff --git a/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/MoveTokensBetweenAccounts.java b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/MoveTokensBetweenAccounts.java new file mode 100644 index 00000000..c2f0ffb2 --- /dev/null +++ b/Accounts/worldcupticketbooking/workflows/src/main/java/com/t20worldcup/flows/MoveTokensBetweenAccounts.java @@ -0,0 +1,143 @@ +package com.t20worldcup.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.UtilitiesKt; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.types.TokenType; +import com.r3.corda.lib.tokens.selection.TokenQueryBy; +import com.r3.corda.lib.tokens.selection.database.config.DatabaseSelectionConfigKt; +import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection; +import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt; +import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilitiesKt; +import kotlin.Pair; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; + +import java.security.PublicKey; +import java.util.*; + +/** + * This flow only talks about moving fungible tokens from one account to other. + */ +@StartableByRPC +@InitiatingFlow +public class MoveTokensBetweenAccounts extends FlowLogic { + + private final String buyerAccountName; + private final String sellerAccountName; + private final String currency; + private final Long costOfTicket; + + public MoveTokensBetweenAccounts(String buyerAccountName, String sellerAccountName, String currency, Long costOfTicket) { + this.buyerAccountName = buyerAccountName; + this.sellerAccountName = sellerAccountName; + this.currency = currency; + this.costOfTicket = costOfTicket; + } + + @Override + @Suspendable + public String call() throws FlowException { + + //Get buyers and sellers account infos + AccountInfo buyerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(buyerAccountName).get(0).getState().getData(); + AccountInfo sellerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(sellerAccountName).get(0).getState().getData(); + + //Generate new keys for buyers and sellers + AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo));//mappng saved locally + AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo));//mappiing requested from counterparty. does the counterparty save i dont think so + + //buyer will create generate a move tokens state and send this state with new holder(seller) to seller + Amount amount = new Amount(costOfTicket, getInstance(currency)); + + //Buyer Query for token balance. + QueryCriteria queryCriteria = QueryUtilitiesKt.heldTokenAmountCriteria(this.getInstance(currency), buyerAccount).and(QueryUtilitiesKt.sumTokenCriteria()); + List sum = getServiceHub().getVaultService().queryBy(FungibleToken.class, queryCriteria).component5(); + if(sum.size() == 0) + throw new FlowException(buyerAccountName + " has 0 token balance. Please ask the Bank to issue some cash."); + else { + Long tokenBalance = (Long) sum.get(0); + if(tokenBalance < costOfTicket) + throw new FlowException("Available token balance of " + buyerAccountName+ " is less than the cost of the ticket. Please ask the Bank to issue some cash if you wish to buy the ticket "); + } + + //the tokens to move to new account which is the seller account + Pair> partyAndAmount = new Pair(sellerAccount, amount); + + //let's use the DatabaseTokenSelection to get the tokens from the db + DatabaseTokenSelection tokenSelection = new DatabaseTokenSelection( + getServiceHub(), + DatabaseSelectionConfigKt.MAX_RETRIES_DEFAULT, + DatabaseSelectionConfigKt.RETRY_SLEEP_DEFAULT, + DatabaseSelectionConfigKt.RETRY_CAP_DEFAULT, + DatabaseSelectionConfigKt.PAGE_SIZE_DEFAULT + ); + + //call generateMove which gives us 2 stateandrefs with tokens having new owner as seller. + Pair>, List> inputsAndOutputs = + tokenSelection.generateMove(Arrays.asList(partyAndAmount), buyerAccount, new TokenQueryBy(), getRunId().getUuid()); + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + TransactionBuilder transactionBuilder = new TransactionBuilder(notary); + + MoveTokensUtilitiesKt.addMoveTokens(transactionBuilder, inputsAndOutputs.getFirst(), inputsAndOutputs.getSecond()); + + Set mySigners = new HashSet<>(); + + List> commandWithPartiesList = transactionBuilder.toLedgerTransaction(getServiceHub()).getCommands(); + + for(CommandWithParties commandDataCommandWithParties : commandWithPartiesList) { + if(((ArrayList)(getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners()))).size() > 0) { + mySigners.add(((ArrayList)getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners())).get(0)); + } + } + + FlowSession sellerSession = initiateFlow(sellerAccountInfo.getHost()); + + //sign the transaction with the signers we got by calling filterMyKeys + SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, mySigners); + + //call FinalityFlow for finality + subFlow(new FinalityFlow(selfSignedTransaction, Arrays.asList(sellerSession))); + + return null; + } + + public TokenType getInstance(String currencyCode) { + Currency currency = Currency.getInstance(currencyCode); + return new TokenType(currency.getCurrencyCode(), 0); + } +} + +@InitiatedBy(MoveTokensBetweenAccounts.class) +class MoveTokensBetweenAccountsResponder extends FlowLogic { + + private final FlowSession otherSide; + + public MoveTokensBetweenAccountsResponder(FlowSession otherSide) { + this.otherSide = otherSide; + } + + @Override + @Suspendable + public Void call() throws FlowException { + + subFlow(new ReceiveFinalityFlow(otherSide)); + + return null; + } +} \ No newline at end of file diff --git a/Advanced/README.md b/Advanced/README.md index 5e50ea89..f5e9271c 100644 --- a/Advanced/README.md +++ b/Advanced/README.md @@ -1,17 +1,34 @@ -## samples-java/advanced-cordapps +## Advanced Cordapp Samples This folder features Advanced sample projects, each of them demonstrates a complex cordapp that uses multiple features of Corda. -### [auction cordapp](./auction-cordapp): -An blockchain application that leverages different features of Corda like SchedulableState, StatePointer -and OwnableState. It also demonstrates how to perform a DvP (Delivery vs Payment) transaction on Corda. +### [Auction Cordapp](./auction-cordapp): +An blockchain application that leverages different features of Corda like [SchedulableState](https://docs.corda.net/docs/corda-os/event-scheduling.html#how-to-implement-scheduled-events), [StatePointer](https://docs.corda.net/docs/corda-os/api-states.html#state-pointers) +and [OwnableState](https://docs.corda.net/docs/corda-os/api-states.html#ownablestate). It also demonstrates how to perform a DvP (Delivery vs Payment) transaction on Corda. +

+ Corda +

-### [negotiation cordapp](./negotiation-cordapp): +### [Due Diligence Cordapp](./duediligence-cordapp): +An capital market themed app that depicts an ideal shareable due diligence process between banks. It uses advance feature attachment functionality to whitelist the trusted auditors. + +### [Negotiation Cordapp](./negotiation-cordapp): An application that depicts the businsess negotiation and communication process over a distributed ledger system. -It consists of the proposing, negotiating, and settling a corda transaction. +It consists of the proposing, negotiating, and settling a corda transaction. -### [obligation cordapp](./obligation-cordapp): -A simple i-owe-you application illustrates all of the steps of creating an obligation for a resource to change owners. +### [Obligation Cordapp](./obligation-cordapp): +A simple i-owe-you application illustrates all of the steps of creating an obligation for a resource to change owners. +### [Secret Santa Cordapp](./secretsanta-cordapp): +This is an imlementation of Secret Santa using Corda as a tool to store multiple game states.It has a material-ui frontend that lets users create and self-service their own secret santa games. The frontend is implemented in ReactJS and the backend is implemented with a Spring Boot server and some corda flows. It is also equipped with an external emailing package(sendgrid), which you can utilze and turn the app into a live app and send the secret santa assignments to your friends' +emails. +

+ Corda +

+### [Snake and Ladder Game Cordapp](./snakesandladders-cordapp): +This sample implements a simple Snakes And Ladder Game on Corda. This cordapp demonstrate the use of multiple features, including Corda Account Library and Oracle service. +

+ Corda +

diff --git a/Advanced/auction-cordapp/README.md b/Advanced/auction-cordapp/README.md index ae2175d8..fc3582a1 100644 --- a/Advanced/auction-cordapp/README.md +++ b/Advanced/auction-cordapp/README.md @@ -1,7 +1,7 @@ -# auction cordapp +# Auction Cordapp This CorDapp serves as a demo of building an auction application on Corda. It leverages -different features of Corda like `SchedulableState`, `StatePointer` and `OwnableState`. It also demonstrate +different features of Corda like [SchedulableState](https://docs.corda.net/docs/corda-os/event-scheduling.html#how-to-implement-scheduled-events), [StatePointer](https://docs.corda.net/docs/corda-os/api-states.html#state-pointers) and [OwnableState](https://docs.corda.net/docs/corda-os/api-states.html#ownablestate). It also demonstrate how to perform a DvP (Delivery vs Payment) transaction on Corda. It has a full-functional client included and an angular UI to interact with the nodes. @@ -23,52 +23,49 @@ ownership. Left black for simplicity. Has two commands, `CreateAsset` and `Trans - `AuctionContract`: It governs the evolution of the auction. The has the following commands: -- `CreateAuction`: Validation rules governing the creation of the auction. + - `CreateAuction`: Validation rules governing the creation of the auction. -- `Bid`: Validation rules governing the bidding process of the auction. + - `Bid`: Validation rules governing the bidding process of the auction. -- `EndAuction`: Validation rules governing end of the auction i.e making the auction inactive + - `EndAuction`: Validation rules governing end of the auction i.e making the auction inactive once the auction bidding deadline has been reached. -- `Settlement`: Validation rules for settlement of the auction i.e. transfer of asset to the + - `Settlement`: Validation rules for settlement of the auction i.e. transfer of asset to the highest bidder and the highest bid amount transfer to the auctioneer. -- `Exit`: Rules governing the exit (consumption/deletion) of the auction state. + - `Exit`: Rules governing the exit (consumption/deletion) of the auction state. ### Flows: -- `CreateAssetFlow`: This flow is used create an asset. Implemented in [CreateAssetFlow.java](./workflows/src/main/java/net/corda/samples/flows/CreateAssetFlow.java#L44-L66) +- `CreateAssetFlow`: This flow is used create an asset. Implemented in `CreateAssetFlow.java` -- `CreateAuctionFlow`: This flow is used to create an auction ([CreateAuctionFlow.java can be found here](./workflows/src/main/java/net/corda/samples/flows/CreateAuctionFlow.java#L58-L96)). Once an asset has been created using +- `CreateAuctionFlow`: This flow is used to create an auction. Once an asset has been created using the`CreateAssetFlow`, this flow can be used to put the asset on auction. The `AuctionState` references to the `Asset` using a `StatePointer`. Refer here to learn more about StatePointer: https://medium.com/corda/linking-corda-states-using-statepointer-16e24e5e602 - `BidFlow`: It is used to place a bid on an auction. Bids can be placed only till a predetermined -deadline defined in the `AuctionState`. Implemented in [BidFlow.java](./workflows/src/main/java/net/corda/samples/flows/BidFlow.java#L42-L86). +deadline defined in the `AuctionState`. Implemented in `BidFlow.java`. - `EndAuctionFlow`: This is a scheduled flow, which run automatically on auction deadline to mark -the corresponding auction as inactive, so that it stop receiving bids.The auction flow can be found [here](./workflows/src/main/java/net/corda/samples/flows/EndAuctionFlow.java#L39-L83) +the corresponding auction as inactive, so that it stop receiving bids. -- `AuctionSettlementFlow`: +- `AuctionSettlementFlow`: It is used to settle an auction once the bidding deadline has passed. It internally triggers two flows: -It is used to settle an auction once the bidding deadline has passed. It internally triggers two flows: - -- `AuctionDvPFlow`: This flow takes care of the dvp operation for settlement of the auction. It + - `AuctionDvPFlow`: This flow takes care of the dvp operation for settlement of the auction. It transfers the asset on auction to the highest bidder and the highest bid amount is transferred to the auctioneer. It happens as an atomic transaction. -- `AuctionExitFlow`: Once the auction us settled, this flow is used to exit the auction state. This + - `AuctionExitFlow`: Once the auction us settled, this flow is used to exit the auction state. This flow can also be triggered to exit an auction which did not receive any bid till its deadline. ## Usage +## Pre-Requisites +For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). -### Pre-requisites: - -See https://docs.corda.net/getting-set-up.html. ### Running the nodes: Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) diff --git a/Advanced/auction-cordapp/build.gradle b/Advanced/auction-cordapp/build.gradle index 3eaddae7..4d87e6dc 100644 --- a/Advanced/auction-cordapp/build.gradle +++ b/Advanced/auction-cordapp/build.gradle @@ -20,8 +20,9 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -37,9 +38,10 @@ allprojects { repositories { mavenLocal() - jcenter() + mavenCentral() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } maven { url 'https://jitpack.io' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } @@ -60,15 +62,6 @@ apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'net.corda.plugins.cordformation' apply plugin: 'net.corda.plugins.quasar-utils' -cordapp { - info { - name "CorDapp Template" - vendor "Corda Open Source" - targetPlatformVersion corda_platform_version.toInteger() - minimumPlatformVersion corda_platform_version.toInteger() - } -} - sourceSets { main { resources { @@ -96,6 +89,9 @@ dependencies { cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + + cordaDriver "net.corda:corda-shell:4.10" + } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { @@ -107,6 +103,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { cordapp("$corda_release_group:corda-finance-workflows:$corda_release_version") cordapp project(':contracts') cordapp project(':workflows') + runSchemaMigration = true } node { name "O=Notary,L=London,C=GB" @@ -155,4 +152,4 @@ task installQuasar(type: Copy) { from(configurations.quasar) { rename 'quasar-core(.*).jar', 'quasar.jar' } -} \ No newline at end of file +} diff --git a/Advanced/auction-cordapp/client/build.gradle b/Advanced/auction-cordapp/client/build.gradle index 5cc0879a..e94a0e39 100644 --- a/Advanced/auction-cordapp/client/build.gradle +++ b/Advanced/auction-cordapp/client/build.gradle @@ -1,12 +1,10 @@ apply plugin: 'org.springframework.boot' -//sourceSets { -// main { -// resources { -// srcDir rootProject.file("config/dev") -// } -// } -//} +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} dependencies { // Corda dependencies. @@ -33,10 +31,10 @@ dependencies { } springBoot { - mainClassName = "net.corda.samples.client.Starter" + mainClassName = "net.corda.samples.auction.client.Starter" } task runAuctionClient(type: JavaExec, dependsOn: assemble) { classpath = sourceSets.main.runtimeClasspath - main = 'net.corda.samples.client.Starter' + main = 'net.corda.samples.auction.client.Starter' } \ No newline at end of file diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/APIResponse.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/APIResponse.java new file mode 100644 index 00000000..e6cfe3e5 --- /dev/null +++ b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/APIResponse.java @@ -0,0 +1,41 @@ +package net.corda.samples.auction.client; + +/** + * A wrapper to send response from the rest calls. + * @param + */ +public class APIResponse { + private String message; + private T data; + private boolean status; + + public APIResponse(String message, T data, boolean status) { + this.message = message; + this.data = data; + this.status = status; + } + + public String getMessage() { + return message; + } + + public T getData() { + return data; + } + + public boolean isStatus() { + return status; + } + + public static APIResponse success(){ + return new APIResponse<>("SUCCESS", null, true); + } + + public static APIResponse success(T data){ + return new APIResponse<>("SUCCESS", data, true); + } + + public static APIResponse error(String message){ + return new APIResponse<>(message, null, false); + } +} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/AppConfig.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/AppConfig.java new file mode 100644 index 00000000..a5a51420 --- /dev/null +++ b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/AppConfig.java @@ -0,0 +1,54 @@ +package net.corda.samples.auction.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.corda.client.jackson.JacksonSupport; +import net.corda.client.rpc.CordaRPCClient; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class AppConfig implements WebMvcConfigurer { + + @Value("${partyA.host}") + private String partyAHostAndPort; + + @Value("${partyB.host}") + private String partyBHostAndPort; + + @Value("${partyC.host}") + private String partyCHostAndPort; + + @Bean(destroyMethod = "") // Avoids node shutdown on rpc disconnect + public CordaRPCOps partyAProxy(){ + CordaRPCClient partyAClient = new CordaRPCClient(NetworkHostAndPort.parse(partyAHostAndPort)); + return partyAClient.start("user1", "test").getProxy(); + } + + @Bean(destroyMethod = "") + public CordaRPCOps partyBProxy(){ + CordaRPCClient partyBClient = new CordaRPCClient(NetworkHostAndPort.parse(partyBHostAndPort)); + return partyBClient.start("user1", "test").getProxy(); + } + + @Bean(destroyMethod = "") + public CordaRPCOps partyCProxy(){ + CordaRPCClient partyCClient = new CordaRPCClient(NetworkHostAndPort.parse(partyCHostAndPort)); + return partyCClient.start("user1", "test").getProxy(); + } + + /** + * Corda Jackson Support, to convert corda objects to json + */ + @Bean + public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){ + ObjectMapper mapper = JacksonSupport.createDefaultMapper(partyAProxy()); + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(mapper); + return converter; + } +} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Controller.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Controller.java new file mode 100644 index 00000000..71fd77dc --- /dev/null +++ b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Controller.java @@ -0,0 +1,246 @@ +package net.corda.samples.auction.client; + +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.TransactionVerificationException; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.OpaqueBytes; +import net.corda.finance.contracts.asset.Cash; +import net.corda.finance.flows.CashIssueAndPaymentFlow; +import net.corda.samples.auction.flows.*; +import net.corda.samples.auction.states.Asset; +import net.corda.samples.auction.states.AuctionState; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +@RestController +@RequestMapping("/api/auction/") +public class Controller { + + @Autowired + private CordaRPCOps partyAProxy; + + @Autowired + private CordaRPCOps partyBProxy; + + @Autowired + private CordaRPCOps partyCProxy; + + @Autowired + @Qualifier("partyAProxy") + private CordaRPCOps activeParty; + + @GetMapping("list") + public APIResponse>> getAuctionList() { + try{ + List> auctionList = activeParty.vaultQuery(AuctionState.class).getStates(); + return APIResponse.success(auctionList); + }catch(Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @GetMapping("asset/list") + public APIResponse>> getAssetList(){ + try{ + List> assetList = activeParty.vaultQuery(Asset.class).getStates(); + return APIResponse.success(assetList); + }catch(Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @PostMapping("asset/create") + public APIResponse createAsset(@RequestBody Forms.AssetForm assetForm){ + try{ + activeParty.startFlowDynamic(CreateAssetFlow.class, assetForm.getTitle(), assetForm.getDescription(), + assetForm.getImageUrl()).getReturnValue().get(); + return APIResponse.success(); + }catch(Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @PostMapping("create") + public APIResponse createAuction(@RequestBody Forms.CreateAuctionForm auctionForm){ + try { + activeParty.startFlowDynamic(CreateAuctionFlow.CreateAuctionInitiator.class, + Amount.parseCurrency(auctionForm.getBasePrice() + " USD"), + UUID.fromString(auctionForm.getAssetId()), + LocalDateTime.parse(auctionForm.getDeadline(), + DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm:ss a"))).getReturnValue().get(); + return APIResponse.success(); + }catch (ExecutionException e){ + if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ + return APIResponse.error(e.getCause().getMessage()); + }else{ + return APIResponse.error(e.getMessage()); + } + }catch (Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @PostMapping("delete/{auctionId}") + public APIResponse deleteAuction(@PathVariable String auctionId){ + try { + activeParty.startFlowDynamic(AuctionExitFlow.AuctionExitInitiator.class, UUID.fromString(auctionId)).getReturnValue().get(); + return APIResponse.success(); + }catch (ExecutionException e){ + if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ + return APIResponse.error(e.getCause().getMessage()); + }else{ + return APIResponse.error(e.getMessage()); + } + }catch (Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @PostMapping("placeBid") + public APIResponse placeBid(@RequestBody Forms.BidForm bidForm){ + try{ + activeParty.startFlowDynamic(BidFlow.BidInitiator.class, + Amount.parseCurrency(bidForm.getAmount() + " USD"), + UUID.fromString(bidForm.getAuctionId())) + .getReturnValue().get(); + return APIResponse.success(); + }catch (ExecutionException e){ + if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ + return APIResponse.error(e.getCause().getMessage()); + }else{ + return APIResponse.error(e.getMessage()); + } + }catch (Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @PostMapping("payAndSettle") + public APIResponse payAndSettle(@RequestBody Forms.SettlementForm settlementForm){ + try { + activeParty.startFlowDynamic(AuctionSettlementFlow.class, + UUID.fromString(settlementForm.getAuctionId()), + Amount.parseCurrency(settlementForm.getAmount())) + .getReturnValue().get(); + return APIResponse.success(); + } + catch (ExecutionException e){ + if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ + return APIResponse.error(e.getCause().getMessage()); + }else{ + return APIResponse.error(e.getMessage()); + } + }catch (Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @PostMapping("issueCash") + public APIResponse issueCash(@RequestBody Forms.IssueCashForm issueCashForm){ + try{ + activeParty.startFlowDynamic(CashIssueAndPaymentFlow.class, + Amount.parseCurrency(issueCashForm.getAmount() + " USD"), + OpaqueBytes.of("PartyA".getBytes()), + activeParty.partiesFromName(issueCashForm.getParty(), false).iterator().next(), + false, + activeParty.notaryIdentities().get(0)) + .getReturnValue().get(); + return APIResponse.success(); + }catch (ExecutionException e){ + if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ + return APIResponse.error(e.getCause().getMessage()); + }else{ + return APIResponse.error(e.getMessage()); + } + }catch (Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @GetMapping("getCashBalance") + public APIResponse getCashBalance(){ + try { + List> cashStateList = activeParty.vaultQuery(Cash.State.class).getStates(); + Long amount = 0L; + if(cashStateList.size()>0) { + amount = cashStateList.stream().map(stateStateAndRef -> + stateStateAndRef.getState().getData().getAmount().getQuantity()).reduce(Long::sum).get(); + if (amount >= 100) { + amount = amount / 100; + } else { + amount = 0L; + } + } + return APIResponse.success(amount); + }catch (Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @PostMapping(value = "switch-party/{party}") + public APIResponse switchParty(@PathVariable String party){ + if(party.equals("PartyA")){ + activeParty = partyAProxy; + }else if(party.equals("PartyB")){ + activeParty = partyBProxy; + }else if(party.equals("PartyC")){ + activeParty = partyCProxy; + }else{ + return APIResponse.error("Unrecognised Party"); + } + return getCashBalance(); + } + + /** + * Create some initial data to play with. + * @return + */ + @PostMapping("setup") + public APIResponse setupDemoData(){ + try { + partyAProxy.startFlowDynamic(CreateAssetFlow.class, + "Mona Lisa", + "The most famous painting in the world, a masterpiece by Leonardo da Vinci, the mysterious woman with " + + "the enigmatic smile. The sitter in the painting is thought to be Lisa Gherardini, the wife of " + + "Florence merchant Francesco del Giocondo. It did represent an innovation in art -- the painting" + + " is the earliest known Italian portrait to focus so closely on the sitter in a half-length " + + "portrait.", + "img/Mona_Lisa.jpg"); + + partyAProxy.startFlowDynamic(CreateAssetFlow.class, + "The Last Supper", + "Yet another masterpiece by Leonardo da Vinci, painted in an era when religious imagery was still " + + "a dominant artistic theme, \"The Last Supper\" depicts the last time Jesus broke bread with " + + "his disciples before his crucifixion.", + "img/The_Last_Supper.jpg"); + + + partyBProxy.startFlowDynamic(CreateAssetFlow.class, + "The Starry Night", + "Painted by Vincent van Gogh, this comparatively abstract painting is the signature example of " + + "van Gogh's innovative and bold use of thick brushstrokes. The painting's striking blues and " + + "yellows and the dreamy, swirling atmosphere have intrigued art lovers for decades.", + "img/The_Scary_Night.jpg"); + + partyCProxy.startFlowDynamic(CreateAssetFlow.class, + "The Scream", + "First things first -- \"The Scream\" is not a single work of art. According to a British Museum's blog," + + " there are two paintings, two pastels and then an unspecified number of prints. Date back to " + + "the the year 1893, this masterpiece is a work of Edvard Munch", + "img/The_Scream.jpg"); + + activeParty = partyAProxy; + }catch (Exception e){ + return APIResponse.error(e.getMessage()); + } + return APIResponse.success(); + } +} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Forms.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Forms.java new file mode 100644 index 00000000..3786d69d --- /dev/null +++ b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Forms.java @@ -0,0 +1,128 @@ +package net.corda.samples.auction.client; + +public class Forms { + + public static class BidForm { + private String auctionId; + private int amount; + + public String getAuctionId() { + return auctionId; + } + + public void setAuctionId(String auctionId) { + this.auctionId = auctionId; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + } + + public static class SettlementForm { + private String auctionId; + private String amount; + + public String getAuctionId() { + return auctionId; + } + + public void setAuctionId(String auctionId) { + this.auctionId = auctionId; + } + + public String getAmount() { + return amount; + } + + public void setAmount(String amount) { + this.amount = amount; + } + } + + public static class CreateAuctionForm { + private int basePrice; + private String assetId; + private String deadline; + + public int getBasePrice() { + return basePrice; + } + + public void setBasePrice(int basePrice) { + this.basePrice = basePrice; + } + + public String getAssetId() { + return assetId; + } + + public void setAssetId(String assetId) { + this.assetId = assetId; + } + + public String getDeadline() { + return deadline; + } + + public void setDeadline(String deadline) { + this.deadline = deadline; + } + } + + public static class IssueCashForm { + private String party; + private int amount; + + public String getParty() { + return party; + } + + public void setParty(String party) { + this.party = party; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + } + + public static class AssetForm { + private String imageUrl; + private String title; + private String description; + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } + +} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Starter.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Starter.java new file mode 100644 index 00000000..26c69069 --- /dev/null +++ b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/auction/client/Starter.java @@ -0,0 +1,18 @@ +package net.corda.samples.auction.client; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Starter { + + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Starter.class); + app.setBannerMode(Banner.Mode.OFF); + app.setWebApplicationType(WebApplicationType.SERVLET); + app.run(args); + } + +} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/APIResponse.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/APIResponse.java deleted file mode 100644 index 4c541e1c..00000000 --- a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/APIResponse.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.corda.samples.client; - -/** - * A wrapper to send response from the rest calls. - * @param - */ -public class APIResponse { - private String message; - private T data; - private boolean status; - - public APIResponse(String message, T data, boolean status) { - this.message = message; - this.data = data; - this.status = status; - } - - public String getMessage() { - return message; - } - - public T getData() { - return data; - } - - public boolean isStatus() { - return status; - } - - public static APIResponse success(){ - return new APIResponse<>("SUCCESS", null, true); - } - - public static APIResponse success(T data){ - return new APIResponse<>("SUCCESS", data, true); - } - - public static APIResponse error(String message){ - return new APIResponse<>(message, null, false); - } -} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/AppConfig.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/AppConfig.java deleted file mode 100644 index 25680c2b..00000000 --- a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/AppConfig.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.corda.samples.client; - -import com.fasterxml.jackson.databind.ObjectMapper; -import net.corda.client.jackson.JacksonSupport; -import net.corda.client.rpc.CordaRPCClient; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.utilities.NetworkHostAndPort; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class AppConfig implements WebMvcConfigurer { - - @Value("${partyA.host}") - private String partyAHostAndPort; - - @Value("${partyB.host}") - private String partyBHostAndPort; - - @Value("${partyC.host}") - private String partyCHostAndPort; - - @Bean(destroyMethod = "") // Avoids node shutdown on rpc disconnect - public CordaRPCOps partyAProxy(){ - CordaRPCClient partyAClient = new CordaRPCClient(NetworkHostAndPort.parse(partyAHostAndPort)); - return partyAClient.start("user1", "test").getProxy(); - } - - @Bean(destroyMethod = "") - public CordaRPCOps partyBProxy(){ - CordaRPCClient partyBClient = new CordaRPCClient(NetworkHostAndPort.parse(partyBHostAndPort)); - return partyBClient.start("user1", "test").getProxy(); - } - - @Bean(destroyMethod = "") - public CordaRPCOps partyCProxy(){ - CordaRPCClient partyCClient = new CordaRPCClient(NetworkHostAndPort.parse(partyCHostAndPort)); - return partyCClient.start("user1", "test").getProxy(); - } - - /** - * Corda Jackson Support, to convert corda objects to json - */ - @Bean - public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){ - ObjectMapper mapper = JacksonSupport.createDefaultMapper(partyAProxy()); - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); - converter.setObjectMapper(mapper); - return converter; - } -} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Controller.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Controller.java deleted file mode 100644 index 5dc31c4a..00000000 --- a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Controller.java +++ /dev/null @@ -1,246 +0,0 @@ -package net.corda.samples.client; - -import net.corda.core.contracts.Amount; -import net.corda.core.contracts.StateAndRef; -import net.corda.core.contracts.TransactionVerificationException; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.utilities.OpaqueBytes; -import net.corda.finance.contracts.asset.Cash; -import net.corda.finance.flows.CashIssueAndPaymentFlow; -import net.corda.samples.flows.*; -import net.corda.samples.states.Asset; -import net.corda.samples.states.AuctionState; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.ExecutionException; - -@RestController -@RequestMapping("/api/auction/") -public class Controller { - - @Autowired - private CordaRPCOps partyAProxy; - - @Autowired - private CordaRPCOps partyBProxy; - - @Autowired - private CordaRPCOps partyCProxy; - - @Autowired - @Qualifier("partyAProxy") - private CordaRPCOps activeParty; - - @GetMapping("list") - public APIResponse>> getAuctionList() { - try{ - List> auctionList = activeParty.vaultQuery(AuctionState.class).getStates(); - return APIResponse.success(auctionList); - }catch(Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @GetMapping("asset/list") - public APIResponse>> getAssetList(){ - try{ - List> assetList = activeParty.vaultQuery(Asset.class).getStates(); - return APIResponse.success(assetList); - }catch(Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @PostMapping("asset/create") - public APIResponse createAsset(@RequestBody Forms.AssetForm assetForm){ - try{ - activeParty.startFlowDynamic(CreateAssetFlow.class, assetForm.getTitle(), assetForm.getDescription(), - assetForm.getImageUrl()).getReturnValue().get(); - return APIResponse.success(); - }catch(Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @PostMapping("create") - public APIResponse createAuction(@RequestBody Forms.CreateAuctionForm auctionForm){ - try { - activeParty.startFlowDynamic(CreateAuctionFlow.Initiator.class, - Amount.parseCurrency(auctionForm.getBasePrice() + " USD"), - UUID.fromString(auctionForm.getAssetId()), - LocalDateTime.parse(auctionForm.getDeadline(), - DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm:ss a"))).getReturnValue().get(); - return APIResponse.success(); - }catch (ExecutionException e){ - if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ - return APIResponse.error(e.getCause().getMessage()); - }else{ - return APIResponse.error(e.getMessage()); - } - }catch (Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @PostMapping("delete/{auctionId}") - public APIResponse deleteAuction(@PathVariable String auctionId){ - try { - activeParty.startFlowDynamic(AuctionExitFlow.Initiator.class, UUID.fromString(auctionId)).getReturnValue().get(); - return APIResponse.success(); - }catch (ExecutionException e){ - if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ - return APIResponse.error(e.getCause().getMessage()); - }else{ - return APIResponse.error(e.getMessage()); - } - }catch (Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @PostMapping("placeBid") - public APIResponse placeBid(@RequestBody Forms.BidForm bidForm){ - try{ - activeParty.startFlowDynamic(BidFlow.Initiator.class, - Amount.parseCurrency(bidForm.getAmount() + " USD"), - UUID.fromString(bidForm.getAuctionId())) - .getReturnValue().get(); - return APIResponse.success(); - }catch (ExecutionException e){ - if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ - return APIResponse.error(e.getCause().getMessage()); - }else{ - return APIResponse.error(e.getMessage()); - } - }catch (Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @PostMapping("payAndSettle") - public APIResponse payAndSettle(@RequestBody Forms.SettlementForm settlementForm){ - try { - activeParty.startFlowDynamic(AuctionSettlementFlow.class, - UUID.fromString(settlementForm.getAuctionId()), - Amount.parseCurrency(settlementForm.getAmount())) - .getReturnValue().get(); - return APIResponse.success(); - } - catch (ExecutionException e){ - if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ - return APIResponse.error(e.getCause().getMessage()); - }else{ - return APIResponse.error(e.getMessage()); - } - }catch (Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @PostMapping("issueCash") - public APIResponse issueCash(@RequestBody Forms.IssueCashForm issueCashForm){ - try{ - activeParty.startFlowDynamic(CashIssueAndPaymentFlow.class, - Amount.parseCurrency(issueCashForm.getAmount() + " USD"), - OpaqueBytes.of("PartyA".getBytes()), - activeParty.partiesFromName(issueCashForm.getParty(), false).iterator().next(), - false, - activeParty.notaryIdentities().get(0)) - .getReturnValue().get(); - return APIResponse.success(); - }catch (ExecutionException e){ - if(e.getCause() != null && e.getCause().getClass().equals(TransactionVerificationException.ContractRejection.class)){ - return APIResponse.error(e.getCause().getMessage()); - }else{ - return APIResponse.error(e.getMessage()); - } - }catch (Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @GetMapping("getCashBalance") - public APIResponse getCashBalance(){ - try { - List> cashStateList = activeParty.vaultQuery(Cash.State.class).getStates(); - Long amount = 0L; - if(cashStateList.size()>0) { - amount = cashStateList.stream().map(stateStateAndRef -> - stateStateAndRef.getState().getData().getAmount().getQuantity()).reduce(Long::sum).get(); - if (amount >= 100) { - amount = amount / 100; - } else { - amount = 0L; - } - } - return APIResponse.success(amount); - }catch (Exception e){ - return APIResponse.error(e.getMessage()); - } - } - - @PostMapping(value = "switch-party/{party}") - public APIResponse switchParty(@PathVariable String party){ - if(party.equals("PartyA")){ - activeParty = partyAProxy; - }else if(party.equals("PartyB")){ - activeParty = partyBProxy; - }else if(party.equals("PartyC")){ - activeParty = partyCProxy; - }else{ - return APIResponse.error("Unrecognised Party"); - } - return getCashBalance(); - } - - /** - * Create some initial data to play with. - * @return - */ - @PostMapping("setup") - public APIResponse setupDemoData(){ - try { - partyAProxy.startFlowDynamic(CreateAssetFlow.class, - "Mona Lisa", - "The most famous painting in the world, a masterpiece by Leonardo da Vinci, the mysterious woman with " + - "the enigmatic smile. The sitter in the painting is thought to be Lisa Gherardini, the wife of " + - "Florence merchant Francesco del Giocondo. It did represent an innovation in art -- the painting" + - " is the earliest known Italian portrait to focus so closely on the sitter in a half-length " + - "portrait.", - "img/Mona_Lisa.jpg"); - - partyAProxy.startFlowDynamic(CreateAssetFlow.class, - "The Last Supper", - "Yet another masterpiece by Leonardo da Vinci, painted in an era when religious imagery was still " + - "a dominant artistic theme, \"The Last Supper\" depicts the last time Jesus broke bread with " + - "his disciples before his crucifixion.", - "img/The_Last_Supper.jpg"); - - - partyBProxy.startFlowDynamic(CreateAssetFlow.class, - "The Starry Night", - "Painted by Vincent van Gogh, this comparatively abstract painting is the signature example of " + - "van Gogh's innovative and bold use of thick brushstrokes. The painting's striking blues and " + - "yellows and the dreamy, swirling atmosphere have intrigued art lovers for decades.", - "img/The_Scary_Night.jpg"); - - partyCProxy.startFlowDynamic(CreateAssetFlow.class, - "The Scream", - "First things first -- \"The Scream\" is not a single work of art. According to a British Museum's blog," + - " there are two paintings, two pastels and then an unspecified number of prints. Date back to " + - "the the year 1893, this masterpiece is a work of Edvard Munch", - "img/The_Scream.jpg"); - - activeParty = partyAProxy; - }catch (Exception e){ - return APIResponse.error(e.getMessage()); - } - return APIResponse.success(); - } -} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Forms.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Forms.java deleted file mode 100644 index 1b490132..00000000 --- a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Forms.java +++ /dev/null @@ -1,128 +0,0 @@ -package net.corda.samples.client; - -public class Forms { - - public static class BidForm { - private String auctionId; - private int amount; - - public String getAuctionId() { - return auctionId; - } - - public void setAuctionId(String auctionId) { - this.auctionId = auctionId; - } - - public int getAmount() { - return amount; - } - - public void setAmount(int amount) { - this.amount = amount; - } - } - - public static class SettlementForm { - private String auctionId; - private String amount; - - public String getAuctionId() { - return auctionId; - } - - public void setAuctionId(String auctionId) { - this.auctionId = auctionId; - } - - public String getAmount() { - return amount; - } - - public void setAmount(String amount) { - this.amount = amount; - } - } - - public static class CreateAuctionForm { - private int basePrice; - private String assetId; - private String deadline; - - public int getBasePrice() { - return basePrice; - } - - public void setBasePrice(int basePrice) { - this.basePrice = basePrice; - } - - public String getAssetId() { - return assetId; - } - - public void setAssetId(String assetId) { - this.assetId = assetId; - } - - public String getDeadline() { - return deadline; - } - - public void setDeadline(String deadline) { - this.deadline = deadline; - } - } - - public static class IssueCashForm { - private String party; - private int amount; - - public String getParty() { - return party; - } - - public void setParty(String party) { - this.party = party; - } - - public int getAmount() { - return amount; - } - - public void setAmount(int amount) { - this.amount = amount; - } - } - - public static class AssetForm { - private String imageUrl; - private String title; - private String description; - - public String getImageUrl() { - return imageUrl; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - } - -} diff --git a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Starter.java b/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Starter.java deleted file mode 100644 index 7c991ba8..00000000 --- a/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/Starter.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.corda.samples.client; - -import org.springframework.boot.Banner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.WebApplicationType; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Starter { - - public static void main(String[] args) { - SpringApplication app = new SpringApplication(Starter.class); - app.setBannerMode(Banner.Mode.OFF); - app.setWebApplicationType(WebApplicationType.SERVLET); - app.run(args); - } - -} \ No newline at end of file diff --git a/Advanced/auction-cordapp/contracts/build.gradle b/Advanced/auction-cordapp/contracts/build.gradle index 6d9813f8..d35b4df3 100644 --- a/Advanced/auction-cordapp/contracts/build.gradle +++ b/Advanced/auction-cordapp/contracts/build.gradle @@ -23,7 +23,7 @@ dependencies { // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" - compileOnly "$corda_release_group:corda-testserver-impl:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" cordaRuntime "$corda_release_group:corda-testserver:$corda_release_version" diff --git a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/contracts/AssetContract.java b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/contracts/AssetContract.java new file mode 100644 index 00000000..014596e7 --- /dev/null +++ b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/contracts/AssetContract.java @@ -0,0 +1,22 @@ +package net.corda.samples.auction.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +//TODO +public class AssetContract implements Contract { + // This is used to identify our contracts when building a transaction. + public static final String ID = "net.corda.samples.auction.contracts.AssetContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Verification code goes here. Left blank for simplicity + } + + public interface Commands extends CommandData { + class CreateAsset implements Commands {} + class TransferAsset implements Commands {} + } +} diff --git a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/contracts/AuctionContract.java b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/contracts/AuctionContract.java similarity index 92% rename from Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/contracts/AuctionContract.java rename to Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/contracts/AuctionContract.java index 8703bfec..abd174ac 100644 --- a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/contracts/AuctionContract.java +++ b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/contracts/AuctionContract.java @@ -1,20 +1,20 @@ -package net.corda.samples.contracts; +package net.corda.samples.auction.contracts; import net.corda.core.contracts.Command; import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; import net.corda.core.transactions.LedgerTransaction; -import net.corda.samples.states.Asset; -import net.corda.samples.states.AuctionState; +import net.corda.samples.auction.states.Asset; +import net.corda.samples.auction.states.AuctionState; // ************ // * Contract * // ************ public class AuctionContract implements Contract { - // This is used to identify our contract when building a transaction. - public static final String ID = "net.corda.samples.contracts.AuctionContract"; + // This is used to identify our contracts when building a transaction. + public static final String ID = "net.corda.samples.auction.contracts.AuctionContract"; - // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // A transaction is valid if the verify() function of the contracts of all the transaction's input and output states // does not throw an exception. @Override public void verify(LedgerTransaction tx) { @@ -118,4 +118,4 @@ class Settlement implements Commands {} class Exit implements Commands {} } -} \ No newline at end of file +} diff --git a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/states/Asset.java b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/states/Asset.java similarity index 83% rename from Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/states/Asset.java rename to Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/states/Asset.java index 83ac221f..d40d3222 100644 --- a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/states/Asset.java +++ b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/states/Asset.java @@ -1,17 +1,15 @@ -package net.corda.samples.states; +package net.corda.samples.auction.states; -import com.google.common.collect.ImmutableList; import net.corda.core.contracts.*; import net.corda.core.identity.AbstractParty; -import net.corda.samples.contracts.AssetContract; -import net.corda.samples.contracts.AuctionContract; +import net.corda.samples.auction.contracts.AssetContract; import org.jetbrains.annotations.NotNull; import java.util.Arrays; import java.util.List; /** - * An ownable state to represent an asset that could be put on auction. + * An ownable states to represent an asset that could be put on auction. */ @BelongsToContract(AssetContract.class) public class Asset implements OwnableState, LinearState { @@ -44,11 +42,11 @@ public AbstractParty getOwner() { } /** - * This method should be called to retrieve an ownership transfer command and the updated state with the new owner + * This method should be called to retrieve an ownership transfer command and the updated states with the new owner * passed as a parameter to the method. * * @param newOwner of the asset - * @return A CommandAndState object encapsulating the command and the new state with the changed owner, to be used + * @return A CommandAndState object encapsulating the command and the new states with the changed owner, to be used * in the ownership transfer transaction. */ @NotNull diff --git a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/states/AuctionState.java b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/states/AuctionState.java similarity index 94% rename from Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/states/AuctionState.java rename to Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/states/AuctionState.java index 85bb717c..b87f9deb 100644 --- a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/states/AuctionState.java +++ b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/auction/states/AuctionState.java @@ -1,11 +1,11 @@ -package net.corda.samples.states; +package net.corda.samples.auction.states; import net.corda.core.contracts.*; import net.corda.core.flows.FlowLogicRef; import net.corda.core.flows.FlowLogicRefFactory; import net.corda.core.identity.AbstractParty; import net.corda.core.identity.Party; -import net.corda.samples.contracts.AuctionContract; +import net.corda.samples.auction.contracts.AuctionContract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -63,7 +63,7 @@ public AuctionState(LinearPointer auctionItem, UUID auctionId, Amou } /** - * This method returns a ScheduledActivity. The ScheduledActivity encapsulates a flow ref and a trigger time + * This method returns a ScheduledActivity. The ScheduledActivity encapsulates a flows ref and a trigger time * to start the ScheduledFlow. * * @param thisStateRef @@ -78,7 +78,7 @@ public ScheduledActivity nextScheduledActivity(@NotNull StateRef thisStateRef, return null; FlowLogicRef flowLogicRef = flowLogicRefFactory.create( - "net.corda.samples.flows.EndAuctionFlow$Initiator", auctionId); + "net.corda.samples.auction.flows.EndAuctionFlow$EndAuctionInitiator", auctionId); return new ScheduledActivity(flowLogicRef, bidEndTime); } diff --git a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/contracts/AssetContract.java b/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/contracts/AssetContract.java deleted file mode 100644 index 60d9c61a..00000000 --- a/Advanced/auction-cordapp/contracts/src/main/java/net/corda/samples/contracts/AssetContract.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.corda.samples.contracts; - -import net.corda.core.contracts.CommandData; -import net.corda.core.contracts.Contract; -import net.corda.core.transactions.LedgerTransaction; -import org.jetbrains.annotations.NotNull; - -public class AssetContract implements Contract { - @Override - public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { - // Contract Verification code goes here. Left blank for simplicity - } - - public interface Commands extends CommandData { - class CreateAsset implements Commands {} - class TransferAsset implements Commands {} - } -} diff --git a/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/auction/contracts/ContractTests.java b/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/auction/contracts/ContractTests.java new file mode 100644 index 00000000..096aa6fd --- /dev/null +++ b/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/auction/contracts/ContractTests.java @@ -0,0 +1,94 @@ +package net.corda.samples.auction.contracts; + +import net.corda.core.contracts.Amount; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.auction.states.AuctionState; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; + +import static net.corda.testing.node.NodeTestUtils.transaction; + +public class ContractTests { + private final TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "", "GB")); + private final TestIdentity bob = new TestIdentity(new CordaX500Name("Bob", "", "GB")); + private MockServices ledgerServices = new MockServices(new TestIdentity(new CordaX500Name("TestId", "", "GB"))); + + + @Test + public void testVerifyBid() { + AuctionState input = new AuctionState(null, null, Amount.parseCurrency("100 USD"), + Amount.parseCurrency("200 USD"), null, null, null, true, null, + null, null); + + + AuctionState input_inactive = new AuctionState(null, null, Amount.parseCurrency("100 USD"), + Amount.parseCurrency("200 USD"), null, null, null, false, null, + null, null); + + AuctionState output = new AuctionState(null, null, Amount.parseCurrency("100 USD"), + Amount.parseCurrency("220 USD"), null, null, null, true, null, + null, null); + + AuctionState output_lt_basePrice = new AuctionState(null, null, Amount.parseCurrency("100 USD"), + Amount.parseCurrency("80 USD"), null, null, null, true, null, + null, null); + + + // Should fail auction is inactive + transaction(ledgerServices, tx -> { + tx.input(AuctionContract.ID, input_inactive); + tx.output(AuctionContract.ID, output); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new AuctionContract.Commands.Bid()); + tx.fails(); + return null; + }); + + + // Should fail bid price is less than base price + transaction(ledgerServices, tx -> { + tx.input(AuctionContract.ID, input); + tx.output(AuctionContract.ID, output_lt_basePrice); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new AuctionContract.Commands.Bid()); + tx.fails(); + return null; + }); + + AuctionState output_lt_highestBid = new AuctionState(null, null, Amount.parseCurrency("100 USD"), + Amount.parseCurrency("180 USD"), null, null, null, true, null, + null, null); + + // Should fail bid price is less than previous highest bid + transaction(ledgerServices, tx -> { + tx.input(AuctionContract.ID, input); + tx.output(AuctionContract.ID, output_lt_highestBid); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new AuctionContract.Commands.Bid()); + tx.fails(); + return null; + }); + + AuctionState output_eq_highestBid = new AuctionState(null, null, Amount.parseCurrency("100 USD"), + Amount.parseCurrency("200 USD"), null, null, null, true, null, + null, null); + + // Should fail bid price is equal to previous highest bid + transaction(ledgerServices, tx -> { + tx.input(AuctionContract.ID, input); + tx.output(AuctionContract.ID, output_eq_highestBid); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new AuctionContract.Commands.Bid()); + tx.fails(); + return null; + }); + + //Should verify + transaction(ledgerServices, tx -> { + tx.input(AuctionContract.ID, input); + tx.output(AuctionContract.ID, output); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new AuctionContract.Commands.Bid()); + tx.verifies(); + return null; + }); + } +} \ No newline at end of file diff --git a/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/auction/contracts/StateTests.java b/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/auction/contracts/StateTests.java new file mode 100644 index 00000000..e83bbc21 --- /dev/null +++ b/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/auction/contracts/StateTests.java @@ -0,0 +1,20 @@ +package net.corda.samples.auction.contracts; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.samples.auction.states.AuctionState; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.UUID; + +public class StateTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void hasFieldOfCorrectType() throws NoSuchFieldException { + // Does the message field exist? + AuctionState.class.getDeclaredField("auctionId"); + // Is the message field of the correct type? + assert(AuctionState.class.getDeclaredField("auctionId").getType().equals(UUID.class)); + } +} \ No newline at end of file diff --git a/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/contracts/ContractTests.java b/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/contracts/ContractTests.java deleted file mode 100644 index 733be464..00000000 --- a/Advanced/auction-cordapp/contracts/src/test/java/net/corda/samples/contracts/ContractTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.corda.samples.contracts; - -import net.corda.testing.node.MockServices; -import org.junit.Test; - -public class ContractTests { - private final MockServices ledgerServices = new MockServices(); - - @Test - public void dummyTest() { - - } -} \ No newline at end of file diff --git a/Advanced/auction-cordapp/gradle.properties b/Advanced/auction-cordapp/gradle.properties index 10468f74..edd7041f 100644 --- a/Advanced/auction-cordapp/gradle.properties +++ b/Advanced/auction-cordapp/gradle.properties @@ -1,3 +1,3 @@ name=Auction CorDapp -group=net.corda.samples -version=1.0 \ No newline at end of file +group=com.auction +version=1.0 diff --git a/Advanced/auction-cordapp/gradle/wrapper/gradle-wrapper.properties b/Advanced/auction-cordapp/gradle/wrapper/gradle-wrapper.properties index 857061e8..504900f7 100644 --- a/Advanced/auction-cordapp/gradle/wrapper/gradle-wrapper.properties +++ b/Advanced/auction-cordapp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/Advanced/auction-cordapp/workflows/src/integrationTest/java/net/corda/samples/DriverBasedTest.java b/Advanced/auction-cordapp/workflows/src/integrationTest/java/net/corda/samples/DriverBasedTest.java deleted file mode 100644 index 4f66f387..00000000 --- a/Advanced/auction-cordapp/workflows/src/integrationTest/java/net/corda/samples/DriverBasedTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.corda.samples; - -import com.google.common.collect.ImmutableList; -import net.corda.core.concurrent.CordaFuture; -import net.corda.core.identity.CordaX500Name; -import net.corda.testing.core.TestIdentity; -import net.corda.testing.driver.DriverParameters; -import net.corda.testing.driver.NodeHandle; -import net.corda.testing.driver.NodeParameters; -import org.junit.Test; - -import java.util.List; - -import static net.corda.testing.driver.Driver.driver; -import static org.junit.Assert.assertEquals; - -public class DriverBasedTest { - private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); - private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); - - @Test - public void nodeTest() { - driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { - // Start a pair of nodes and wait for them both to be ready. - List> handleFutures = ImmutableList.of( - dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), - dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) - ); - - try { - NodeHandle partyAHandle = handleFutures.get(0).get(); - NodeHandle partyBHandle = handleFutures.get(1).get(); - - // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the - // nodes have started and can communicate. - - // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault - // and other important metrics to ensure that your CorDapp is working as intended. - assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); - assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); - } catch (Exception e) { - throw new RuntimeException("Caught exception during test: ", e); - } - - return null; - }); - } -} \ No newline at end of file diff --git a/Advanced/auction-cordapp/workflows/src/integrationTest/java/net/corda/samples/auction/DriverBasedTest.java b/Advanced/auction-cordapp/workflows/src/integrationTest/java/net/corda/samples/auction/DriverBasedTest.java new file mode 100644 index 00000000..4fe5bf2a --- /dev/null +++ b/Advanced/auction-cordapp/workflows/src/integrationTest/java/net/corda/samples/auction/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.auction; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionDvPFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionDvPFlow.java similarity index 82% rename from Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionDvPFlow.java rename to Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionDvPFlow.java index 56075a48..e4321c63 100644 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionDvPFlow.java +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionDvPFlow.java @@ -1,4 +1,4 @@ -package net.corda.samples.flows; +package net.corda.samples.auction.flows; import co.paralleluniverse.fibers.Suspendable; import kotlin.Pair; @@ -6,22 +6,22 @@ import net.corda.core.contracts.CommandAndState; import net.corda.core.contracts.StateAndRef; import net.corda.core.flows.*; +import net.corda.core.identity.Party; import net.corda.core.node.services.Vault; import net.corda.core.node.services.vault.QueryCriteria; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; -import net.corda.finance.contracts.asset.OnLedgerAsset; -import net.corda.finance.contracts.asset.PartyAndAmount; import net.corda.finance.workflows.asset.CashUtils; -import net.corda.samples.states.Asset; -import net.corda.samples.states.AuctionState; +import net.corda.samples.auction.states.Asset; +import net.corda.samples.auction.states.AuctionState; import org.jetbrains.annotations.NotNull; + import java.security.PublicKey; import java.util.*; /** - * This flow takes care of the delivery-vs-payment for settlement of the auction. The auctioned asset's ownership is + * This flows takes care of the delivery-vs-payment for settlement of the auction. The auctioned asset's ownership is * transferred from the auctioneer to the highest bidder and the bid amount is transferred from the highest bidder to * the auctioneer in a single atomic transaction. */ @@ -29,19 +29,19 @@ public class AuctionDvPFlow { @StartableByRPC @InitiatingFlow - public static class Initiator extends FlowLogic{ + public static class AuctionDvPInitiator extends FlowLogic{ private final UUID auctionId; private final Amount payment; /** - * Constructor to initialise flow parameters. + * Constructor to initialise flows parameters. * * @param auctionId is the unique id of the auction to be settled * @param payment is the bid amount which is required to be transferred from the highest bidded to auctioneer to * settle the auction. */ - public Initiator(UUID auctionId, Amount payment) { + public AuctionDvPInitiator(UUID auctionId, Amount payment) { this.auctionId = auctionId; this.payment = payment; } @@ -50,8 +50,8 @@ public Initiator(UUID auctionId, Amount payment) { @Suspendable public SignedTransaction call() throws FlowException { - // Query the vault to fetch a list of all AuctionState state, and filter the results based on the auctionId - // to fetch the desired AuctionState state from the vault. + // Query the vault to fetch a list of all AuctionState states, and filter the results based on the auctionId + // to fetch the desired AuctionState states from the vault. List> auntionStateAndRefs = getServiceHub().getVaultService() .queryBy(AuctionState.class).getStates(); StateAndRef auctionStateAndRef = auntionStateAndRefs.stream().filter(stateAndRef -> { @@ -73,14 +73,17 @@ public SignedTransaction call() throws FlowException { StateAndRef assetStateAndRef = getServiceHub().getVaultService(). queryBy(Asset.class, queryCriteria).getStates().get(0); - // Use the withNewOwner() of the Ownable state get the command and the output state to be used in the + // Use the withNewOwner() of the Ownable states get the command and the output states to be used in the // transaction from ownership transfer of the asset. CommandAndState commandAndState = assetStateAndRef.getState().getData() .withNewOwner(auctionState.getWinner()); + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = auctionStateAndRef.getState().getNotary(); + // Create the transaction builder. - TransactionBuilder transactionBuilder = new TransactionBuilder(getServiceHub().getNetworkMapCache() - .getNotaryIdentities().get(0)); + TransactionBuilder transactionBuilder = new TransactionBuilder(notary); // Generate Spend for the Cash. The CashUtils generateSpend method can be used to update the transaction // builder with the appropriate inputs and outputs corresponding to the cash spending. A new keypair is @@ -116,12 +119,12 @@ public SignedTransaction call() throws FlowException { } } - @InitiatedBy(Initiator.class) - public static class Responder extends FlowLogic{ + @InitiatedBy(AuctionDvPInitiator.class) + public static class AuctionDvPResponder extends FlowLogic{ private FlowSession otherPartySession; - public Responder(FlowSession otherPartySession) { + public AuctionDvPResponder(FlowSession otherPartySession) { this.otherPartySession = otherPartySession; } diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionExitFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionExitFlow.java similarity index 85% rename from Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionExitFlow.java rename to Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionExitFlow.java index 1eeec8ad..7a4a3b54 100644 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionExitFlow.java +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionExitFlow.java @@ -1,4 +1,4 @@ -package net.corda.samples.flows; +package net.corda.samples.auction.flows; import co.paralleluniverse.fibers.Suspendable; import net.corda.core.contracts.StateAndRef; @@ -6,8 +6,8 @@ import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; -import net.corda.samples.contracts.AuctionContract; -import net.corda.samples.states.AuctionState; +import net.corda.samples.auction.contracts.AuctionContract; +import net.corda.samples.auction.states.AuctionState; import org.jetbrains.annotations.NotNull; import java.security.PublicKey; @@ -17,26 +17,24 @@ import java.util.UUID; /** - * This flow is used to exit an auction state once it has been settled or auction did not received bids till the + * This flows is used to exit an auction states once it has been settled or auction did not received bids till the * deadline. * - * The flow is initiated by the highest bidder in case as part of auction settlement flow. + * The flows is initiated by the highest bidder in case as part of auction settlement flows. * In case of no bids received, it can be initiated by the auctioneer to exit the auction. */ public class AuctionExitFlow { - private AuctionExitFlow(){} - @StartableByRPC @InitiatingFlow - public static class Initiator extends FlowLogic{ + public static class AuctionExitInitiator extends FlowLogic{ private UUID auctionId; /** * * @param auctionId is the unique id of the auction to be consumed. */ - public Initiator(UUID auctionId) { + public AuctionExitInitiator(UUID auctionId) { this.auctionId = auctionId; } @@ -44,8 +42,8 @@ public Initiator(UUID auctionId) { @Suspendable public SignedTransaction call() throws FlowException { - // Query the vault to fetch a list of all AuctionState state, and filter the results based on the auctionId - // to fetch the desired AuctionState state from the vault. This filtered state would be used as input to the + // Query the vault to fetch a list of all AuctionState states, and filter the results based on the auctionId + // to fetch the desired AuctionState states from the vault. This filtered states would be used as input to the // transaction. List> auntionStateAndRefs = getServiceHub().getVaultService() .queryBy(AuctionState.class).getStates(); @@ -106,11 +104,11 @@ public SignedTransaction call() throws FlowException { } } - @InitiatedBy(Initiator.class) - public static class Responder extends FlowLogic { + @InitiatedBy(AuctionExitInitiator.class) + public static class AuctionExitResponder extends FlowLogic { private FlowSession otherPartySession; - public Responder(FlowSession otherPartySession) { + public AuctionExitResponder(FlowSession otherPartySession) { this.otherPartySession = otherPartySession; } diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionSettlementFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionSettlementFlow.java new file mode 100644 index 00000000..3218d4e5 --- /dev/null +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/AuctionSettlementFlow.java @@ -0,0 +1,48 @@ +package net.corda.samples.auction.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.Amount; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.samples.auction.flows.AuctionDvPFlow; +import net.corda.samples.auction.flows.AuctionExitFlow; + +import java.util.Currency; +import java.util.UUID; + +/** + * This purpose of this flows is the settle the auction which includes: + * - Transferring the highest bid amount to the auctioneer. + * - Transferring the auctioned asset's ownership to the highest bidder + * - Consuming the auction states. + * + * The AuctionDvPFlow does the delivery-vs-payment of the auctioned asset and the bid amount. + * The AuctionExitFlow consumes the auction states. + */ +@StartableByRPC +public class AuctionSettlementFlow extends FlowLogic { + + private final UUID auctionId; + private final Amount amount; + + /** + * Constructor to initialise flows parameters received from rpc. + * + * @param auctionId is the unique id of the auction to be settled + * @param amount is the bid amount which is required to be transferred from the highest bidded to auctioneer to + * settle the auction. + */ + public AuctionSettlementFlow(UUID auctionId, Amount amount) { + this.auctionId = auctionId; + this.amount = amount; + } + + @Override + @Suspendable + public Void call() throws FlowException { + subFlow(new AuctionDvPFlow.AuctionDvPInitiator(auctionId, amount)); + subFlow(new AuctionExitFlow.AuctionExitInitiator(auctionId)); + return null; + } +} diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/BidFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/BidFlow.java similarity index 77% rename from Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/BidFlow.java rename to Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/BidFlow.java index 60399463..d91976fa 100644 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/BidFlow.java +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/BidFlow.java @@ -1,4 +1,4 @@ -package net.corda.samples.flows; +package net.corda.samples.auction.flows; import co.paralleluniverse.fibers.Suspendable; import net.corda.core.contracts.Amount; @@ -7,32 +7,30 @@ import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; -import net.corda.samples.contracts.AuctionContract; -import net.corda.samples.states.AuctionState; +import net.corda.samples.auction.contracts.AuctionContract; +import net.corda.samples.auction.states.AuctionState; import java.util.*; /** - * This flow is used to put a bid on an asset put on auction. + * This flows is used to put a bid on an asset put on auction. */ public class BidFlow { - private BidFlow(){} - @InitiatingFlow @StartableByRPC - public static class Initiator extends FlowLogic{ + public static class BidInitiator extends FlowLogic{ private final Amount bidAmount; private final UUID auctionId; /** - * Constructor to initialise flow parameters received from rpc. + * Constructor to initialise flows parameters received from rpc. * * @param bidAmount is the amount the bidder is bidding for for the asset on auction. * @param auctionId is the unique identifier of the auction on which this bid it put. */ - public Initiator(Amount bidAmount, UUID auctionId) { + public BidInitiator(Amount bidAmount, UUID auctionId) { this.bidAmount = bidAmount; this.auctionId = auctionId; } @@ -41,8 +39,8 @@ public Initiator(Amount bidAmount, UUID auctionId) { @Suspendable public SignedTransaction call() throws FlowException { - // Query the vault to fetch a list of all AuctionState state, and filter the results based on the auctionId - // to fetch the desired AuctionState state from the vault. This filtered state would be used as input to the + // Query the vault to fetch a list of all AuctionState states, and filter the results based on the auctionId + // to fetch the desired AuctionState states from the vault. This filtered states would be used as input to the // transaction. List> auntionStateAndRefs = getServiceHub().getVaultService() .queryBy(AuctionState.class).getStates(); @@ -56,13 +54,13 @@ public SignedTransaction call() throws FlowException { AuctionState input = inputStateAndRef.getState().getData(); - //Create the output state + //Create the output states AuctionState output = new AuctionState(input.getAuctionItem(), input.getAuctionId(), input.getBasePrice(), bidAmount, getOurIdentity(), input.getBidEndTime(), null, true, input.getAuctioneer(), input.getBidders(), null); - // Build the transaction. On successful completion of the transaction the current auction state is consumed - // and a new auction state is create as an output containg tge bid details. + // Build the transaction. On successful completion of the transaction the current auction states is consumed + // and a new auction states is create as an output containg tge bid details. TransactionBuilder builder = new TransactionBuilder(inputStateAndRef.getState().getNotary()) .addInputState(inputStateAndRef) .addOutputState(output) @@ -86,12 +84,12 @@ bidAmount, getOurIdentity(), input.getBidEndTime(), null, true, } } - @InitiatedBy(Initiator.class) - public static class Responder extends FlowLogic { + @InitiatedBy(BidInitiator.class) + public static class BidResponder extends FlowLogic { private FlowSession counterpartySession; - public Responder(FlowSession counterpartySession) { + public BidResponder(FlowSession counterpartySession) { this.counterpartySession = counterpartySession; } @@ -101,4 +99,4 @@ public SignedTransaction call() throws FlowException { return subFlow(new ReceiveFinalityFlow(counterpartySession)); } } -} \ No newline at end of file +} diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAssetFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAssetFlow.java new file mode 100644 index 00000000..dc95df45 --- /dev/null +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAssetFlow.java @@ -0,0 +1,68 @@ +package net.corda.samples.auction.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.auction.contracts.AssetContract; +import net.corda.samples.auction.states.Asset; +import net.corda.core.identity.CordaX500Name; + +import java.util.Arrays; +import java.util.Collections; + + +/** + * This flows is used to build a transaction to issue an asset on the Corda Ledger, which can later be put on auction. + * It creates a self issues transaction, the states is only issued on the ledger of the party who executes the flows. + */ +@StartableByRPC +public class CreateAssetFlow extends FlowLogic { + + private final String title; + private final String description; + private final String imageURL; + + /** + * Constructor to initialise flows parameters received from rpc. + * + * @param title of the asset to be issued on ledger + * @param description of the asset to be issued in ledger + * @param imageURL is a url of an image of the asset + */ + public CreateAssetFlow(String title, String description, String imageURL) { + this.title = title; + this.description = description; + this.imageURL = imageURL; + } + + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + // Create the output states + Asset output = new Asset(new UniqueIdentifier(), title, description, imageURL, + getOurIdentity()); + + // Build the transaction, add the output states and the command to the transaction. + TransactionBuilder transactionBuilder = new TransactionBuilder(notary) + .addOutputState(output) + .addCommand(new AssetContract.Commands.CreateAsset(), + Arrays.asList(getOurIdentity().getOwningKey())); // Required Signers + + // Verify the transaction + transactionBuilder.verify(getServiceHub()); + + // Sign the transaction + SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(transactionBuilder); + + // Notarise the transaction and record the states in the ledger. + return subFlow(new FinalityFlow(signedTransaction, Collections.emptyList())); + } +} diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/CreateAuctionFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAuctionFlow.java similarity index 76% rename from Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/CreateAuctionFlow.java rename to Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAuctionFlow.java index 86223f32..b9b1ccd8 100644 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/CreateAuctionFlow.java +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/CreateAuctionFlow.java @@ -1,4 +1,5 @@ -package net.corda.samples.flows; +package net.corda.samples.auction.flows; + import co.paralleluniverse.fibers.Suspendable; import net.corda.core.contracts.Amount; @@ -6,12 +7,14 @@ import net.corda.core.contracts.LinearState; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; import net.corda.core.utilities.ProgressTracker; -import net.corda.samples.contracts.AuctionContract; -import net.corda.samples.states.AuctionState; +import net.corda.samples.auction.contracts.AuctionContract; +import net.corda.samples.auction.states.AuctionState; +import net.corda.core.identity.CordaX500Name; import java.time.LocalDateTime; import java.time.ZoneId; @@ -23,11 +26,9 @@ */ public class CreateAuctionFlow { - private CreateAuctionFlow(){} - @InitiatingFlow @StartableByRPC - public static class Initiator extends FlowLogic { + public static class CreateAuctionInitiator extends FlowLogic { private final ProgressTracker progressTracker = new ProgressTracker(); @@ -42,7 +43,7 @@ public static class Initiator extends FlowLogic { * @param auctionItem is the uuid of the asset to be put on auction * @param bidDeadLine is the time till when the auction will be active */ - public Initiator(Amount basePrice, UUID auctionItem, LocalDateTime bidDeadLine) { + public CreateAuctionInitiator(Amount basePrice, UUID auctionItem, LocalDateTime bidDeadLine) { this.basePrice = basePrice; this.auctionItem = auctionItem; this.bidDeadLine = bidDeadLine; @@ -56,8 +57,10 @@ public ProgressTracker getProgressTracker() { @Suspendable @Override public SignedTransaction call() throws FlowException { - // Fetch the notary for the transaction - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + Party auctioneer = getOurIdentity(); // Fetch all parties from the network map and remove the auctioneer and notary. All the parties are added as @@ -78,8 +81,8 @@ public SignedTransaction call() throws FlowException { // Build the transaction TransactionBuilder builder = new TransactionBuilder(notary) - .addOutputState(auctionState) - .addCommand(new AuctionContract.Commands.CreateAuction(), Arrays.asList(auctioneer.getOwningKey())); + .addOutputState(auctionState) + .addCommand(new AuctionContract.Commands.CreateAuction(), Arrays.asList(auctioneer.getOwningKey())); // Verify the transaction builder.verify(getServiceHub()); @@ -96,12 +99,12 @@ public SignedTransaction call() throws FlowException { } } - @InitiatedBy(Initiator.class) - public static class Responder extends FlowLogic { + @InitiatedBy(CreateAuctionInitiator.class) + public static class CreateAuctionResponder extends FlowLogic { private FlowSession counterpartySession; - public Responder(FlowSession counterpartySession) { + public CreateAuctionResponder(FlowSession counterpartySession) { this.counterpartySession = counterpartySession; } diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/EndAuctionFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/EndAuctionFlow.java similarity index 89% rename from Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/EndAuctionFlow.java rename to Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/EndAuctionFlow.java index f07c2737..720dc8ce 100644 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/EndAuctionFlow.java +++ b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/auction/flows/EndAuctionFlow.java @@ -1,4 +1,4 @@ -package net.corda.samples.flows; +package net.corda.samples.auction.flows; import co.paralleluniverse.fibers.Suspendable; import net.corda.core.contracts.StateAndRef; @@ -6,8 +6,8 @@ import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; -import net.corda.samples.contracts.AuctionContract; -import net.corda.samples.states.AuctionState; +import net.corda.samples.auction.contracts.AuctionContract; +import net.corda.samples.auction.states.AuctionState; import java.util.ArrayList; import java.util.List; @@ -19,18 +19,16 @@ */ public class EndAuctionFlow { - private EndAuctionFlow() {} - //Scheduled Flows must be annotated with @SchedulableFlow. @SchedulableFlow @InitiatingFlow - public static class Initiator extends FlowLogic{ + public static class EndAuctionInitiator extends FlowLogic{ private final UUID auctionId; /** * @param auctionId is the unique identifier of the auction to be closed. */ - public Initiator(UUID auctionId) { + public EndAuctionInitiator(UUID auctionId) { this.auctionId = auctionId; } @@ -84,12 +82,12 @@ public SignedTransaction call() throws FlowException { } - @InitiatedBy(Initiator.class) - public static class Responder extends FlowLogic { + @InitiatedBy(EndAuctionInitiator.class) + public static class EndAuctionResponder extends FlowLogic { private FlowSession counterpartySession; - public Responder(FlowSession counterpartySession) { + public EndAuctionResponder(FlowSession counterpartySession) { this.counterpartySession = counterpartySession; } diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionSettlementFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionSettlementFlow.java deleted file mode 100644 index 9522076f..00000000 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/AuctionSettlementFlow.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.corda.samples.flows; - -import co.paralleluniverse.fibers.Suspendable; -import net.corda.core.contracts.Amount; -import net.corda.core.flows.FlowException; -import net.corda.core.flows.FlowLogic; -import net.corda.core.flows.StartableByRPC; - -import java.util.Currency; -import java.util.UUID; - -/** - * This purpose of this flow is the settle the auction which includes: - * - Transferring the highest bid amount to the auctioneer. - * - Transferring the auctioned asset's ownership to the highest bidder - * - Consuming the auction state. - * - * The AuctionDvPFlow does the delivery-vs-payment of the auctioned asset and the bid amount. - * The AuctionExitFlow consumes the auction state. - */ -@StartableByRPC -public class AuctionSettlementFlow extends FlowLogic { - - private final UUID auctionId; - private final Amount amount; - - /** - * Constructor to initialise flow parameters received from rpc. - * - * @param auctionId is the unique id of the auction to be settled - * @param amount is the bid amount which is required to be transferred from the highest bidded to auctioneer to - * settle the auction. - */ - public AuctionSettlementFlow(UUID auctionId, Amount amount) { - this.auctionId = auctionId; - this.amount = amount; - } - - @Override - @Suspendable - public Void call() throws FlowException { - subFlow(new AuctionDvPFlow.Initiator(auctionId, amount)); - subFlow(new AuctionExitFlow.Initiator(auctionId)); - return null; - } -} \ No newline at end of file diff --git a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/CreateAssetFlow.java b/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/CreateAssetFlow.java deleted file mode 100644 index 0ea91a8c..00000000 --- a/Advanced/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/CreateAssetFlow.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.corda.samples.flows; - -import co.paralleluniverse.fibers.Suspendable; -import net.corda.core.contracts.UniqueIdentifier; -import net.corda.core.flows.*; -import net.corda.core.identity.Party; -import net.corda.core.transactions.SignedTransaction; -import net.corda.core.transactions.TransactionBuilder; -import net.corda.samples.contracts.AssetContract; -import net.corda.samples.states.Asset; - -import java.util.Arrays; -import java.util.Collections; - - -/** - * This flow is used to build a transaction to issue an asset on the Corda Ledger, which can later be put on auction. - * It creates a self issues transaction, the state is only issued on the ledger of the party who executes the flow. - */ -@InitiatingFlow -@StartableByRPC -public class CreateAssetFlow extends FlowLogic { - - private final String title; - private final String description; - private final String imageURL; - - /** - * Constructor to initialise flow parameters received from rpc. - * - * @param title of the asset to be issued on ledger - * @param description of the asset to be issued in ledger - * @param imageURL is a url of an image of the asset - */ - public CreateAssetFlow(String title, String description, String imageURL) { - this.title = title; - this.description = description; - this.imageURL = imageURL; - } - - - @Override - @Suspendable - public SignedTransaction call() throws FlowException { - // Choose a notary for the transaction. - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); - - // Create the output state - Asset output = new Asset(new UniqueIdentifier(), title, description, imageURL, - getOurIdentity()); - - // Build the transaction, add the output state and the command to the transaction. - TransactionBuilder transactionBuilder = new TransactionBuilder(notary) - .addOutputState(output) - .addCommand(new AssetContract.Commands.CreateAsset(), - Arrays.asList(getOurIdentity().getOwningKey())); // Required Signers - - // Verify the transaction - transactionBuilder.verify(getServiceHub()); - - // Sign the transaction - SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(transactionBuilder); - - // Notarise the transaction and record the state in the ledger. - return subFlow(new FinalityFlow(signedTransaction, Collections.emptyList())); - } -} \ No newline at end of file diff --git a/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/FlowTests.java b/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/FlowTests.java deleted file mode 100644 index 5b32e7d7..00000000 --- a/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/FlowTests.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.corda.samples; - -import com.google.common.collect.ImmutableList; -import net.corda.core.concurrent.CordaFuture; -import net.corda.core.contracts.Amount; -import net.corda.core.node.NetworkParameters; -import net.corda.core.transactions.SignedTransaction; -import net.corda.samples.flows.BidFlow; -import net.corda.samples.flows.CreateAssetFlow; -import net.corda.samples.flows.CreateAuctionFlow; -import net.corda.samples.states.Asset; -import net.corda.samples.states.AuctionState; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.StartedMockNode; -import net.corda.testing.node.TestCordapp; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Collections; -import java.util.UUID; - -import static org.junit.Assert.*; - -public class FlowTests { - private MockNetwork network; - private StartedMockNode a; - private StartedMockNode b; - - @Before - public void setup() { - network = new MockNetwork( - new MockNetworkParameters( - ImmutableList.of( - TestCordapp.findCordapp("net.corda.samples.flows"), - TestCordapp.findCordapp("net.corda.samples.contracts") - ) - ).withNetworkParameters(new NetworkParameters(4, Collections.emptyList(), - 10485760, 10485760 * 50, Instant.now(), 1, - Collections.emptyMap())) - ); - a = network.createPartyNode(null); - b = network.createPartyNode(null); - network.runNetwork(); - } - - @After - public void tearDown() { - network.stopNodes(); - } - - @Test - public void testCreateAssetFlow() throws Exception { - CreateAssetFlow flow = new CreateAssetFlow("Test Asset", "Dummy Asset", "http://abc.com/dummy.png"); - CordaFuture future = a.startFlow(flow); - network.runNetwork(); - SignedTransaction signedTransaction = future.get(); - Asset asset = (Asset) signedTransaction.getTx().getOutput(0); - assertNotNull(asset); - } - - @Test - public void testCreateAuctionFlow() throws Exception { - CreateAssetFlow assetflow = new CreateAssetFlow("Test Asset", "Dummy Asset", "http://abc.com/dummy.png"); - CordaFuture future = a.startFlow(assetflow); - network.runNetwork(); - SignedTransaction signedTransaction = future.get(); - Asset asset = (Asset) signedTransaction.getTx().getOutput(0); - CreateAuctionFlow.Initiator auctionFlow = new CreateAuctionFlow.Initiator(Amount.parseCurrency("1000 USD"), - asset.getLinearId().getId(), LocalDateTime.ofInstant(Instant.now().plusMillis(30000), ZoneId.systemDefault())); - CordaFuture future1 = a.startFlow(auctionFlow); - network.runNetwork(); - SignedTransaction transaction = future1.get(); - AuctionState auctionState = (AuctionState) transaction.getTx().getOutput(0); - assertNotNull(auctionState); - } -} \ No newline at end of file diff --git a/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/NodeDriver.java b/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/NodeDriver.java deleted file mode 100644 index 591f99a0..00000000 --- a/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/NodeDriver.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.corda.samples; - -import com.google.common.collect.ImmutableList; -import net.corda.core.identity.CordaX500Name; -import net.corda.testing.driver.DriverParameters; -import net.corda.testing.driver.NodeParameters; -import net.corda.testing.node.User; -import com.google.common.collect.ImmutableSet; - -import java.util.List; -import static net.corda.testing.driver.Driver.driver; - -/** - * Allows you to run your nodes through an IDE (as opposed to using deployNodes). Do not use in a production - * environment. - */ -public class NodeDriver { - public static void main(String[] args) { - final List rpcUsers = - ImmutableList.of(new User("user1", "test", ImmutableSet.of("ALL"))); - - driver(new DriverParameters().withStartNodesInProcess(true).withWaitForAllNodesToFinish(true), dsl -> { - try { - dsl.startNode(new NodeParameters() - .withProvidedName(new CordaX500Name("PartyA", "London", "GB")) - .withRpcUsers(rpcUsers)).get(); - dsl.startNode(new NodeParameters() - .withProvidedName(new CordaX500Name("PartyB", "New York", "US")) - .withRpcUsers(rpcUsers)).get(); - } catch (Throwable e) { - System.err.println("Encountered exception in node startup: " + e.getMessage()); - e.printStackTrace(); - } - - return null; - } - ); - } -} diff --git a/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/auction/FlowTests.java b/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/auction/FlowTests.java new file mode 100644 index 00000000..58b2a115 --- /dev/null +++ b/Advanced/auction-cordapp/workflows/src/test/java/net/corda/samples/auction/FlowTests.java @@ -0,0 +1,78 @@ +package net.corda.samples.auction; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.contracts.Amount; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.NetworkParameters; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.auction.flows.CreateAssetFlow; +import net.corda.samples.auction.flows.CreateAuctionFlow; +import net.corda.samples.auction.states.Asset; +import net.corda.samples.auction.states.AuctionState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Collections; + +import static org.junit.Assert.*; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork( + new MockNetworkParameters( + ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.auction.flows"), + TestCordapp.findCordapp("net.corda.samples.auction.contracts") + ) + ).withNetworkParameters(new NetworkParameters(4, Collections.emptyList(), + 10485760, 10485760 * 50, Instant.now(), 1, + Collections.emptyMap()) + ).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void testCreateAssetFlow() throws Exception { + CreateAssetFlow flow = new CreateAssetFlow("Test Asset", "Dummy Asset", "http://abc.com/dummy.png"); + CordaFuture future = a.startFlow(flow); + network.runNetwork(); + SignedTransaction signedTransaction = future.get(); + Asset asset = (Asset) signedTransaction.getTx().getOutput(0); + assertNotNull(asset); + } + + @Test + public void testCreateAuctionFlow() throws Exception { + CreateAssetFlow assetflow = new CreateAssetFlow("Test Asset", "Dummy Asset", "http://abc.com/dummy.png"); + CordaFuture future = a.startFlow(assetflow); + network.runNetwork(); + SignedTransaction signedTransaction = future.get(); + Asset asset = (Asset) signedTransaction.getTx().getOutput(0); + CreateAuctionFlow.CreateAuctionInitiator auctionFlow = new CreateAuctionFlow.CreateAuctionInitiator(Amount.parseCurrency("1000 USD"), + asset.getLinearId().getId(), LocalDateTime.ofInstant(Instant.now().plusMillis(30000), ZoneId.systemDefault())); + CordaFuture future1 = a.startFlow(auctionFlow); + network.runNetwork(); + SignedTransaction transaction = future1.get(); + AuctionState auctionState = (AuctionState) transaction.getTx().getOutput(0); + assertNotNull(auctionState); + } +} \ No newline at end of file diff --git a/Advanced/constants.properties b/Advanced/constants.properties index 0b0f28b2..adcdb361 100644 --- a/Advanced/constants.properties +++ b/Advanced/constants.properties @@ -1,12 +1,12 @@ cordaReleaseGroup=net.corda cordaCoreReleaseGroup=net.corda -cordaVersion=4.5-RC02 -cordaCoreVersion=4.5-RC02 -gradlePluginsVersion=5.0.10 +cordaVersion=4.10 +cordaCoreVersion=4.10 +gradlePluginsVersion=5.0.12 kotlinVersion=1.2.71 junitVersion=4.12 quasarVersion=0.7.10 -log4jVersion =2.11.2 -platformVersion=5 +log4jVersion =2.17.1 +platformVersion=12 slf4jVersion=1.7.25 nettyVersion=4.1.22.Final diff --git a/Basic/rpc-nodeinfo/LICENCE b/Advanced/duediligence-cordapp/LICENCE similarity index 100% rename from Basic/rpc-nodeinfo/LICENCE rename to Advanced/duediligence-cordapp/LICENCE diff --git a/Advanced/duediligence-cordapp/README.md b/Advanced/duediligence-cordapp/README.md new file mode 100644 index 00000000..7dbbf0cb --- /dev/null +++ b/Advanced/duediligence-cordapp/README.md @@ -0,0 +1,66 @@ +# Due Diligence Cordapp + +This cordapp is an example of how blockchain can work in the capital market industry. Due diligence is commonly the first step of any action in the capital market. In many of the industry, it is also required by the regulators to prevent fraudulent and risky transactions. Each firm will have its own due-diligence process and standards, but at the same time, the cost of executing due diligence is also bared by them individually. + +## App Design + + +The above picture is a high level mock overview of a shareable due diligence DLT app. BankA will initiate the original Corporate Records auditing with an autitor. Then it will share the auditiing report with BankB to save BankB's cost on getting the same report. Wise versa, BankB can work with a different auditor and produce a different report and share with BankA. In the implmentation of this sample cordapp, we will only cover one type of the file auditing. + +Notes: another key feature of this app is whitlisting trusted auditors. It is done by utilizing attachment function in Corda. More samples on how to use attachment can be found in the [Features samples folder](../../Features) + + +## Pre-running the app + +Deploying nodes: `./gradlew clean deployNodes` + +Starting the nodes: `./build/nodes/runnodes` + +Uploading whitelisted Auditors: `./gradlew uploadWhitelists` + + + +## Running the Cordapp +Step #1: At PartyA, file the original Corporate Records auditing process with Auditor(Trusted Auditor) +``` +flow start RequestToValidateCorporateRecordsInitiator validater: Trusted Auditor, numberOfFiles: 10 +``` + +Step #2: Go to the Trusted Auditor Node, validate the auditing request(This step symbolize the auditing process by this third party auditor). Put in the linearId which was returned in Step #1. +``` +flow start ValidateCorporateRecordsInitiator linearId: +``` + +Step #3: Go to PartyA, Do a query to confirm that the request has been validated. You should see the variable `qualification=true`. + +``` +run vaultQuery contractStateType: net.corda.samples.duediligence.states.CorporateRecordsAuditRequest +``` +Then, we will instruct PartyA to share a copy of the auditing result with PartyB: (Again, You would need put in the linearId returned from Step #1). The parameter `trustedAuditorAttachment` is a jar file which records the trusted auditors. If PartyA used an untrusted auditor to accquire the corporate records auditing report. He will be prohibited to share with anyone because it is valueless effort(in this business use case, Of course you can modify the business use cases). +``` +flow start ShareAuditingResultInitiator AuditingResultID: , sendTo: BankB, trustedAuditorAttachment: "8DF3275D80B26B9A45AB022F2FDA4A2ED996449B425F8F2245FA5BCF7D1AC587" +``` +This flow will return the LinearId of the copy of auditing report, you would need this in Step #6. + +Step #4: Go to PartyB, do a query to confirm the delievery of copy of the Auditing Report. +``` +run vaultQuery contractStateType: net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest +``` +As of now, the sharing of the trusted auditing report is done. What left now for both PartyA and PartyB in this use case is to upload the Corporate Records auditing report into a due-diligence list, which they can share with a regulator.(You can again alter this step to suit any other use cases). + + +Step #5: Go to PartyA, Attach the Corporate Records auditing report into a due-diligence checklist and report to the Regulator. Again, the approvalId is the linearId returned in Step #1. +``` +flow start CreateCheckListAndAddApprovalInitiator reportTo: Regulator, approvalId: + +``` +Step #6: Go to PartyB, Attach the copy of the Corporate Records auditing report into a due-diligence checklist and report to the Regulator. You would need the linearId that is return from Step #5 +``` +flow start CreateCheckListAndAddApprovalInitiator reportTo: Regulator, approvalId: +``` +Step #7: Go to Regulator, do a query on reported due-diligence checklists. You will be able to see both PartyA and PartyB had filed a due-diligence checklist. +``` +run vaultQuery contractStateType: net.corda.samples.duediligence.states.DueDChecklist + +``` +This use of Distributed technology Corda helped PartyB saved the cost of go through corporate records due-diligence process while still reaching a trusted auditing report. diff --git a/Advanced/duediligence-cordapp/TRADEMARK b/Advanced/duediligence-cordapp/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Advanced/duediligence-cordapp/TRADEMARK @@ -0,0 +1,4 @@ +Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates. All rights reserved. + +For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy at +https://www.r3.com/trademark-policy/. \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/build.gradle b/Advanced/duediligence-cordapp/build.gradle new file mode 100644 index 00000000..ef682822 --- /dev/null +++ b/Advanced/duediligence-cordapp/build.gradle @@ -0,0 +1,154 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + corda_gradle_plugins_version = constants.getProperty("gradlePluginsVersion") + kotlin_version = constants.getProperty("kotlinVersion") + junit_version = constants.getProperty("junitVersion") + quasar_version = constants.getProperty("quasarVersion") + log4j_version = constants.getProperty("log4jVersion") + slf4j_version = constants.getProperty("slf4jVersion") + corda_platform_version = constants.getProperty("platformVersion").toInteger() + //springboot + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/corda-releases' } + } + + dependencies { + classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" + classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" + classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" + } +} + +allprojects {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://jitpack.io' } + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. + } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":workflows") + cordapp project(":contracts") + + cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" + cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=BankA,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=BankB,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=Trusted Auditor,L=New York,C=US" + p2pPort 10011 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=Regulator,L=New York,C=US" + p2pPort 10014 + rpcSettings { + address("localhost:10015") + adminAddress("localhost:10055") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} diff --git a/Advanced/duediligence-cordapp/clients/build.gradle b/Advanced/duediligence-cordapp/clients/build.gradle new file mode 100644 index 00000000..0585ba30 --- /dev/null +++ b/Advanced/duediligence-cordapp/clients/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'org.springframework.boot' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + +dependencies { + // Corda dependencies. + compile "$corda_release_group:corda-rpc:$corda_release_version" + + // CorDapp dependencies. + compile project(":contracts") + compile project(":workflows") + compile("org.springframework.boot:spring-boot-starter-websocket:$spring_boot_version") { + exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" + } + compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + compile "org.apache.logging.log4j:log4j-web:${log4j_version}" + compile "org.slf4j:jul-to-slf4j:$slf4j_version" +} + + +task uploadWhitelists(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.duediligence.client.Client' + args 'localhost:10006', 'localhost:10009', 'localhost:10012' +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/clients/src/main/java/net/corda/samples/duediligence/client/Client.java b/Advanced/duediligence-cordapp/clients/src/main/java/net/corda/samples/duediligence/client/Client.java new file mode 100644 index 00000000..0e5fad53 --- /dev/null +++ b/Advanced/duediligence-cordapp/clients/src/main/java/net/corda/samples/duediligence/client/Client.java @@ -0,0 +1,96 @@ +package net.corda.samples.duediligence.client; + +import com.google.common.base.Charsets; +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.crypto.SecureHash; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import net.corda.samples.duediligence.Constants; + +import java.io.*; +import java.nio.file.FileAlreadyExistsException; +import java.util.List; +import java.util.jar.JarInputStream; +import java.util.stream.Collectors; + +public class Client { + + private static class Companion { + static Logger logger = LoggerFactory.getLogger(Client.class); + } + + /** + * Uploads the jar of blacklisted counterparties with whom agreements cannot be struck to the node. + */ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + String message = "Usage: uploadBlacklist ..."; + throw new IllegalArgumentException(message.toString()); + } + + for (String arg : args) { + NetworkHostAndPort nodeAddress = NetworkHostAndPort.parse(arg); + CordaRPCConnection rpcConnection = new CordaRPCClient(nodeAddress).start("user1", "test"); + CordaRPCOps proxy = rpcConnection.getProxy(); + + SecureHash attachmentHash = Constants.CORPORATE_JAR_HASH; + + // take relative path using substring of constant BLACKLIST_JAR_PATH, check if node contains blacklist already + if (!proxy.attachmentExists(attachmentHash)) { + System.out.println("Working Directory = " + + System.getProperty("user.dir")); + attachmentHash = uploadAttachment(proxy, Constants.CORPORATE_JAR_PATH); + Companion.logger.info("Corporate Auditor List uploaded to node at " + nodeAddress); + } else { + Companion.logger.info("Node already contains Corporate Auditor List, skipping upload at " + nodeAddress); + } + + JarInputStream attachmentJar = downloadAttachment(proxy, attachmentHash); + Companion.logger.info("Corporate Auditor List downloaded from node at " + nodeAddress); + + checkAttachment(attachmentJar, Constants.CORPORATE_ATTACTMENT_FILE_NAME, Constants.CORPORATE_ATTACHMENT_EXPECTED_CONTENTS); + Companion.logger.info("Attachment contents checked on node at " + nodeAddress); + } + + } + + /** + * Uploads the attachment at [attachmentPath] to the node. + */ + private static SecureHash uploadAttachment(CordaRPCOps proxy, String attachmentPath) throws FileNotFoundException, FileAlreadyExistsException { + FileInputStream attachmentUploadInputStream = new FileInputStream(new File(attachmentPath)); + return proxy.uploadAttachment(attachmentUploadInputStream); + } + + /** + * Downloads the attachment with hash [attachmentHash] from the node. + */ + private static JarInputStream downloadAttachment(CordaRPCOps proxy, SecureHash attachmentHash) throws IOException { + InputStream attachmentDownloadInputStream = proxy.openAttachment(attachmentHash); + return new JarInputStream(attachmentDownloadInputStream); + } + + /** + * Checks the [expectedFileName] and [expectedContents] of the downloaded [attachmentJar]. + */ + private static void checkAttachment(JarInputStream attachmentJar, String expectedFileName, List expectedContents) throws IOException { + String name = attachmentJar.getNextEntry().getName(); + while (!name.equals(expectedFileName)) { + name = attachmentJar.getNextEntry().getName(); + } + + BufferedInputStream bisAttachmentJar = new BufferedInputStream(attachmentJar, (8 * 1024)); + InputStreamReader isrAttachmentJar = new InputStreamReader(bisAttachmentJar, Charsets.UTF_8); + BufferedReader brAttachmentJar = new BufferedReader(isrAttachmentJar); + + List contents = brAttachmentJar.lines().collect(Collectors.toList()); + + if (!contents.equals(expectedContents)) { + throw new IllegalArgumentException("Downloaded JAR did not have the expected contents."); + } + + } +} \ No newline at end of file diff --git a/Basic/ping-pong/client-java/src/main/resources/log4j2.xml b/Advanced/duediligence-cordapp/config/dev/log4j2.xml similarity index 100% rename from Basic/ping-pong/client-java/src/main/resources/log4j2.xml rename to Advanced/duediligence-cordapp/config/dev/log4j2.xml diff --git a/Basic/yo-cordapp/config/test/log4j2.xml b/Advanced/duediligence-cordapp/config/test/log4j2.xml similarity index 100% rename from Basic/yo-cordapp/config/test/log4j2.xml rename to Advanced/duediligence-cordapp/config/test/log4j2.xml diff --git a/Advanced/duediligence-cordapp/contracts/build.gradle b/Advanced/duediligence-cordapp/contracts/build.gradle new file mode 100644 index 00000000..9d669fa4 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Due Diligence Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/Constants.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/Constants.java new file mode 100644 index 00000000..24d84b8c --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/Constants.java @@ -0,0 +1,26 @@ +package net.corda.samples.duediligence; + +import net.corda.core.crypto.SecureHash; + +import java.util.Arrays; +import java.util.List; + +public interface Constants { + //Whitelisted Corporate Auditors + String CORPORATE_JAR_PATH = "../contracts/src/main/resources/corporateAuditors.jar"; + SecureHash CORPORATE_JAR_HASH = SecureHash.parse("8DF3275D80B26B9A45AB022F2FDA4A2ED996449B425F8F2245FA5BCF7D1AC587"); + String CORPORATE_ATTACTMENT_FILE_NAME = "whitelistedCorporateAuditors.txt"; + List CORPORATE_ATTACHMENT_EXPECTED_CONTENTS = Arrays.asList( + "Crossland Savings", + "Trusted Auditor" + ); + + //Whitelisted Finance Auditors + String FINANCIAL_JAR_PATH = "../contracts/src/main/resources/financialAuditors.jar"; + SecureHash FINANCIAL_JAR_HASH = SecureHash.parse("DE7635E2AD626BC57D811D065F7841FC35839FA2D0CF095857CACE1579756A1C"); + String FINANCIAL_ATTACTMENT_FILE_NAME = "whitelistedFinancialAuditors.txt"; + List FINANCIAL_ATTACHMENT_EXPECTED_CONTENTS = Arrays.asList( + "Detroit Partners Group", + "Tifton Banking Company" + ); +} diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/CorporateRecordsContract.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/CorporateRecordsContract.java new file mode 100644 index 00000000..c0f892b6 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/CorporateRecordsContract.java @@ -0,0 +1,125 @@ +package net.corda.samples.duediligence.contracts; + +import kotlin.text.Charsets; +import net.corda.core.contracts.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.jar.JarInputStream; +import java.util.stream.Collectors; + +import static net.corda.core.contracts.ContractsDSL.requireThat; +import static net.corda.samples.duediligence.Constants.CORPORATE_JAR_HASH; + +public class CorporateRecordsContract implements Contract { + + public static final String CorporateRecordsContract_ID = "net.corda.samples.duediligence.contracts.CorporateRecordsContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + final CommandWithParties command = tx.getCommands().get(0); + + //Propose request + if (command.getValue() instanceof Commands.Propose) { + requireThat(require -> { + require.using("There are no inputs", tx.getInputs().isEmpty()); + require.using("Only one output state should be created.", tx.getOutputs().size() == 1); + require.using("The single output is of type CorporateRecords State", tx.outputsOfType(CorporateRecordsAuditRequest.class).size() == 1); + return null; + }); + }else if (command.getValue() instanceof Commands.Validate) { //Validate Rules + requireThat(require -> { + require.using("Only one output state should be created.", tx.getOutputs().size() == 1); + require.using("The single output is of type CorporateRecords State", tx.outputsOfType(CorporateRecordsAuditRequest.class).size() == 1); + return null; + }); + }else if (command.getValue() instanceof Commands.Reject) { //Rejection Rules + requireThat(require -> { + require.using("Only one output state should be created.", tx.getOutputs().size() == 1); + require.using("The single output is of type CorporateRecords State", tx.outputsOfType(CorporateRecordsAuditRequest.class).size() == 1); + return null; + }); + } + else if (command.getValue() instanceof Commands.Share) { //Share Rules + + // Check if the report has any value to share among other network participants. + List nonContractAttachments = tx.getAttachments() + .stream() + .filter(p -> !(p instanceof ContractAttachment)) + .map(p -> (Attachment) p) + .collect(Collectors.toList()); + + Attachment attached = nonContractAttachments.get(0); + + ContractsDSL.requireThat(req -> { + req.using("The transaction should have a single non-contract attachment", + nonContractAttachments.size() == 1); + req.using("The jar's hash should be correct", attached.getId().equals(CORPORATE_JAR_HASH)); + return null; + }); + + // Extract the whitelisted company names from the JAR. + List whitelistedCompanies = new ArrayList<>(); + JarInputStream attachmentJar = attached.openAsJAR(); + try { + while (!attachmentJar.getNextEntry().getName().equals("whitelistedCorporateAuditors.txt")) { + // Calling 'getNextEntry()' causes us to scroll through the JAR. + } + InputStreamReader isrWhitelistlist = new InputStreamReader(attachmentJar, Charsets.UTF_8); + BufferedReader brWhitelist = new BufferedReader(isrWhitelistlist, (8 * 1024)); // Note - changed BIR to BR + + String company = brWhitelist.readLine(); + + while (company != null) { + whitelistedCompanies.add(company); + company = brWhitelist.readLine(); + } + } catch (IOException e) { + System.out.println("error reading whitelistedCorporateAuditors.txt"); + } + + // Constraints on the Whitelist parties + //CorporateRecordsAuditRequest corporateRecords = tx.outputsOfType(CorporateRecordsAuditRequest.class).get(0); + //CorporateRecordsAuditRequest corporateRecords = tx.inputsOfType(CorporateRecordsAuditRequest.class).get(0); + StateAndRef recordsStateAndRef = tx.referenceInputRefsOfType(CorporateRecordsAuditRequest.class).get(0); + CorporateRecordsAuditRequest corporateRecords = recordsStateAndRef.getState().getData(); + + List participants = new ArrayList<>(); + List participantsOrgs = new ArrayList<>(); + for (AbstractParty p : corporateRecords.getParticipants()) { + Party participant = (Party) p; + participantsOrgs.add(participant.getName().getOrganisation()); + participants.add(participant); + } + + // overlap is whether any participants in the transaction belong to a whitelisted org. + Set overlap = new HashSet<>(whitelistedCompanies); //Crossland Savings & TCF National Bank Wisconsin + overlap.retainAll(new HashSet<>(participantsOrgs)); // intersection | TCF & PartyA + + ContractsDSL.requireThat(req -> { + req.using("The agreement did not use any whitelisted auditors" + overlap.toString(), (!overlap.isEmpty())); + return null; + }); + } + } + + public interface Commands extends CommandData { + class Propose implements Commands { } + class Validate implements Commands { } + class Reject implements Commands { } + class Share implements Commands { } + class Report implements Commands { } + } + +} diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/DueDChecklistContract.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/DueDChecklistContract.java new file mode 100644 index 00000000..0bc61562 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/contracts/DueDChecklistContract.java @@ -0,0 +1,37 @@ +package net.corda.samples.duediligence.contracts; + +import net.corda.core.contracts.*; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class DueDChecklistContract implements Contract { + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + final CommandWithParties command = tx.getCommands().get(0); + + //Propose request + if (command.getValue() instanceof DueDChecklistContract.Commands.Add) { + requireThat(require -> { + StateAndRef input = tx.getInputs().get(0); + if (input.getState().getData().getClass() == CorporateRecordsAuditRequest.class){ + CorporateRecordsAuditRequest request = (CorporateRecordsAuditRequest)input.getState().getData(); + require.using("Qualification Must be True", request.getQualification()); + }else{ + CopyOfCoporateRecordsAuditRequest request = (CopyOfCoporateRecordsAuditRequest) input.getState().getData(); + require.using("Qualification Must be True", request.getQualification()); + } + return null; + }); + } + } + + public interface Commands extends CommandData { + class Add implements Commands { } + } +} diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CopyOfCoporateRecordsAuditRequest.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CopyOfCoporateRecordsAuditRequest.java new file mode 100644 index 00000000..cb403c35 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CopyOfCoporateRecordsAuditRequest.java @@ -0,0 +1,74 @@ +package net.corda.samples.duediligence.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.crypto.SecureHash; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +@BelongsToContract(CorporateRecordsContract.class) +public class CopyOfCoporateRecordsAuditRequest implements LinearState { + + + //private variables + private Boolean qualification = false; + private Party originalOwner; + private Party copyReceiver; + private UniqueIdentifier originalRequestId; + private SecureHash originalReportTxId; + private UniqueIdentifier linearId; + private Party originalValidater; + + public CopyOfCoporateRecordsAuditRequest(Party originalOwner, Party copyReceiver, + UniqueIdentifier originalRequestId, SecureHash originalReportTxId, + Party originalValidater,Boolean qualification, UniqueIdentifier linearId) { + this.originalOwner = originalOwner; + this.copyReceiver = copyReceiver; + this.originalRequestId = originalRequestId; + this.originalValidater = originalValidater; + this.originalReportTxId = originalReportTxId; + this.linearId = linearId; + this.qualification = qualification; + } + + public Party getOriginalOwner() { + return originalOwner; + } + + public Party getCopyReceiver() { + return copyReceiver; + } + + public UniqueIdentifier getOriginalRequestId() { + return originalRequestId; + } + + public UniqueIdentifier getLinearId() { + return linearId; + } + + public Party getoriginalValidater() { + return originalValidater; + } + + public Boolean getQualification() { + return qualification; + } + + public SecureHash getOriginalReportTxId() { + return originalReportTxId; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(copyReceiver); + } +} diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CorporateRecordsAuditRequest.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CorporateRecordsAuditRequest.java new file mode 100644 index 00000000..d6433574 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/CorporateRecordsAuditRequest.java @@ -0,0 +1,79 @@ +package net.corda.samples.duediligence.states; + +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +// ********* +// * State * +// ********* +@BelongsToContract(CorporateRecordsContract.class) +public class CorporateRecordsAuditRequest implements LinearState { + /*This can include reviewing incorporation documents, company constitutions, + organisational charts, a list of security holders, employee share plans and any + options granted to acquire securities.*/ + + //private variables + private Boolean qualification = false; + private Party applicant; + private Party validater; + private int numberOfFiles; + private UniqueIdentifier linearId; + + /* Constructor of your Corda state */ + @ConstructorForDeserialization + public CorporateRecordsAuditRequest(Party applicant, Party validater, int numberOfFiles, UniqueIdentifier linearId, Boolean qualification) { + this.applicant = applicant; + this.validater = validater; + this.numberOfFiles = numberOfFiles; + this.linearId = linearId; + this.qualification = qualification; + } + + public CorporateRecordsAuditRequest(Party applicant, Party validater, int numberOfFiles, UniqueIdentifier linearId) { + this.applicant = applicant; + this.validater = validater; + this.numberOfFiles = numberOfFiles; + this.linearId = linearId; + } + + public CorporateRecordsAuditRequest(Party applicant, Party validater, int numberOfFiles) { + this.qualification = qualification; + this.applicant = applicant; + this.validater = validater; + this.numberOfFiles = numberOfFiles; + this.linearId = new UniqueIdentifier(); + } + + //getters + public Boolean getQualification() { return qualification; } + public Party getApplicant() { return applicant; } + public Party getValidater() { return validater; } + public int getNumberOfFiles() { return numberOfFiles; } + + /* This method will indicate who are the participants and required signers when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + return Arrays.asList(applicant,validater); + } + + public void validatedAndApproved (){ + this.qualification = true; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.linearId; + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/DueDChecklist.java b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/DueDChecklist.java new file mode 100644 index 00000000..36cf1f54 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/java/net/corda/samples/duediligence/states/DueDChecklist.java @@ -0,0 +1,84 @@ +package net.corda.samples.duediligence.states; + +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.duediligence.contracts.DueDChecklistContract; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// ********* +// * State * +// ********* +@BelongsToContract(DueDChecklistContract.class) +public class DueDChecklist implements LinearState { + + //private variables + private int numberOfapprovalsNeeded; + private String status = "INCOMPLETE"; + private Party operationNode; + private Party reportTo; + private List attachedApprovals = new ArrayList(); + private UniqueIdentifier linearID; + + /* Constructor of your Corda state */ + @ConstructorForDeserialization + public DueDChecklist(int numberOfapprovalsNeeded, String status, Party operationNode, Party reportTo, List attachedApprovals, UniqueIdentifier linearID) { + this.numberOfapprovalsNeeded = numberOfapprovalsNeeded; + this.status = status; + this.operationNode = operationNode; + this.reportTo = reportTo; + this.attachedApprovals = attachedApprovals; + this.linearID = linearID; + } + + //Modification will + public DueDChecklist(int numberOfapprovalsNeeded, Party operationNode, Party reportTo, UniqueIdentifier linearID) { + this.numberOfapprovalsNeeded = numberOfapprovalsNeeded; + this.operationNode = operationNode; + this.reportTo = reportTo; + this.linearID = linearID; + } + + //getters + public int getNumberOfapprovals() { return numberOfapprovalsNeeded; } + public String getStatus() { return status; } + public Party getOperationNode() { return operationNode; } + + public int getNumberOfapprovalsNeeded() { + return numberOfapprovalsNeeded; + } + + public List getAttachedApprovals() { + return attachedApprovals; + } + + public void uploadApproval(UniqueIdentifier approvalId){ + List copyOfExistingList = new ArrayList(); + for (UniqueIdentifier id : attachedApprovals){ + copyOfExistingList.add(id); + } + copyOfExistingList.add(approvalId); + this.attachedApprovals = copyOfExistingList; + } + + + /* This method will indicate who are the participants and required signers when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + return Arrays.asList(operationNode,reportTo); + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return this.linearID; + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/main/resources/corporateAuditors.jar b/Advanced/duediligence-cordapp/contracts/src/main/resources/corporateAuditors.jar new file mode 100644 index 00000000..b4b25be1 Binary files /dev/null and b/Advanced/duediligence-cordapp/contracts/src/main/resources/corporateAuditors.jar differ diff --git a/Advanced/duediligence-cordapp/contracts/src/main/resources/financialAuditors.jar b/Advanced/duediligence-cordapp/contracts/src/main/resources/financialAuditors.jar new file mode 100644 index 00000000..d50b1452 Binary files /dev/null and b/Advanced/duediligence-cordapp/contracts/src/main/resources/financialAuditors.jar differ diff --git a/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedCorporateAuditors.txt b/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedCorporateAuditors.txt new file mode 100644 index 00000000..fa5db9de --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedCorporateAuditors.txt @@ -0,0 +1,2 @@ +Crossland Savings +Trusted Auditor \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedFinancialAuditors.txt b/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedFinancialAuditors.txt new file mode 100644 index 00000000..3a26bc0e --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/main/resources/whitelistedFinancialAuditors.txt @@ -0,0 +1,2 @@ +Detroit Partners Group +Tifton Banking Company \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/ContractTests.java b/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/ContractTests.java new file mode 100644 index 00000000..e992d98e --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/ContractTests.java @@ -0,0 +1,40 @@ +package net.corda.samples.duediligence.contracts; + +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; + +import static net.corda.samples.duediligence.contracts.CorporateRecordsContract.CorporateRecordsContract_ID; +import static net.corda.testing.node.NodeTestUtils.ledger; + +public class ContractTests { + private final MockServices ledgerServices = new MockServices( + Arrays.asList("net.corda.samples.duediligence.contracts") + ); + public static TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + public static TestIdentity bob = new TestIdentity(new CordaX500Name("Bob", "TestCity", "US")); + + @Test + public void ProposeTransactionshouldhavezeroinput() { + CorporateRecordsAuditRequest cr = new CorporateRecordsAuditRequest(alice.getParty(),bob.getParty(),10); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(CorporateRecordsContract_ID, cr); + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new CorporateRecordsContract.Commands.Propose()); + tx.output(CorporateRecordsContract_ID, cr); + return tx.fails(); + }); + l.transaction(tx -> { + tx.command(Arrays.asList(alice.getPublicKey(), bob.getPublicKey()), new CorporateRecordsContract.Commands.Propose()); + tx.output(CorporateRecordsContract_ID, cr); + return tx.verifies(); // As there are no input sates + }); + return null; + }); + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/StateTests.java b/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/StateTests.java new file mode 100644 index 00000000..77320744 --- /dev/null +++ b/Advanced/duediligence-cordapp/contracts/src/test/java/net/corda/samples/duediligence/contracts/StateTests.java @@ -0,0 +1,18 @@ +package net.corda.samples.duediligence.contracts; + +import net.corda.core.identity.Party; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class StateTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void hasFieldOfCorrectType() throws NoSuchFieldException { + // Does the message field exist? + CorporateRecordsAuditRequest.class.getDeclaredField("applicant"); + // Is the message field of the correct type? + assert(CorporateRecordsAuditRequest.class.getDeclaredField("applicant").getType().equals(Party.class)); + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/due-d diagram.png b/Advanced/duediligence-cordapp/due-d diagram.png new file mode 100644 index 00000000..7dd6f0c8 Binary files /dev/null and b/Advanced/duediligence-cordapp/due-d diagram.png differ diff --git a/Advanced/duediligence-cordapp/gradle.properties b/Advanced/duediligence-cordapp/gradle.properties new file mode 100644 index 00000000..5a45466c --- /dev/null +++ b/Advanced/duediligence-cordapp/gradle.properties @@ -0,0 +1,3 @@ +name=Due diligence Cordapp +group=com.duediligence +version=1.0 \ No newline at end of file diff --git a/Basic/spring-webserver/gradle/wrapper/gradle-wrapper.jar b/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from Basic/spring-webserver/gradle/wrapper/gradle-wrapper.jar rename to Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.jar diff --git a/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.properties b/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Advanced/duediligence-cordapp/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#Fri Aug 25 12:50:39 BST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip + diff --git a/Basic/rpc-nodeinfo/gradlew b/Advanced/duediligence-cordapp/gradlew similarity index 100% rename from Basic/rpc-nodeinfo/gradlew rename to Advanced/duediligence-cordapp/gradlew diff --git a/Basic/rpc-nodeinfo/gradlew.bat b/Advanced/duediligence-cordapp/gradlew.bat similarity index 100% rename from Basic/rpc-nodeinfo/gradlew.bat rename to Advanced/duediligence-cordapp/gradlew.bat diff --git a/Advanced/duediligence-cordapp/repositories.gradle b/Advanced/duediligence-cordapp/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Advanced/duediligence-cordapp/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Advanced/duediligence-cordapp/settings.gradle b/Advanced/duediligence-cordapp/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Advanced/duediligence-cordapp/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/workflows/build.gradle b/Advanced/duediligence-cordapp/workflows/build.gradle new file mode 100644 index 00000000..996f079c --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Due Diligence Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +dependencies { + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/workflows/src/integrationTest/java/net/corda/samples/duediligence/DriverBasedTest.java b/Advanced/duediligence-cordapp/workflows/src/integrationTest/java/net/corda/samples/duediligence/DriverBasedTest.java new file mode 100644 index 00000000..17a3d698 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/integrationTest/java/net/corda/samples/duediligence/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.duediligence; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/AddApprovalsToCheckList.java b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/AddApprovalsToCheckList.java new file mode 100644 index 00000000..a97fdb1f --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/AddApprovalsToCheckList.java @@ -0,0 +1,107 @@ +package net.corda.samples.duediligence.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.duediligence.contracts.DueDChecklistContract; +import net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.DueDChecklist; + +import javax.naming.ldap.UnsolicitedNotification; +import java.util.Arrays; +import java.util.UUID; + +public class AddApprovalsToCheckList { + + @InitiatingFlow + @StartableByRPC + public static class CreateCheckListAndAddApprovalInitiator extends FlowLogic { + + private Party reportTo; + private UniqueIdentifier approvalId; + + public CreateCheckListAndAddApprovalInitiator(Party reportTo, UniqueIdentifier approvalId) { + this.reportTo = reportTo; + this.approvalId = approvalId; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + //Query the input + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(approvalId.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(ContractState.class, inputCriteria).getStates().get(0); + + //extract the notary + Party notary = inputStateAndRef.getState().getNotary(); + + + //create due-diligence Checklist + DueDChecklist checklist = new DueDChecklist(3,getOurIdentity(),reportTo,new UniqueIdentifier()); + checklist.uploadApproval(this.approvalId); + + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addInputState(inputStateAndRef) + .addOutputState(checklist) + .addCommand(new DueDChecklistContract.Commands.Add(),Arrays.asList(getOurIdentity().getOwningKey(),reportTo.getOwningKey())); + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(reportTo); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker())); + + // Notarise and record the transaction in both parties' vaults. + if (inputStateAndRef.getState().getData().getClass() == CorporateRecordsAuditRequest.class){ + CorporateRecordsAuditRequest request = (CorporateRecordsAuditRequest) inputStateAndRef.getState().getData(); + return subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession,initiateFlow(request.getValidater())))); + }else{ + CopyOfCoporateRecordsAuditRequest request = (CopyOfCoporateRecordsAuditRequest) inputStateAndRef.getState().getData(); + return subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession,initiateFlow(request.getoriginalValidater()),initiateFlow(request.getOriginalOwner())))); + } + } + } + + @InitiatedBy(CreateCheckListAndAddApprovalInitiator.class) + public static class CreateCheckListAndAddApprovalResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public CreateCheckListAndAddApprovalResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/RequestToValidateCorporateRecords.java b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/RequestToValidateCorporateRecords.java new file mode 100644 index 00000000..c8b20d09 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/RequestToValidateCorporateRecords.java @@ -0,0 +1,91 @@ +package net.corda.samples.duediligence.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.core.identity.CordaX500Name; + +import java.util.Arrays; +import java.util.List; + +public class RequestToValidateCorporateRecords { + + @InitiatingFlow + @StartableByRPC + public static class RequestToValidateCorporateRecordsInitiator extends FlowLogic { + + //private variables + private Party validater; + private int numberOfFiles; + + + public RequestToValidateCorporateRecordsInitiator(Party validater, int numberOfFiles) { + this.validater = validater; + this.numberOfFiles = numberOfFiles; + + } + + @Suspendable + @Override + public String call() throws FlowException { + //notary + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + //Initiate Corporate Records validation + CorporateRecordsAuditRequest cr = new CorporateRecordsAuditRequest(getOurIdentity(),this.validater,this.numberOfFiles); + + //Build transaction + final TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addOutputState(cr) + .addCommand(new CorporateRecordsContract.Commands.Propose(), + Arrays.asList(getOurIdentity().getOwningKey(),validater.getOwningKey())); + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(validater); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker())); + + // Notarise and record the transaction in both parties' vaults. + subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))); + return "Corporate Records Auditing Request has sent to: " + cr.getValidater().getName().getOrganisation() + +"\nCase Id: "+ cr.getLinearId(); + } + } + + @InitiatedBy(RequestToValidateCorporateRecordsInitiator.class) + public static class RequestToValidateCorporateRecordsResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public RequestToValidateCorporateRecordsResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } +} diff --git a/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ShareAuditingResult.java b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ShareAuditingResult.java new file mode 100644 index 00000000..5389ad64 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ShareAuditingResult.java @@ -0,0 +1,112 @@ +package net.corda.samples.duediligence.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.CommandAndState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import net.corda.samples.duediligence.states.CopyOfCoporateRecordsAuditRequest; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; + +import java.util.Arrays; +import java.util.UUID; + +public class ShareAuditingResult { + + @InitiatingFlow + @StartableByRPC + public static class ShareAuditingResultInitiator extends FlowLogic { + + private UniqueIdentifier AuditingResultID; + private Party sendTo; + private static SecureHash trustedAuditorAttachment; + + + public ShareAuditingResultInitiator(UniqueIdentifier AuditingResultID, Party sendTo, SecureHash trustedAuditorAttachment) { + this.AuditingResultID = AuditingResultID; + this.sendTo = sendTo; + this.trustedAuditorAttachment = trustedAuditorAttachment; + + } + + @Override + @Suspendable + public String call() throws FlowException { + + //Query the input + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(AuditingResultID.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(CorporateRecordsAuditRequest.class, inputCriteria).getStates().get(0); + CorporateRecordsAuditRequest input = (CorporateRecordsAuditRequest) inputStateAndRef.getState().getData(); + + //Send the copy to PartyB. + SignedTransaction originalTx = getServiceHub().getValidatedTransactions().getTransaction(inputStateAndRef.getRef().getTxhash()); + //subFlow(new SendTransactionFlow(initiateFlow(sendTo), originalTx)); + + //extract the notary + Party notary = inputStateAndRef.getState().getNotary(); + //CorporateRecordsAuditRequest imageState = new CorporateRecordsAuditRequest(input.getApplicant(),input.getValidater(),input.getNumberOfFiles(),input.getLinearId(),input.getQualification()); + UniqueIdentifier copyId = new UniqueIdentifier(); + CopyOfCoporateRecordsAuditRequest copyOfResult = new CopyOfCoporateRecordsAuditRequest(input.getApplicant(),sendTo,input.getLinearId(), + originalTx.getId(), input.getValidater(), input.getQualification(), copyId); + + TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addReferenceState(inputStateAndRef.referenced()) + .addOutputState(copyOfResult) + .addCommand(new CorporateRecordsContract.Commands.Share(), Arrays.asList(input.getApplicant().getOwningKey(),sendTo.getOwningKey())) + .addAttachment(trustedAuditorAttachment); + ; + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(sendTo); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker())); + + // Notarise and record the transaction in both parties' vaults. + subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))); + return "A Copy of Corporate Auditing Report has sent to" + sendTo.getName().getOrganisation() + + "\nID of the Copy: "+ copyId; + } + } + + @InitiatedBy(ShareAuditingResultInitiator.class) + public static class ShareAuditingResultResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public ShareAuditingResultResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ValidateCorporateRecords.java b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ValidateCorporateRecords.java new file mode 100644 index 00000000..ee553798 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/main/java/net/corda/samples/duediligence/flows/ValidateCorporateRecords.java @@ -0,0 +1,99 @@ +package net.corda.samples.duediligence.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.duediligence.contracts.CorporateRecordsContract; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; + +import java.util.Arrays; +import java.util.UUID; + +public class ValidateCorporateRecords { + + @InitiatingFlow + @StartableByRPC + public static class ValidateCorporateRecordsInitiator extends FlowLogic { + + private UniqueIdentifier linearId; + + + public ValidateCorporateRecordsInitiator(UniqueIdentifier linearId) { + this.linearId = linearId; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + + //Query the input + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(linearId.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(CorporateRecordsAuditRequest.class, inputCriteria).getStates().get(0); + CorporateRecordsAuditRequest input = (CorporateRecordsAuditRequest) inputStateAndRef.getState().getData(); + + //extract the notary + Party notary = inputStateAndRef.getState().getNotary(); + + //Creating the output + CorporateRecordsAuditRequest output = new CorporateRecordsAuditRequest(input.getApplicant(),getOurIdentity(),input.getNumberOfFiles(),input.getLinearId()); + + //set validation status to true + output.validatedAndApproved(); + + //Build transaction + final TransactionBuilder txBuilder = new TransactionBuilder(notary) + .addInputState(inputStateAndRef) + .addOutputState(output) + .addCommand(new CorporateRecordsContract.Commands.Validate(), + Arrays.asList(getOurIdentity().getOwningKey(),input.getApplicant().getOwningKey())); + + // Verify that the transaction is valid. + txBuilder.verify(getServiceHub()); + + // Sign the transaction. + final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Send the state to the counterparty, and receive it back with their signature. + FlowSession otherPartySession = initiateFlow(input.getApplicant()); + final SignedTransaction fullySignedTx = subFlow( + new CollectSignaturesFlow(partSignedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker())); + + // Notarise and record the transaction in both parties' vaults. + return subFlow(new FinalityFlow(fullySignedTx, Arrays.asList(otherPartySession))); + } + } + + @InitiatedBy(ValidateCorporateRecordsInitiator.class) + public static class ValidateCorporateRecordsResponder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public ValidateCorporateRecordsResponder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } +} diff --git a/Advanced/duediligence-cordapp/workflows/src/test/java/net/corda/samples/duediligence/FlowTests.java b/Advanced/duediligence-cordapp/workflows/src/test/java/net/corda/samples/duediligence/FlowTests.java new file mode 100644 index 00000000..ae0438d4 --- /dev/null +++ b/Advanced/duediligence-cordapp/workflows/src/test/java/net/corda/samples/duediligence/FlowTests.java @@ -0,0 +1,66 @@ +package net.corda.samples.duediligence; + +import com.google.common.collect.ImmutableList; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.samples.duediligence.flows.RequestToValidateCorporateRecords; +import net.corda.samples.duediligence.states.CorporateRecordsAuditRequest; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import static org.junit.Assert.assertEquals; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.duediligence.contracts"), + TestCordapp.findCordapp("net.corda.samples.duediligence.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void CreationAndSigningTheAuditingRequest() throws ExecutionException, InterruptedException { + RequestToValidateCorporateRecords.RequestToValidateCorporateRecordsInitiator flow1 = + new RequestToValidateCorporateRecords.RequestToValidateCorporateRecordsInitiator(b.getInfo().getLegalIdentities().get(0),10); + Future future = a.startFlow(flow1); + network.runNetwork(); + String result1 = future.get(); + System.out.println(result1); + int subString = result1.indexOf("Case Id: "); + String ApproalID = result1.substring(subString+9); + System.out.println("-"+ ApproalID+"-"); + + UniqueIdentifier id = UniqueIdentifier.Companion.fromString(ApproalID); + //Query the input + QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria() + .withUuid(Arrays.asList(UUID.fromString(id.toString()))) + .withStatus(Vault.StateStatus.UNCONSUMED) + .withRelevancyStatus(Vault.RelevancyStatus.RELEVANT); + StateAndRef inputStateAndRef = a.getServices().getVaultService().queryBy(ContractState.class, inputCriteria).getStates().get(0); + CorporateRecordsAuditRequest result = (CorporateRecordsAuditRequest)inputStateAndRef.getState().getData(); + assertEquals(result.getLinearId(),id); + } +} diff --git a/Advanced/negotiation-cordapp/README.md b/Advanced/negotiation-cordapp/README.md index 9366b447..582340f6 100644 --- a/Advanced/negotiation-cordapp/README.md +++ b/Advanced/negotiation-cordapp/README.md @@ -1,4 +1,4 @@ -.# negotiation cordapp +# Negotiation Cordapp This CorDapp shows how multi-party negotiation is handled on the Corda ledger, in the absence of an API for user interaction. @@ -16,21 +16,21 @@ accept or modify the proposal, this attempt will be rejected automatically at th ### Flows -We start with the proposal flow implemented in [ProposalFlow.java](./workflows/src/main/java/negotiation/flows/ProposalFlow.java) +We start with the proposal flow implemented in `ProposalFlow.java`. -The modification of the proposal is implemented in [ModificationFlow.java](./workflows/src/main/java/negotiation/flows/ModificationFlow.java#L42-L49). +The modification of the proposal is implemented in `ModificationFlow.java`. -In the [AcceptanceFlow](./workflows/src/main/java/negotiation/flows/AcceptanceFlow.java#L42-L75), we receive the modified ProposalState and it's converted into a TradeState. +In the `AcceptanceFlow.java`, we receive the modified ProposalState and it's converted into a TradeState. ## Usage -### Pre-requisites: +### Pre-Requisites +For development environment setup, please refer to: [Setup Guide](https://docs.corda.net/getting-set-up.html). -See https://docs.corda.net/getting-set-up.html. ### Running the nodes: @@ -44,7 +44,7 @@ Then type: (to run the nodes) ./build/nodes/runnodes ``` -We will interact with this CorDapp via the nodes' CRaSH shells. +We will interact with this CorDapp via the nodes' [CRaSH](https://docs.corda.net/docs/corda-os/shell.html) shells. First, go the the shell of PartyA, and propose a deal with yourself as buyer and a value of 10 to PartyB: @@ -52,7 +52,7 @@ First, go the the shell of PartyA, and propose a deal with yourself as buyer and We can now look at the proposals in the PartyA's vault: - run vaultQuery contractStateType: negotiation.states.ProposalState + run vaultQuery contractStateType: ProposalState If we note down the state's `linearId.id`, we can now modify the proposal from the shell of PartyB by running: @@ -65,4 +65,4 @@ Finally, let's have PartyA accept the proposal: We can now see the accepted trade in our vault with the new value by running the command (note we are now querying for `TradeState`s, not `ProposalState`s): - run vaultQuery contractStateType: negotiation.states.TradeState + run vaultQuery contractStateType: TradeState diff --git a/Advanced/negotiation-cordapp/build.gradle b/Advanced/negotiation-cordapp/build.gradle index 6f7e2f41..d98cff3d 100644 --- a/Advanced/negotiation-cordapp/build.gradle +++ b/Advanced/negotiation-cordapp/build.gradle @@ -4,58 +4,151 @@ buildscript { ext { corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") corda_release_version = constants.getProperty("cordaVersion") - corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") corda_core_release_version = constants.getProperty("cordaCoreVersion") corda_gradle_plugins_version = constants.getProperty("gradlePluginsVersion") + kotlin_version = constants.getProperty("kotlinVersion") junit_version = constants.getProperty("junitVersion") quasar_version = constants.getProperty("quasarVersion") log4j_version = constants.getProperty("log4jVersion") slf4j_version = constants.getProperty("slf4jVersion") - corda_platform_version = constants.getProperty("platformVersion") + corda_platform_version = constants.getProperty("platformVersion").toInteger() + //springboot + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' } repositories { + mavenLocal() mavenCentral() - jcenter() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'http://software.r3.com/artifactory/corda-lib' } + maven { url 'http://software.r3.com/artifactory/corda-lib-dev' } + maven { url 'https://download.corda.net/maven/corda-releases' } + + // Corda dependencies for the patched Quasar version + maven { url "https://download.corda.net/maven/corda-dependencies" } // access to the patched Quasar version } dependencies { classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" } } allprojects { + apply from: "${rootProject.projectDir}/repositories.gradle" apply plugin: 'java' repositories { mavenLocal() - jcenter() + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + // Can be removed post-release - used to get nightly snapshot build. + maven { url 'https://download.corda.net/maven/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-lib-dev' } maven { url 'https://jitpack.io' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } - maven { url 'https://repo.gradle.org/gradle/libs-releases' } + maven { url "https://repo.gradle.org/gradle/libs-releases-local" } } tasks.withType(JavaCompile) { - options.compilerArgs << "-parameters" // Required for shell commands. + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. } + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' - configurations { - compile { - // We want to use SLF4J's version of these bindings: jcl-over-slf4j - // Remove any transitive dependency on Apache's version. - exclude group: 'commons-logging', module: 'commons-logging' +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") } } +} - jar { - // CorDapps do not configure a Node's logging. - exclude '**/log4j2*.xml' +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":workflows") + cordapp project(":contracts") + + cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" + cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaDriver "net.corda:corda-shell:4.10" + +} + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + workflow { + name "Negotiation CorDapp" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + nodeDefaults { + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true + } + + node { + name "O=Notary,L=London,C=GB" + notary = [validating: false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=PartyA,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=PartyB,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} + +task installQuasar(type: Copy) { + destinationDir rootProject.file("lib") + from(configurations.quasar) { + rename 'quasar-core(.*).jar', 'quasar.jar' } } diff --git a/Advanced/negotiation-cordapp/contracts/build.gradle b/Advanced/negotiation-cordapp/contracts/build.gradle index 7fdfb05c..589d8ab7 100644 --- a/Advanced/negotiation-cordapp/contracts/build.gradle +++ b/Advanced/negotiation-cordapp/contracts/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'java' apply plugin: 'net.corda.plugins.cordapp' -jar.baseName = "timesheet-timesheet-contracts" +//jar.baseName = "timesheet-timesheet-contracts" TODO remove this cordapp { targetPlatformVersion corda_platform_version.toInteger() minimumPlatformVersion corda_platform_version.toInteger() contract { - name "Negotiation Cordapp" + name "Negotiation Contract" vendor "Corda Open Source" licence "Apache License, Version 2.0" versionId 1 @@ -29,4 +29,4 @@ dependencies { tasks.withType(JavaCompile) { options.compilerArgs << "-parameters" // Required for shell commands. -} \ No newline at end of file +} diff --git a/Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/contracts/ProposalAndTradeContract.java b/Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/contracts/ProposalAndTradeContract.java similarity index 87% rename from Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/contracts/ProposalAndTradeContract.java rename to Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/contracts/ProposalAndTradeContract.java index afa50151..d72648d4 100644 --- a/Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/contracts/ProposalAndTradeContract.java +++ b/Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/contracts/ProposalAndTradeContract.java @@ -1,21 +1,23 @@ -package negotiation.contracts; +package net.corda.samples.negotiation.contracts; import com.google.common.collect.ImmutableSet; -import negotiation.states.ProposalState; -import negotiation.states.TradeState; +import net.corda.samples.negotiation.states.ProposalState; +import net.corda.samples.negotiation.states.TradeState; import net.corda.core.contracts.CommandData; import net.corda.core.contracts.CommandWithParties; import net.corda.core.contracts.Contract; import net.corda.core.transactions.LedgerTransaction; + import static net.corda.core.contracts.ContractsDSL.requireThat; public class ProposalAndTradeContract implements Contract { - public static String ID = "negotiation.contracts.ProposalAndTradeContract"; + public static String ID = "net.corda.samples.negotiation.contracts.ProposalAndTradeContract"; + @Override public void verify(LedgerTransaction tx) throws IllegalArgumentException { final CommandWithParties command = tx.getCommands().get(0); - if( command.getValue() instanceof Commands.Propose) { + if (command.getValue() instanceof Commands.Propose) { requireThat(require -> { require.using("There are no inputs", tx.getInputs().isEmpty()); require.using("Only one output state should be created.", tx.getOutputs().size() == 1); @@ -28,7 +30,7 @@ public void verify(LedgerTransaction tx) throws IllegalArgumentException { require.using("The proposee is a required signer", command.getSigners().contains(output.getProposee().getOwningKey())); return null; }); - }else if(command.getValue() instanceof Commands.Accept){ + } else if (command.getValue() instanceof Commands.Accept) { requireThat(require -> { require.using("There is exactly one input", tx.getInputStates().size() == 1); require.using("The single input is of type ProposalState", tx.inputsOfType(ProposalState.class).size() == 1); @@ -48,8 +50,8 @@ public void verify(LedgerTransaction tx) throws IllegalArgumentException { require.using("The proposee is a required signer", command.getSigners().contains(input.getProposee().getOwningKey())); return null; }); - }else if(command.getValue() instanceof Commands.Modify){ - requireThat(require ->{ + } else if (command.getValue() instanceof Commands.Modify) { + requireThat(require -> { require.using("There is exactly one input", tx.getInputStates().size() == 1); require.using("The single input is of type ProposalState", tx.inputsOfType(ProposalState.class).size() == 1); require.using("There is exactly one output", tx.getOutputs().size() == 1); @@ -69,7 +71,7 @@ public void verify(LedgerTransaction tx) throws IllegalArgumentException { return null; }); - }else{ + } else { throw new IllegalArgumentException("Command of incorrect type"); } @@ -77,8 +79,19 @@ public void verify(LedgerTransaction tx) throws IllegalArgumentException { public interface Commands extends CommandData { - class Propose implements Commands{}; - class Accept implements Commands{}; - class Modify implements Commands{}; + class Propose implements Commands { + } + + ; + + class Accept implements Commands { + } + + ; + + class Modify implements Commands { + } + + ; } } diff --git a/Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/states/ProposalState.java b/Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/states/ProposalState.java similarity index 91% rename from Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/states/ProposalState.java rename to Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/states/ProposalState.java index 51301105..d3c2ed05 100644 --- a/Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/states/ProposalState.java +++ b/Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/states/ProposalState.java @@ -1,14 +1,14 @@ -package negotiation.states; +package net.corda.samples.negotiation.states; import com.google.common.collect.ImmutableList; -import negotiation.contracts.ProposalAndTradeContract; + import net.corda.core.contracts.BelongsToContract; -import net.corda.core.contracts.ContractState; import net.corda.core.contracts.LinearState; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.identity.AbstractParty; import net.corda.core.identity.Party; import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.negotiation.contracts.ProposalAndTradeContract; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -67,8 +67,7 @@ public UniqueIdentifier getLinearId() { @NotNull @Override - public List getParticipants(){ + public List getParticipants() { return ImmutableList.of(proposer, proposee); - } } diff --git a/Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/states/TradeState.java b/Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/states/TradeState.java similarity index 89% rename from Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/states/TradeState.java rename to Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/states/TradeState.java index 8ed5d9dc..597bf922 100644 --- a/Advanced/negotiation-cordapp/contracts/src/main/java/negotiation/states/TradeState.java +++ b/Advanced/negotiation-cordapp/contracts/src/main/java/net/corda/samples/negotiation/states/TradeState.java @@ -1,7 +1,7 @@ -package negotiation.states; +package net.corda.samples.negotiation.states; import com.google.common.collect.ImmutableList; -import negotiation.contracts.ProposalAndTradeContract; +import net.corda.samples.negotiation.contracts.ProposalAndTradeContract; import net.corda.core.contracts.BelongsToContract; import net.corda.core.contracts.LinearState; import net.corda.core.contracts.UniqueIdentifier; @@ -51,6 +51,6 @@ public UniqueIdentifier getLinearId() { @Override public List getParticipants() { - return ImmutableList.of(buyer,seller); + return ImmutableList.of(buyer, seller); } } diff --git a/Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/AcceptanceContractTests.java b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/AcceptanceContractTests.java similarity index 97% rename from Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/AcceptanceContractTests.java rename to Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/AcceptanceContractTests.java index 233d6354..9897dfc3 100644 --- a/Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/AcceptanceContractTests.java +++ b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/AcceptanceContractTests.java @@ -1,8 +1,8 @@ -package negotiation.contracts; +package net.corda.samples.negotiation.contracts; import com.google.common.collect.ImmutableList; -import negotiation.states.ProposalState; -import negotiation.states.TradeState; +import net.corda.samples.negotiation.states.ProposalState; +import net.corda.samples.negotiation.states.TradeState; import net.corda.testing.core.DummyCommandData; import net.corda.testing.core.TestIdentity; import net.corda.testing.node.MockServices; @@ -14,7 +14,7 @@ public class AcceptanceContractTests { - private MockServices ledgerServices = new MockServices(ImmutableList.of("negotiation.contracts")); + private MockServices ledgerServices = new MockServices(ImmutableList.of("net.corda.samples.negotiation.contracts")); private TestIdentity alice = new TestIdentity(new net.corda.core.identity.CordaX500Name("alice","New York", "US")); private TestIdentity bob = new TestIdentity(new net.corda.core.identity.CordaX500Name("bob","Tokyo", "JP")); private TestIdentity charlie = new TestIdentity(new net.corda.core.identity.CordaX500Name("charlie","London", "GB")); diff --git a/Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/ModificationContractTests.java b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/ModificationContractTests.java similarity index 97% rename from Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/ModificationContractTests.java rename to Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/ModificationContractTests.java index ee6b8ef9..089b77e9 100644 --- a/Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/ModificationContractTests.java +++ b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/ModificationContractTests.java @@ -1,8 +1,8 @@ -package negotiation.contracts; +package net.corda.samples.negotiation.contracts; import com.google.common.collect.ImmutableList; -import negotiation.states.ProposalState; -import negotiation.states.TradeState; +import net.corda.samples.negotiation.states.ProposalState; +import net.corda.samples.negotiation.states.TradeState; import net.corda.testing.core.DummyCommandData; import net.corda.testing.core.TestIdentity; import net.corda.testing.node.MockServices; @@ -13,7 +13,8 @@ import static net.corda.testing.node.NodeTestUtils.ledger; public class ModificationContractTests { - private MockServices ledgerServices = new MockServices(ImmutableList.of("negotiation.contracts")); + + private MockServices ledgerServices = new MockServices(ImmutableList.of("net.corda.samples.negotiation.contracts")); private TestIdentity alice = new TestIdentity(new net.corda.core.identity.CordaX500Name("alice","New York", "US")); private TestIdentity bob = new TestIdentity(new net.corda.core.identity.CordaX500Name("bob","Tokyo", "JP")); private TestIdentity charlie = new TestIdentity(new net.corda.core.identity.CordaX500Name("charlie","London", "GB")); diff --git a/Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/ProposalContractTests.java b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/ProposalContractTests.java similarity index 97% rename from Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/ProposalContractTests.java rename to Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/ProposalContractTests.java index 29905a8c..74df7938 100644 --- a/Advanced/negotiation-cordapp/contracts/src/test/java/negotiation/contracts/ProposalContractTests.java +++ b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/contracts/ProposalContractTests.java @@ -1,7 +1,7 @@ -package negotiation.contracts; +package net.corda.samples.negotiation.contracts; import com.google.common.collect.ImmutableList; -import negotiation.states.ProposalState; +import net.corda.samples.negotiation.states.ProposalState; import net.corda.testing.contracts.DummyState; import net.corda.testing.core.DummyCommandData; import net.corda.testing.core.TestIdentity; @@ -15,7 +15,7 @@ public class ProposalContractTests { - private MockServices ledgerServices = new MockServices(ImmutableList.of("negotiation.contracts")); + private MockServices ledgerServices = new MockServices(ImmutableList.of("net.corda.samples.negotiation.contracts")); private TestIdentity alice = new TestIdentity(new net.corda.core.identity.CordaX500Name("alice","New York", "US")); private TestIdentity bob = new TestIdentity(new net.corda.core.identity.CordaX500Name("bob","Tokyo", "JP")); private TestIdentity charlie = new TestIdentity(new net.corda.core.identity.CordaX500Name("charlie","London", "GB")); diff --git a/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/states/ProposalStateTests.java b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/states/ProposalStateTests.java new file mode 100644 index 00000000..c4550d67 --- /dev/null +++ b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/states/ProposalStateTests.java @@ -0,0 +1,57 @@ +package net.corda.samples.negotiation.states; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.testing.core.TestIdentity; +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.*; + + +public class ProposalStateTests { + + private int amount = 10; + private final Party proposer = new TestIdentity(new CordaX500Name("Alice", "", "GB")).getParty(); + private final Party buyer = new TestIdentity(new CordaX500Name("Bob", "", "GB")).getParty(); + private final Party seller = new TestIdentity(new CordaX500Name("Charlie", "", "GB")).getParty(); + private final Party proposee = new TestIdentity(new CordaX500Name("Dan", "", "GB")).getParty(); + + @Test + public void constructorTest() { + + UniqueIdentifier tempId = new UniqueIdentifier(); + ProposalState ps = new ProposalState(amount, buyer, seller, proposer, proposee, tempId); + + assertEquals(ps.getAmount(), amount); + assertEquals(ps.getBuyer(), buyer); + assertEquals(ps.getSeller(), seller); + assertEquals(ps.getProposer(), proposer); + assertEquals(ps.getProposee(), proposee); + assertEquals(ps.getLinearId(), tempId); + + assertNotEquals(ps.getAmount(), 0); + assertNotEquals(ps.getAmount(), -5); + assertNotEquals(ps.getAmount(), null); + } + + @Test + public void linearIdTest() { + ProposalState ps = new ProposalState(amount, buyer, seller, proposer, proposee); + + // ensure ID is generated with shorter constructor stub + assertTrue(ps.getLinearId() instanceof UniqueIdentifier); + } + + @Test + public void participantTest() { + ProposalState ps = new ProposalState(amount, buyer, seller, proposer, proposee); + + // ensure participants are generated correctly + assertTrue(ps.getParticipants().contains(proposer)); + assertTrue(ps.getParticipants().contains(proposee)); + assertFalse(ps.getParticipants().contains(buyer)); + assertFalse(ps.getParticipants().contains(seller)); + } +} diff --git a/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/states/TradeStateTests.java b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/states/TradeStateTests.java new file mode 100644 index 00000000..3828ef69 --- /dev/null +++ b/Advanced/negotiation-cordapp/contracts/src/test/java/net/corda/samples/negotiation/states/TradeStateTests.java @@ -0,0 +1,52 @@ +package net.corda.samples.negotiation.states; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.testing.core.TestIdentity; +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.*; + + +public class TradeStateTests { + + private int amount = 10; + private final Party buyer = new TestIdentity(new CordaX500Name("Bob", "", "GB")).getParty(); + private final Party seller = new TestIdentity(new CordaX500Name("Charlie", "", "GB")).getParty(); + + @Test + public void constructorTest() { + + UniqueIdentifier tempId = new UniqueIdentifier(); + TradeState ts = new TradeState(amount, buyer, seller, tempId); + + assertEquals(ts.getAmount(), amount); + assertEquals(ts.getBuyer(), buyer); + assertEquals(ts.getSeller(), seller); + assertEquals(ts.getLinearId(), tempId); + + assertNotEquals(ts.getAmount(), 0); + assertNotEquals(ts.getAmount(), -5); + assertNotEquals(ts.getAmount(), null); + } + + @Test + public void linearIdTest() { + TradeState ts = new TradeState(amount, buyer, seller); + + // ensure ID is generated with shorter constructor stub + assertTrue(ts.getLinearId() instanceof UniqueIdentifier); + } + + @Test + public void participantTest() { + TradeState ts = new TradeState(amount, buyer, seller); + + // ensure participants are generated correctly + assertTrue(ts.getParticipants().contains(buyer)); + assertTrue(ts.getParticipants().contains(seller)); + } + +} diff --git a/Advanced/negotiation-cordapp/gradle.properties b/Advanced/negotiation-cordapp/gradle.properties index 80ba07ea..03a6057a 100644 --- a/Advanced/negotiation-cordapp/gradle.properties +++ b/Advanced/negotiation-cordapp/gradle.properties @@ -1,3 +1,3 @@ -name=Test -group=com.negotiation +name=Negotiation Cordapp +group=net.corda.samples.negotiation version=0.2 diff --git a/Advanced/negotiation-cordapp/gradle/wrapper/gradle-wrapper.properties b/Advanced/negotiation-cordapp/gradle/wrapper/gradle-wrapper.properties index d757f3d3..674bdda0 100644 --- a/Advanced/negotiation-cordapp/gradle/wrapper/gradle-wrapper.properties +++ b/Advanced/negotiation-cordapp/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/Advanced/negotiation-cordapp/repositories.gradle b/Advanced/negotiation-cordapp/repositories.gradle index 2874c2ab..9797c0ea 100644 --- a/Advanced/negotiation-cordapp/repositories.gradle +++ b/Advanced/negotiation-cordapp/repositories.gradle @@ -1,8 +1,7 @@ repositories { mavenLocal() mavenCentral() - jcenter() maven { url 'https://jitpack.io' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Advanced/negotiation-cordapp/workflows/build.gradle b/Advanced/negotiation-cordapp/workflows/build.gradle index aa2fcefb..c8bde906 100644 --- a/Advanced/negotiation-cordapp/workflows/build.gradle +++ b/Advanced/negotiation-cordapp/workflows/build.gradle @@ -9,19 +9,43 @@ cordapp { targetPlatformVersion corda_platform_version.toInteger() minimumPlatformVersion corda_platform_version.toInteger() workflow { - name "Negotiation CorDapp" + name "Negotiation flow" vendor "Corda Open Source" licence "Apache License, Version 2.0" versionId 1 } } - sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } resources { - srcDir "../config/test" + srcDir rootProject.file("config/test") } } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime } dependencies { @@ -29,58 +53,15 @@ dependencies { // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" - cordaCompile "$corda_release_group:corda-jackson:$corda_release_version" - cordaCompile "$corda_release_group:corda-rpc:$corda_release_version" - cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" - - testCompile "$corda_release_group:corda-node-driver:$corda_release_version" - // Needed by deployNodes task. cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" // CorDapp dependencies. cordapp project(":contracts") } -jar { - // CorDapps do not configure a Node's logging. - exclude '**/log4j2*.xml' -} - -tasks.withType(JavaCompile) { - options.compilerArgs << "-parameters" // Required for shell commands. -} - -task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - nodeDefaults { - - cordapp project(':contracts') - } - node { - name "O=Notary,L=London,C=GB" - notary = [validating : false] - p2pPort 10002 - rpcSettings { - address("localhost:10003") - adminAddress("localhost:10043") - } - } - node { - name "O=PartyA,L=London,C=GB" - p2pPort 10005 - rpcSettings { - address("localhost:10006") - adminAddress("localhost:10046") - } - rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] - } - node { - name "O=PartyB,L=New York,C=US" - p2pPort 10008 - rpcSettings { - address("localhost:10009") - adminAddress("localhost:10049") - } - rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] - } -} +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Advanced/negotiation-cordapp/workflows/src/integrationTest/java/net/corda/samples/negotiation/flows/DriverBasedTest.java b/Advanced/negotiation-cordapp/workflows/src/integrationTest/java/net/corda/samples/negotiation/flows/DriverBasedTest.java new file mode 100644 index 00000000..a68ffa86 --- /dev/null +++ b/Advanced/negotiation-cordapp/workflows/src/integrationTest/java/net/corda/samples/negotiation/flows/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.negotiation.flows; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/AcceptanceFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/AcceptanceFlow.java similarity index 95% rename from Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/AcceptanceFlow.java rename to Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/AcceptanceFlow.java index 28f83567..62426e2c 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/AcceptanceFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/AcceptanceFlow.java @@ -1,11 +1,11 @@ -package negotiation.flows; +package net.corda.samples.negotiation.flows; import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; -import negotiation.contracts.ProposalAndTradeContract; -import negotiation.states.ProposalState; -import negotiation.states.TradeState; +import net.corda.samples.negotiation.contracts.ProposalAndTradeContract; +import net.corda.samples.negotiation.states.ProposalState; +import net.corda.samples.negotiation.states.TradeState; import net.corda.core.contracts.Command; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.UniqueIdentifier; diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/ModificationFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java similarity index 96% rename from Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/ModificationFlow.java rename to Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java index 3ad1dd40..2d6e8d39 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/ModificationFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java @@ -1,9 +1,9 @@ -package negotiation.flows; +package net.corda.samples.negotiation.flows; import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; -import negotiation.contracts.ProposalAndTradeContract; -import negotiation.states.ProposalState; +import net.corda.samples.negotiation.contracts.ProposalAndTradeContract; +import net.corda.samples.negotiation.states.ProposalState; import net.corda.core.contracts.Command; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.UniqueIdentifier; @@ -18,7 +18,6 @@ import net.corda.core.utilities.ProgressTracker; import org.jetbrains.annotations.NotNull; -import javax.annotation.Signed; import java.security.PublicKey; import java.security.SignatureException; import java.util.List; diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/ProposalFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java similarity index 86% rename from Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/ProposalFlow.java rename to Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java index 542f6833..45def7ee 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/negotiation/flows/ProposalFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java @@ -1,9 +1,9 @@ -package negotiation.flows; +package net.corda.samples.negotiation.flows; import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; -import negotiation.contracts.ProposalAndTradeContract; -import negotiation.states.ProposalState; +import net.corda.samples.negotiation.contracts.ProposalAndTradeContract; +import net.corda.samples.negotiation.states.ProposalState; import net.corda.core.contracts.Command; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.crypto.SecureHash; @@ -15,6 +15,7 @@ import java.security.PublicKey; import java.util.List; +import net.corda.core.identity.CordaX500Name; public class ProposalFlow { @InitiatingFlow @@ -49,7 +50,11 @@ public UniqueIdentifier call() throws FlowException { Command command = new Command(commandType, requiredSigners); //Building the transaction - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + TransactionBuilder txBuilder = new TransactionBuilder(notary) .addOutputState(output, ProposalAndTradeContract.ID) .addCommand(command); diff --git a/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/FlowTestsBase.java b/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/FlowTestsBase.java deleted file mode 100644 index 2753a862..00000000 --- a/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/FlowTestsBase.java +++ /dev/null @@ -1,71 +0,0 @@ -package negotiation.flows; - -import com.google.common.collect.ImmutableList; -import net.corda.core.contracts.UniqueIdentifier; -import net.corda.core.identity.Party; -import net.corda.testing.node.MockNetwork; -import net.corda.testing.node.MockNetworkParameters; -import net.corda.testing.node.StartedMockNode; -import net.corda.testing.node.TestCordapp; -import org.junit.After; -import org.junit.Before; - -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -abstract class FlowTestsBase { - protected MockNetwork network; - protected StartedMockNode a; - protected StartedMockNode b; - - @Before - public void setup(){ - network = new MockNetwork(new MockNetworkParameters(ImmutableList.of( - TestCordapp.findCordapp("negotiation.flows"), - TestCordapp.findCordapp("negotiation.contracts") - ) - )); - a = network.createPartyNode(null); - b = network.createPartyNode(null); - - List responseflows = ImmutableList.of(ProposalFlow.Responder.class, AcceptanceFlow.Responder.class, ModificationFlow.Responder.class); - - ImmutableList.of(a,b).forEach(node -> { - for (Class flow:responseflows - ) { - node.registerInitiatedFlow(flow); - - } - }); - network.runNetwork(); - - } - - @After - public void tearDown(){ - network.stopNodes(); - } - - public UniqueIdentifier nodeACreatesProposal(Boolean isBuyer, int amount, Party counterparty) throws ExecutionException, InterruptedException { - ProposalFlow.Initiator flow = new ProposalFlow.Initiator(isBuyer, amount, counterparty); - Future future = a.startFlow(flow); - network.runNetwork(); - return (UniqueIdentifier) future.get(); - - } - - public void nodeBAcceptsProposal(UniqueIdentifier proposalId) throws ExecutionException, InterruptedException { - AcceptanceFlow.Initiator flow = new AcceptanceFlow.Initiator(proposalId); - Future future = b.startFlow(flow); - network.runNetwork(); - future.get(); - } - - public void nodeBModifiesProposal(UniqueIdentifier proposalId, int newAmount) throws ExecutionException, InterruptedException { - ModificationFlow.Initiator flow = new ModificationFlow.Initiator(proposalId, newAmount); - Future future = b.startFlow(flow); - network.runNetwork(); - future.get(); - } -} diff --git a/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/AcceptanceFlowTests.java b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/AcceptanceFlowTests.java similarity index 89% rename from Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/AcceptanceFlowTests.java rename to Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/AcceptanceFlowTests.java index cbb4cdf9..6bf73176 100644 --- a/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/AcceptanceFlowTests.java +++ b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/AcceptanceFlowTests.java @@ -1,8 +1,8 @@ -package negotiation.flows; +package net.corda.samples.negotiation.flows; import com.google.common.collect.ImmutableList; -import negotiation.states.ProposalState; -import negotiation.states.TradeState; +import net.corda.samples.negotiation.states.ProposalState; +import net.corda.samples.negotiation.states.TradeState; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.identity.Party; @@ -13,7 +13,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -public class AcceptanceFlowTests extends FlowTestsBase{ +public class AcceptanceFlowTests extends FlowTestBase { @Test public void acceptanceFlowConsumesTheProposalsInBothNodesVaultsAndReplacesWithEquivAccTradesWhenInitiatorIsBuyer() throws ExecutionException, InterruptedException { @@ -43,7 +43,7 @@ private void testAcceptance(Boolean isBuyer) throws ExecutionException, Interrup Party counterparty = b.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); UniqueIdentifier proposalId = nodeACreatesProposal(isBuyer, amount, counterparty); nodeBAcceptsProposal(proposalId); - ImmutableList.of(a,b).forEach(node -> { + ImmutableList.of(a, b).forEach(node -> { node.transaction(() -> { List> proposals = node.getServices().getVaultService().queryBy(ProposalState.class).getStates(); Assert.assertEquals(0, proposals.size()); @@ -56,10 +56,10 @@ private void testAcceptance(Boolean isBuyer) throws ExecutionException, Interrup Party buyer; Party seller; - if(isBuyer){ + if (isBuyer) { buyer = a.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); seller = b.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); - }else{ + } else { seller = a.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); buyer = b.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); } diff --git a/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/FlowTestBase.java b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/FlowTestBase.java new file mode 100644 index 00000000..d6e2fd85 --- /dev/null +++ b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/FlowTestBase.java @@ -0,0 +1,71 @@ +package net.corda.samples.negotiation.flows; + +import com.google.common.collect.ImmutableList; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.testing.driver.VerifierType; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +abstract class FlowTestBase { + protected MockNetwork network; + protected StartedMockNode a; + protected StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters( + ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.negotiation.flows"), + TestCordapp.findCordapp("net.corda.samples.negotiation.contracts")) + ).withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + + List responseflows = ImmutableList.of(ProposalFlow.Responder.class, AcceptanceFlow.Responder.class, ModificationFlow.Responder.class); + + ImmutableList.of(a,b).forEach(node -> { + for (Class flow:responseflows + ) { + node.registerInitiatedFlow(flow); + + } + }); + network.runNetwork(); + + } + + @After + public void tearDown(){ + network.stopNodes(); + } + + public UniqueIdentifier nodeACreatesProposal(Boolean isBuyer, int amount, Party counterparty) throws ExecutionException, InterruptedException { + ProposalFlow.Initiator flow = new ProposalFlow.Initiator(isBuyer, amount, counterparty); + Future future = a.startFlow(flow); + network.runNetwork(); + return (UniqueIdentifier) future.get(); + + } + + public void nodeBAcceptsProposal(UniqueIdentifier proposalId) throws ExecutionException, InterruptedException { + AcceptanceFlow.Initiator flow = new AcceptanceFlow.Initiator(proposalId); + Future future = b.startFlow(flow); + network.runNetwork(); + future.get(); + } + + public void nodeBModifiesProposal(UniqueIdentifier proposalId, int newAmount) throws ExecutionException, InterruptedException { + ModificationFlow.Initiator flow = new ModificationFlow.Initiator(proposalId, newAmount); + Future future = b.startFlow(flow); + network.runNetwork(); + future.get(); + } +} diff --git a/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/ModifyFlowTests.java b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/ModifyFlowTests.java similarity index 95% rename from Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/ModifyFlowTests.java rename to Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/ModifyFlowTests.java index d14a5417..424b6b8c 100644 --- a/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/ModifyFlowTests.java +++ b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/ModifyFlowTests.java @@ -1,7 +1,7 @@ -package negotiation.flows; +package net.corda.samples.negotiation.flows; import com.google.common.collect.ImmutableList; -import negotiation.states.ProposalState; +import net.corda.samples.negotiation.states.ProposalState; import net.corda.core.contracts.StateAndRef; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.identity.Party; @@ -12,7 +12,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -public class ModifyFlowTests extends FlowTestsBase { +public class ModifyFlowTests extends FlowTestBase { @Test public void modificationFlowConsumesTheProposalsInBothNodesVaultsAndReplacesWithEquivWithNEwAmountsWhenInitiatorISBuyer() throws ExecutionException, InterruptedException { diff --git a/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/ProposalFlowTests.java b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/ProposalFlowTests.java similarity index 88% rename from Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/ProposalFlowTests.java rename to Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/ProposalFlowTests.java index c810a557..04a4bdc5 100644 --- a/Advanced/negotiation-cordapp/workflows/src/test/java/negotiation/flows/ProposalFlowTests.java +++ b/Advanced/negotiation-cordapp/workflows/src/test/java/net/corda/samples/negotiation/flows/ProposalFlowTests.java @@ -1,7 +1,7 @@ -package negotiation.flows; +package net.corda.samples.negotiation.flows; import com.google.common.collect.ImmutableList; -import negotiation.states.ProposalState; +import net.corda.samples.negotiation.states.ProposalState; import net.corda.core.contracts.StateAndRef; import net.corda.core.identity.Party; import org.junit.Assert; @@ -10,7 +10,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; -public class ProposalFlowTests extends FlowTestsBase { +public class ProposalFlowTests extends FlowTestBase { @Test public void proposalFlowCreatesTheCorrectProposalsInBothNodesVaultsWhenInitiatorIsBuyer() throws ExecutionException, InterruptedException { @@ -22,12 +22,11 @@ public void proposalFlowCreatesTheCorrectProposalsInBothNodesVaultsWhenInitiator testProposal(false); } - private void testProposal(Boolean isBuyer) throws ExecutionException, InterruptedException { int amount = 1; Party counterparty = b.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); nodeACreatesProposal(isBuyer, amount, counterparty); - ImmutableList.of(a,b).forEach(node -> { + ImmutableList.of(a, b).forEach(node -> { node.transaction(() -> { List> proposals = node.getServices().getVaultService().queryBy(ProposalState.class).getStates(); Assert.assertEquals(1, proposals.size()); @@ -38,10 +37,10 @@ private void testProposal(Boolean isBuyer) throws ExecutionException, Interrupte Party buyer; Party seller; - if(isBuyer){ + if (isBuyer) { buyer = a.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); seller = b.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); - }else{ + } else { seller = a.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); buyer = b.getInfo().getLegalIdentitiesAndCerts().get(0).getParty(); } diff --git a/Advanced/obligation-cordapp/README.md b/Advanced/obligation-cordapp/README.md index 44a367d3..952c7f9a 100644 --- a/Advanced/obligation-cordapp/README.md +++ b/Advanced/obligation-cordapp/README.md @@ -1,26 +1,20 @@ -# obligation-cordap +# Obligation Cordap -This Cordapp is the complete implementation of our signature IOU (I-owe-you) demonstration. +This CorDapp is the complete implementation of our signature IOU (I-owe-you) demonstration. ## Concepts -An IOU is someone who has cash that is paying it back to someone they owe it to. - -You have to have the original concept of the debt itself, (the IOU), and the cash. - -Then the ability to exchange assets like cash or assets, and then the ability to settle up. - -Given this is intended to implement an IOU, our cordapp consists of three flows `issue`, `transfer` and `settle` flows. +An IOU is someone who has cash that is paying it back to someone they owe it to. You have to have the original concept of the debt itself, (the IOU), and the cash. Then the ability to exchange assets like cash or assets, and then the ability to settle up. Given this is intended to implement an IOU, our cordapp consists of three flows `issue`, `transfer` and `settle` flows. ### Flows -The first flows are the ones that issue the original cash and assets. You can find that the cash flow [here](./workflows/src/main/java/net/corda/samples/flows/SelfIssueCashFlow.java#L24-L32) and the IOU issurance in [IOUIssueFlow.java](./workflows/src/main/java/net/corda/samples/flows/IOUIssueFlow.java#L40-L80). +The first flows are the ones that issue the original cash and assets. You can find that the cash flow at `SelfIssueCashFlow.java` and the IOU issuance in `IOUIssueFlow.java`. -The next flow is the one that transfers ownership of that asset over to another party. That can be found in [IOUTransferFlow.java](./workflows/src/main/java/net/corda/samples/flows/IOUTransferFlow.java#L132-L159). +The next flow is the one that transfers ownership of that asset over to another party. That can be found in `IOUTransferFlow.java`. -Finally, once we have the ability to transfer assets, we just need to settle up. That functiionality can be found [here in IOUSettleFlow.java](./workflows/src/main/java/net/corda/samples/flows/IOUSettleFlow.java#L54-L116) +Finally, once we have the ability to transfer assets, we just need to settle up. That functionality can be found here in `IOUSettleFlow.java` @@ -40,12 +34,25 @@ Then type: (to run the nodes) ./build/nodes/runnodes ``` +### Starting the webserver +Once the nodes are up, we will start the webservers next. This app consists of three nodes and one notary, so we will be starting 3 webservers separately. First, lets start PartyA's webserver. Open a new tab of the terminal (make sure you are still in the project directory) and run: +``` +./gradlew runPartyAServer +``` +repeat the same for PartyB and PartyC, run each of the commands in a new tab: +``` +./gradlew runPartyBServer +``` +and +``` +./gradlew runPartyCServer +``` + ### Interacting with the CorDapp -Once all the three nodes have started up (look for `Webserver started up in XXX sec` in the terminal or IntelliJ ), you can interact with the app via a web browser. +Once all the three servers have started up (look for `Webserver started up in XXX sec` in the terminal), you can interact with the app via a web browser. * From a Node Driver configuration, look for `Starting webserver on address localhost:100XX` for the addresses. * From the terminal: Node A: `localhost:10009`, Node B: `localhost:10012`, Node C: `localhost:10015`. To access the front-end gui for each node, navigate to `localhost:XXXX/web/iou/` - diff --git a/Advanced/obligation-cordapp/build.gradle b/Advanced/obligation-cordapp/build.gradle index 833ebe42..15bd2c09 100644 --- a/Advanced/obligation-cordapp/build.gradle +++ b/Advanced/obligation-cordapp/build.gradle @@ -22,8 +22,8 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } + + maven { url 'https://download.corda.net/maven/corda-releases' } } dependencies { @@ -41,9 +41,8 @@ allprojects { repositories { mavenLocal() - jcenter() mavenCentral() - maven { url 'https://software.r3.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://jitpack.io' } } @@ -60,8 +59,6 @@ allprojects { } - - apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'net.corda.plugins.cordformation' apply plugin: 'net.corda.plugins.quasar-utils' @@ -75,15 +72,18 @@ sourceSets { } dependencies { + // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" cordaCompile "$corda_release_group:corda-finance-contracts:$corda_release_version" cordaCompile "$corda_release_group:corda-finance-workflows:$corda_release_version" cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" cordaRuntime "$corda_release_group:corda:$corda_release_version" + // CorDapp dependencies. cordapp project(":workflows") cordapp project(":contracts") + cordapp "$corda_release_group:corda-finance-contracts:$corda_release_version" cordapp "$corda_release_group:corda-finance-workflows:$corda_release_version" cordapp "$corda_release_group:corda-confidential-identities:$corda_release_version" @@ -91,11 +91,13 @@ dependencies { cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaDriver "net.corda:corda-shell:4.10" + } cordapp { info { - name "CorDapp Template" + name "Obligation CorDapp" vendor "Corda Open Source" targetPlatformVersion corda_platform_version minimumPlatformVersion corda_platform_version @@ -107,8 +109,11 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { projectCordapp { deploy = false } - cordapp project(':contracts') - cordapp project(':workflows') + + cordapp project(":workflows") + cordapp project(":contracts") + + runSchemaMigration = true cordapp("$corda_release_group:corda-finance-contracts:$corda_release_version") cordapp("$corda_release_group:corda-finance-workflows:$corda_release_version") cordapp("$corda_release_group:corda-confidential-identities:$corda_release_version") diff --git a/Advanced/obligation-cordapp/clients/build.gradle b/Advanced/obligation-cordapp/clients/build.gradle index 24913c79..6bf3c08f 100644 --- a/Advanced/obligation-cordapp/clients/build.gradle +++ b/Advanced/obligation-cordapp/clients/build.gradle @@ -1,6 +1,6 @@ repositories { mavenLocal() - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } @@ -26,6 +26,12 @@ dependencyManagement { } } +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + dependencies { // CorDapp dependencies. compile project(":contracts") @@ -53,18 +59,18 @@ dependencies { task runPartyAServer(type: JavaExec, dependsOn: jar) { classpath = sourceSets.main.runtimeClasspath - main = 'net.corda.training.server.Server' + main = 'net.corda.samples.obligation.server.Server' args '--server.port=10009', '--config.rpc.host=localhost', '--config.rpc.port=10008', '--config.rpc.username=user1', '--config.rpc.password=password' } task runPartyBServer(type: JavaExec, dependsOn: jar) { classpath = sourceSets.main.runtimeClasspath - main = 'net.corda.training.server.Server' + main = 'net.corda.samples.obligation.server.Server' args '--server.port=10012', '--config.rpc.host=localhost', '--config.rpc.port=10011', '--config.rpc.username=user1', '--config.rpc.password=password' } task runPartyCServer(type: JavaExec, dependsOn: jar) { classpath = sourceSets.main.runtimeClasspath - main = 'net.corda.training.server.Server' + main = 'net.corda.samples.obligation.server.Server' args '--server.port=10015', '--config.rpc.host=localhost', '--config.rpc.port=10014', '--config.rpc.username=user1', '--config.rpc.password=password' } diff --git a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/CONSTANTS.java b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/CONSTANTS.java new file mode 100644 index 00000000..8c838881 --- /dev/null +++ b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/CONSTANTS.java @@ -0,0 +1,8 @@ +package net.corda.samples.obligation.server; + +public interface CONSTANTS { + String CORDA_USER_NAME = "config.rpc.username"; + String CORDA_USER_PASSWORD = "config.rpc.password"; + String CORDA_NODE_HOST = "config.rpc.host"; + String CORDA_RPC_PORT = "config.rpc.port"; +} diff --git a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/MainController.java b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/MainController.java new file mode 100644 index 00000000..373213a4 --- /dev/null +++ b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/MainController.java @@ -0,0 +1,232 @@ +package net.corda.samples.obligation.server; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.corda.client.jackson.JacksonSupport; +import net.corda.core.contracts.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; +import net.corda.core.transactions.SignedTransaction; +import net.corda.finance.contracts.asset.Cash; +import net.corda.samples.obligation.flows.IOUIssueFlow; +import net.corda.samples.obligation.flows.IOUSettleFlow; +import net.corda.samples.obligation.flows.IOUTransferFlow; +import net.corda.samples.obligation.flows.SelfIssueCashFlow; +import net.corda.samples.obligation.states.IOUState; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static net.corda.finance.workflows.GetBalances.getCashBalances; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; + +/** + * Define your API endpoints here. + */ +@RestController +@RequestMapping("/api/iou") // The paths for HTTP requests are relative to this base path. +public class MainController { + private static final Logger logger = LoggerFactory.getLogger(RestController.class); + private final CordaRPCOps proxy; + private final CordaX500Name me; + + public MainController(NodeRPCConnection rpc) { + this.proxy = rpc.getProxy(); + this.me = proxy.nodeInfo().getLegalIdentities().get(0).getName(); + + } + + /** Helpers for filtering the network map cache. */ + public String toDisplayString(X500Name name){ + return BCStyle.INSTANCE.toString(name); + } + + private boolean isNotary(NodeInfo nodeInfo) { + return !proxy.notaryIdentities() + .stream().filter(el -> nodeInfo.isLegalIdentity(el)) + .collect(Collectors.toList()).isEmpty(); + } + + private boolean isMe(NodeInfo nodeInfo){ + return nodeInfo.getLegalIdentities().get(0).getName().equals(me); + } + + private boolean isNetworkMap(NodeInfo nodeInfo){ + return nodeInfo.getLegalIdentities().get(0).getName().getOrganisation().equals("Network Map Service"); + } + + @Configuration + class Plugin { + @Bean + public ObjectMapper registerModule() { + return JacksonSupport.createNonRpcMapper(); + } + } + + @GetMapping(value = "/status", produces = TEXT_PLAIN_VALUE) + private String status() { + return "200"; + } + + @GetMapping(value = "/servertime", produces = TEXT_PLAIN_VALUE) + private String serverTime() { + return (LocalDateTime.ofInstant(proxy.currentNodeTime(), ZoneId.of("UTC"))).toString(); + } + + @GetMapping(value = "/addresses", produces = TEXT_PLAIN_VALUE) + private String addresses() { + return proxy.nodeInfo().getAddresses().toString(); + } + + @GetMapping(value = "/identities", produces = TEXT_PLAIN_VALUE) + private String identities() { + return proxy.nodeInfo().getLegalIdentities().toString(); + } + + @GetMapping(value = "/platformversion", produces = TEXT_PLAIN_VALUE) + private String platformVersion() { + return Integer.toString(proxy.nodeInfo().getPlatformVersion()); + } + + @GetMapping(value = "/peers", produces = APPLICATION_JSON_VALUE) + public HashMap> getPeers() { + HashMap> myMap = new HashMap<>(); + + // Find all nodes that are not notaries, ourself, or the network map. + Stream filteredNodes = proxy.networkMapSnapshot().stream() + .filter(el -> !isNotary(el) && !isMe(el) && !isNetworkMap(el)); + // Get their names as strings + List nodeNames = filteredNodes.map(el -> el.getLegalIdentities().get(0).getName().toString()) + .collect(Collectors.toList()); + + myMap.put("peers", nodeNames); + return myMap; + } + + @GetMapping(value = "/notaries", produces = TEXT_PLAIN_VALUE) + private String notaries() { + return proxy.notaryIdentities().toString(); + } + + @GetMapping(value = "/flows", produces = TEXT_PLAIN_VALUE) + private String flows() { + return proxy.registeredFlows().toString(); + } + + @GetMapping(value = "/states", produces = TEXT_PLAIN_VALUE) + private String states() { + return proxy.vaultQuery(ContractState.class).getStates().toString(); + } + + @GetMapping(value = "/me",produces = APPLICATION_JSON_VALUE) + private HashMap whoami(){ + HashMap myMap = new HashMap<>(); + myMap.put("me", me.toString()); + return myMap; + } + @GetMapping(value = "/ious",produces = APPLICATION_JSON_VALUE) + public List> getIOUs() { + // Filter by states type: IOU. + return proxy.vaultQuery(IOUState.class).getStates(); + } + @GetMapping(value = "/cash",produces = APPLICATION_JSON_VALUE) + public List> getCash() { + // Filter by states type: Cash. + return proxy.vaultQuery(Cash.State.class).getStates(); + } + + @GetMapping(value = "/cash-balances",produces = APPLICATION_JSON_VALUE) + public Map> cashBalances(){ + return getCashBalances(proxy); + } + + @PutMapping(value = "/issue-iou" , produces = TEXT_PLAIN_VALUE ) + public ResponseEntity issueIOU(@RequestParam(value = "amount") int amount, + @RequestParam(value = "currency") String currency, + @RequestParam(value = "party") String party) throws IllegalArgumentException { + // Get party objects for myself and the counterparty. + Party me = proxy.nodeInfo().getLegalIdentities().get(0); + Party lender = Optional.ofNullable(proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(party))).orElseThrow(() -> new IllegalArgumentException("Unknown party name.")); + // Create a new IOU states using the parameters given. + try { + IOUState state = new IOUState(new Amount<>((long) amount * 100, Currency.getInstance(currency)), lender, me); + // Start the IOUIssueFlow. We block and waits for the flows to return. + SignedTransaction result = proxy.startTrackedFlowDynamic(IOUIssueFlow.InitiatorFlow.class, state).getReturnValue().get(); + // Return the response. + return ResponseEntity + .status(HttpStatus.CREATED) + .body("Transaction id "+ result.getId() +" committed to ledger.\n " + result.getTx().getOutput(0)); + // For the purposes of this demo app, we do not differentiate by exception type. + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } + @GetMapping(value = "transfer-iou" , produces = TEXT_PLAIN_VALUE ) + public ResponseEntity transferIOU(@RequestParam(value = "id") String id, + @RequestParam(value = "party") String party) { + UniqueIdentifier linearId = new UniqueIdentifier(null,UUID.fromString(id)); + Party newLender = proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(party)); + try { + proxy.startTrackedFlowDynamic(IOUTransferFlow.InitiatorFlow.class, linearId, newLender).getReturnValue().get(); + return ResponseEntity.status(HttpStatus.CREATED).body("IOU "+linearId.toString()+" transferred to "+party+"."); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + } + + /** + * Settles an IOU. Requires cash in the right currency to be able to settle. + * Example request: + * curl -X GET 'http://localhost:10007/api/iou/settle-iou?id=705dc5c5-44da-4006-a55b-e29f78955089&amount=98¤cy=USD' + */ + @GetMapping(value = "settle-iou" , produces = TEXT_PLAIN_VALUE ) + public ResponseEntity settleIOU(@RequestParam(value = "id") String id, + @RequestParam(value = "amount") int amount, + @RequestParam(value = "currency") String currency) { + + UniqueIdentifier linearId = new UniqueIdentifier(null, UUID.fromString(id)); + try { + proxy.startTrackedFlowDynamic(IOUSettleFlow.InitiatorFlow.class, linearId, + new Amount<>((long) amount * 100, Currency.getInstance(currency))).getReturnValue().get(); + return ResponseEntity.status(HttpStatus.CREATED).body(""+ amount+ currency +" paid off on IOU id "+linearId.toString()+"."); + + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + } + + /** + * Helper end-point to issue some cash to ourselves. + * Example request: + * curl -X GET 'http://localhost:10009/api/iou/self-issue-cash?amount=100¤cy=USD' + */ + @GetMapping(value = "self-issue-cash" , produces = TEXT_PLAIN_VALUE ) + public ResponseEntity selfIssueCash(@RequestParam(value = "amount") int amount, + @RequestParam(value = "currency") String currency) { + + try { + Cash.State cashState = proxy.startTrackedFlowDynamic(SelfIssueCashFlow.class, + new Amount<>((long) amount * 100, Currency.getInstance(currency))).getReturnValue().get(); + return ResponseEntity.status(HttpStatus.CREATED).body(cashState.toString()); + + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + } +} diff --git a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/NodeRPCConnection.java b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/NodeRPCConnection.java new file mode 100644 index 00000000..30ce6ec9 --- /dev/null +++ b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/NodeRPCConnection.java @@ -0,0 +1,63 @@ +package net.corda.samples.obligation.server; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/** + * Wraps a node RPC proxy. + */ +@Component +public class NodeRPCConnection implements AutoCloseable { + private final String host; + private final String username; + private final String password; + private final int rpcPort; + + + private CordaRPCConnection rpcConnection; + private CordaRPCOps proxy; // The RPC proxy + + /** + * The RPC proxy is configured based on the properties in `application.properties`. + * @param host The host of the node we are connecting to. + * @param rpcPort The RPC port of the node we are connecting to. + * @param username The username for logging into the RPC client. + * @param password The password for logging into the RPC client. + */ + public NodeRPCConnection( + @Value("${" + CONSTANTS.CORDA_NODE_HOST + "}") String host, + @Value("${" + CONSTANTS.CORDA_USER_NAME + "}") String username, + @Value("${" + CONSTANTS.CORDA_USER_PASSWORD + "}") String password, + @Value("${" + CONSTANTS.CORDA_RPC_PORT + "}") int rpcPort + ) { + this.host = host; + this.username = username; + this.password = password; + this.rpcPort = rpcPort; + } + + @PostConstruct + public void initialiseNodeRPCConnection() { + NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); + CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); + this.rpcConnection = rpcClient.start(username, password); + this.proxy = rpcConnection.getProxy(); + } + + public CordaRPCOps getProxy() { + return proxy; + } + + @PreDestroy + @Override + public void close() throws Exception { + rpcConnection.notifyServerAndClose(); + } +} diff --git a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/Server.java b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/Server.java new file mode 100644 index 00000000..8630e9d4 --- /dev/null +++ b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/obligation/server/Server.java @@ -0,0 +1,22 @@ +package net.corda.samples.obligation.server; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Our Spring Boot application. + */ +@SpringBootApplication +public class Server { + /** + * Starts our Spring Boot application. + */ + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Server.class); + app.setBannerMode(Banner.Mode.OFF); + app.setWebApplicationType(WebApplicationType.SERVLET); + app.run(args); + } +} diff --git a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/CONSTANTS.java b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/CONSTANTS.java deleted file mode 100644 index 0dd2b231..00000000 --- a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/CONSTANTS.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.corda.samples.server; - -public interface CONSTANTS { - String CORDA_USER_NAME = "config.rpc.username"; - String CORDA_USER_PASSWORD = "config.rpc.password"; - String CORDA_NODE_HOST = "config.rpc.host"; - String CORDA_RPC_PORT = "config.rpc.port"; -} diff --git a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/MainController.java b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/MainController.java deleted file mode 100644 index 795d3958..00000000 --- a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/MainController.java +++ /dev/null @@ -1,232 +0,0 @@ -package net.corda.samples.server; - -import com.fasterxml.jackson.databind.ObjectMapper; -import net.corda.client.jackson.JacksonSupport; -import net.corda.core.contracts.*; -import net.corda.core.identity.CordaX500Name; -import net.corda.core.identity.Party; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.node.NodeInfo; -import net.corda.core.transactions.SignedTransaction; -import net.corda.finance.contracts.asset.Cash; -import net.corda.samples.flows.IOUIssueFlow; -import net.corda.samples.flows.IOUSettleFlow; -import net.corda.samples.flows.IOUTransferFlow; -import net.corda.samples.flows.SelfIssueCashFlow; -import net.corda.samples.states.IOUState; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.*; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x500.style.BCStyle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static net.corda.finance.workflows.GetBalances.getCashBalances; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; - -/** - * Define your API endpoints here. - */ -@RestController -@RequestMapping("/api/iou") // The paths for HTTP requests are relative to this base path. -public class MainController { - private static final Logger logger = LoggerFactory.getLogger(RestController.class); - private final CordaRPCOps proxy; - private final CordaX500Name me; - - public MainController(NodeRPCConnection rpc) { - this.proxy = rpc.getProxy(); - this.me = proxy.nodeInfo().getLegalIdentities().get(0).getName(); - - } - - /** Helpers for filtering the network map cache. */ - public String toDisplayString(X500Name name){ - return BCStyle.INSTANCE.toString(name); - } - - private boolean isNotary(NodeInfo nodeInfo) { - return !proxy.notaryIdentities() - .stream().filter(el -> nodeInfo.isLegalIdentity(el)) - .collect(Collectors.toList()).isEmpty(); - } - - private boolean isMe(NodeInfo nodeInfo){ - return nodeInfo.getLegalIdentities().get(0).getName().equals(me); - } - - private boolean isNetworkMap(NodeInfo nodeInfo){ - return nodeInfo.getLegalIdentities().get(0).getName().getOrganisation().equals("Network Map Service"); - } - - @Configuration - class Plugin { - @Bean - public ObjectMapper registerModule() { - return JacksonSupport.createNonRpcMapper(); - } - } - - @GetMapping(value = "/status", produces = TEXT_PLAIN_VALUE) - private String status() { - return "200"; - } - - @GetMapping(value = "/servertime", produces = TEXT_PLAIN_VALUE) - private String serverTime() { - return (LocalDateTime.ofInstant(proxy.currentNodeTime(), ZoneId.of("UTC"))).toString(); - } - - @GetMapping(value = "/addresses", produces = TEXT_PLAIN_VALUE) - private String addresses() { - return proxy.nodeInfo().getAddresses().toString(); - } - - @GetMapping(value = "/identities", produces = TEXT_PLAIN_VALUE) - private String identities() { - return proxy.nodeInfo().getLegalIdentities().toString(); - } - - @GetMapping(value = "/platformversion", produces = TEXT_PLAIN_VALUE) - private String platformVersion() { - return Integer.toString(proxy.nodeInfo().getPlatformVersion()); - } - - @GetMapping(value = "/peers", produces = APPLICATION_JSON_VALUE) - public HashMap> getPeers() { - HashMap> myMap = new HashMap<>(); - - // Find all nodes that are not notaries, ourself, or the network map. - Stream filteredNodes = proxy.networkMapSnapshot().stream() - .filter(el -> !isNotary(el) && !isMe(el) && !isNetworkMap(el)); - // Get their names as strings - List nodeNames = filteredNodes.map(el -> el.getLegalIdentities().get(0).getName().toString()) - .collect(Collectors.toList()); - - myMap.put("peers", nodeNames); - return myMap; - } - - @GetMapping(value = "/notaries", produces = TEXT_PLAIN_VALUE) - private String notaries() { - return proxy.notaryIdentities().toString(); - } - - @GetMapping(value = "/flows", produces = TEXT_PLAIN_VALUE) - private String flows() { - return proxy.registeredFlows().toString(); - } - - @GetMapping(value = "/states", produces = TEXT_PLAIN_VALUE) - private String states() { - return proxy.vaultQuery(ContractState.class).getStates().toString(); - } - - @GetMapping(value = "/me",produces = APPLICATION_JSON_VALUE) - private HashMap whoami(){ - HashMap myMap = new HashMap<>(); - myMap.put("me", me.toString()); - return myMap; - } - @GetMapping(value = "/ious",produces = APPLICATION_JSON_VALUE) - public List> getIOUs() { - // Filter by state type: IOU. - return proxy.vaultQuery(IOUState.class).getStates(); - } - @GetMapping(value = "/cash",produces = APPLICATION_JSON_VALUE) - public List> getCash() { - // Filter by state type: Cash. - return proxy.vaultQuery(Cash.State.class).getStates(); - } - - @GetMapping(value = "/cash-balances",produces = APPLICATION_JSON_VALUE) - public Map> cashBalances(){ - return getCashBalances(proxy); - } - - @PutMapping(value = "/issue-iou" , produces = TEXT_PLAIN_VALUE ) - public ResponseEntity issueIOU(@RequestParam(value = "amount") int amount, - @RequestParam(value = "currency") String currency, - @RequestParam(value = "party") String party) throws IllegalArgumentException { - // Get party objects for myself and the counterparty. - Party me = proxy.nodeInfo().getLegalIdentities().get(0); - Party lender = Optional.ofNullable(proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(party))).orElseThrow(() -> new IllegalArgumentException("Unknown party name.")); - // Create a new IOU state using the parameters given. - try { - IOUState state = new IOUState(new Amount<>((long) amount * 100, Currency.getInstance(currency)), lender, me); - // Start the IOUIssueFlow. We block and waits for the flow to return. - SignedTransaction result = proxy.startTrackedFlowDynamic(IOUIssueFlow.InitiatorFlow.class, state).getReturnValue().get(); - // Return the response. - return ResponseEntity - .status(HttpStatus.CREATED) - .body("Transaction id "+ result.getId() +" committed to ledger.\n " + result.getTx().getOutput(0)); - // For the purposes of this demo app, we do not differentiate by exception type. - } catch (Exception e) { - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(e.getMessage()); - } - } - @GetMapping(value = "transfer-iou" , produces = TEXT_PLAIN_VALUE ) - public ResponseEntity transferIOU(@RequestParam(value = "id") String id, - @RequestParam(value = "party") String party) { - UniqueIdentifier linearId = new UniqueIdentifier(null,UUID.fromString(id)); - Party newLender = proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(party)); - try { - proxy.startTrackedFlowDynamic(IOUTransferFlow.InitiatorFlow.class, linearId, newLender).getReturnValue().get(); - return ResponseEntity.status(HttpStatus.CREATED).body("IOU "+linearId.toString()+" transferred to "+party+"."); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); - } - } - - /** - * Settles an IOU. Requires cash in the right currency to be able to settle. - * Example request: - * curl -X GET 'http://localhost:10007/api/iou/settle-iou?id=705dc5c5-44da-4006-a55b-e29f78955089&amount=98¤cy=USD' - */ - @GetMapping(value = "settle-iou" , produces = TEXT_PLAIN_VALUE ) - public ResponseEntity settleIOU(@RequestParam(value = "id") String id, - @RequestParam(value = "amount") int amount, - @RequestParam(value = "currency") String currency) { - - UniqueIdentifier linearId = new UniqueIdentifier(null, UUID.fromString(id)); - try { - proxy.startTrackedFlowDynamic(IOUSettleFlow.InitiatorFlow.class, linearId, - new Amount<>((long) amount * 100, Currency.getInstance(currency))).getReturnValue().get(); - return ResponseEntity.status(HttpStatus.CREATED).body(""+ amount+ currency +" paid off on IOU id "+linearId.toString()+"."); - - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); - } - } - - /** - * Helper end-point to issue some cash to ourselves. - * Example request: - * curl -X GET 'http://localhost:10009/api/iou/self-issue-cash?amount=100¤cy=USD' - */ - @GetMapping(value = "self-issue-cash" , produces = TEXT_PLAIN_VALUE ) - public ResponseEntity selfIssueCash(@RequestParam(value = "amount") int amount, - @RequestParam(value = "currency") String currency) { - - try { - Cash.State cashState = proxy.startTrackedFlowDynamic(SelfIssueCashFlow.class, - new Amount<>((long) amount * 100, Currency.getInstance(currency))).getReturnValue().get(); - return ResponseEntity.status(HttpStatus.CREATED).body(cashState.toString()); - - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); - } - } -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/NodeRPCConnection.java b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/NodeRPCConnection.java deleted file mode 100644 index 76dd6b09..00000000 --- a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/NodeRPCConnection.java +++ /dev/null @@ -1,63 +0,0 @@ -package net.corda.samples.server; - -import net.corda.client.rpc.CordaRPCClient; -import net.corda.client.rpc.CordaRPCConnection; -import net.corda.core.messaging.CordaRPCOps; -import net.corda.core.utilities.NetworkHostAndPort; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -/** - * Wraps a node RPC proxy. - */ -@Component -public class NodeRPCConnection implements AutoCloseable { - private final String host; - private final String username; - private final String password; - private final int rpcPort; - - - private CordaRPCConnection rpcConnection; - private CordaRPCOps proxy; // The RPC proxy - - /** - * The RPC proxy is configured based on the properties in `application.properties`. - * @param host The host of the node we are connecting to. - * @param rpcPort The RPC port of the node we are connecting to. - * @param username The username for logging into the RPC client. - * @param password The password for logging into the RPC client. - */ - public NodeRPCConnection( - @Value("${" + CONSTANTS.CORDA_NODE_HOST + "}") String host, - @Value("${" + CONSTANTS.CORDA_USER_NAME + "}") String username, - @Value("${" + CONSTANTS.CORDA_USER_PASSWORD + "}") String password, - @Value("${" + CONSTANTS.CORDA_RPC_PORT + "}") int rpcPort - ) { - this.host = host; - this.username = username; - this.password = password; - this.rpcPort = rpcPort; - } - - @PostConstruct - public void initialiseNodeRPCConnection() { - NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); - CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); - this.rpcConnection = rpcClient.start(username, password); - this.proxy = rpcConnection.getProxy(); - } - - public CordaRPCOps getProxy() { - return proxy; - } - - @PreDestroy - @Override - public void close() throws Exception { - rpcConnection.notifyServerAndClose(); - } -} diff --git a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/Server.java b/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/Server.java deleted file mode 100644 index 261ae9ef..00000000 --- a/Advanced/obligation-cordapp/clients/src/main/java/net/corda/samples/server/Server.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.corda.samples.server; - -import org.springframework.boot.Banner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.WebApplicationType; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * Our Spring Boot application. - */ -@SpringBootApplication -public class Server { - /** - * Starts our Spring Boot application. - */ - public static void main(String[] args) { - SpringApplication app = new SpringApplication(Server.class); - app.setBannerMode(Banner.Mode.OFF); - app.setWebApplicationType(WebApplicationType.SERVLET); - app.run(args); - } -} diff --git a/Advanced/obligation-cordapp/contracts/build.gradle b/Advanced/obligation-cordapp/contracts/build.gradle index 8671fdde..ae474abf 100644 --- a/Advanced/obligation-cordapp/contracts/build.gradle +++ b/Advanced/obligation-cordapp/contracts/build.gradle @@ -5,7 +5,7 @@ cordapp { targetPlatformVersion corda_platform_version minimumPlatformVersion corda_platform_version contract { - name "Template CorDapp" + name "Obligation CorDapp Contracts" vendor "Corda Open Source" licence "Apache License, Version 2.0" versionId 1 @@ -20,7 +20,7 @@ sourceSets { } } test{ - java{ + java { srcDir 'src/test/java' java.outputDir = file('bin/test') } @@ -33,8 +33,7 @@ dependencies { cordaRuntime "$corda_release_group:corda:$corda_release_version" cordaCompile "$corda_release_group:corda-finance-contracts:$corda_release_version" cordaCompile "$corda_release_group:corda-finance-workflows:$corda_release_version" - compileOnly "$corda_release_group:corda-testserver-impl:$corda_release_version" testCompile "$corda_release_group:corda-node-driver:$corda_release_version" cordapp "$corda_release_group:corda-finance-contracts:$corda_release_version" cordapp "$corda_release_group:corda-finance-workflows:$corda_release_version" -} \ No newline at end of file +} diff --git a/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/contracts/IOUContract.java b/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/contracts/IOUContract.java deleted file mode 100644 index f6023f99..00000000 --- a/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/contracts/IOUContract.java +++ /dev/null @@ -1,180 +0,0 @@ -package net.corda.samples.contracts; - -import net.corda.core.contracts.*; - -import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; -import static net.corda.core.contracts.ContractsDSL.requireThat; - -import net.corda.core.identity.AbstractParty; -import net.corda.core.identity.Party; -import net.corda.core.transactions.LedgerTransaction; -import net.corda.finance.contracts.asset.Cash; -import net.corda.samples.states.IOUState; - -import java.security.PublicKey; -import java.util.*; -import java.util.stream.Collectors; - -/** - * This is the contract code which defines how the [IOUState] behaves. Looks at the unit tests in - * [IOUContractTests] for more insight on how this contract verifies a transaction. - */ - -// LegalProseReference: this is just a dummy string for the time being. - -@LegalProseReference(uri = "") -public class IOUContract implements Contract { - public static final String IOU_CONTRACT_ID = "net.corda.samples.contracts.IOUContract"; - - /** - * The IOUContract can handle three transaction types involving [IOUState]s. - * - Issuance: Issuing a new [IOUState] on the ledger, which is a bilateral agreement between two parties. - * - Transfer: Re-assigning the lender/beneficiary. - * - Settle: Fully or partially settling the [IOUState] using the Corda [Cash] contract. - */ - public interface Commands extends CommandData { - class Issue extends TypeOnlyCommandData implements Commands{} - class Transfer extends TypeOnlyCommandData implements Commands{} - class Settle extends TypeOnlyCommandData implements Commands{} - } - /** - * The contract code for the [IOUContract]. - * The constraints are self documenting so don't require any additional explanation. - */ - @Override - public void verify(LedgerTransaction tx) { - - // We can use the requireSingleCommand function to extract command data from transaction. - final CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.class); - final Commands commandData = command.getValue(); - - /** - * This command data can then be used inside of a conditional statement to indicate which set of tests we - * should be performing - we will use different assertions to enable the contract to verify the transaction - * for issuing, settling and transferring. - */ - if (commandData.equals(new Commands.Issue())) { - - requireThat(require -> { - - require.using("No inputs should be consumed when issuing an IOU.", tx.getInputStates().size() == 0); - require.using( "Only one output state should be created when issuing an IOU.", tx.getOutputStates().size() == 1); - - IOUState outputState = tx.outputsOfType(IOUState.class).get(0); - require.using( "A newly issued IOU must have a positive amount.", outputState.amount.getQuantity() > 0); - require.using( "The lender and borrower cannot have the same identity.", outputState.lender.getOwningKey() != outputState.borrower.getOwningKey()); - - List signers = tx.getCommands().get(0).getSigners(); - HashSet signersSet = new HashSet<>(); - for (PublicKey key: signers) { - signersSet.add(key); - } - - List participants = tx.getOutputStates().get(0).getParticipants(); - HashSet participantKeys = new HashSet<>(); - for (AbstractParty party: participants) { - participantKeys.add(party.getOwningKey()); - } - - require.using("Both lender and borrower together only may sign IOU issue transaction.", signersSet.containsAll(participantKeys) && signersSet.size() == 2); - - return null; - }); - - } - - else if (commandData.equals(new Commands.Transfer())) { - - requireThat(require -> { - - require.using("An IOU transfer transaction should only consume one input state.", tx.getInputStates().size() == 1); - require.using("An IOU transfer transaction should only create one output state.", tx.getOutputStates().size() == 1); - - // Copy of input with new lender; - IOUState inputState = tx.inputsOfType(IOUState.class).get(0); - IOUState outputState = tx.outputsOfType(IOUState.class).get(0); - IOUState checkOutputState = outputState.withNewLender(inputState.getLender()); - - require.using("Only the lender property may change.", - checkOutputState.amount.equals(inputState.amount) && checkOutputState.getLinearId().equals(inputState.getLinearId()) && checkOutputState.borrower.equals(inputState.borrower) && checkOutputState.paid.equals(inputState.paid)); - require.using("The lender property must change in a transfer.", !outputState.lender.getOwningKey().equals(inputState.lender.getOwningKey())); - - List listOfPublicKeys = new ArrayList<>(); - listOfPublicKeys.add(inputState.lender.getOwningKey()); - listOfPublicKeys.add(inputState.borrower.getOwningKey()); - listOfPublicKeys.add(checkOutputState.lender.getOwningKey()); - - Set listOfParticipantPublicKeys = inputState.getParticipants().stream().map(AbstractParty::getOwningKey).collect(Collectors.toSet()); - listOfParticipantPublicKeys.add(outputState.lender.getOwningKey()); - List arrayOfSigners = command.getSigners(); - Set setOfSigners = new HashSet(arrayOfSigners); - require.using("The borrower, old lender and new lender only must sign an IOU transfer transaction", setOfSigners.equals(listOfParticipantPublicKeys) && setOfSigners.size() == 3); - return null; - - }); - - } - - else if (commandData.equals(new Commands.Settle())) { - - requireThat(require -> { - - // Check there is only one group of IOUs and that there is always an input IOU. - List> groups = tx.groupStates(IOUState.class, IOUState::getLinearId); - require.using("There must be one input IOU.", groups.get(0).getInputs().size() > 0); - - // Check that there are output cash states. - List allOutputCash = tx.outputsOfType(Cash.State.class); - require.using("There must be output cash.", !allOutputCash.isEmpty()); - - // Check that there is only one group of input IOU's - List> allGroupStates = tx.groupStates(IOUState.class, IOUState::getLinearId); - require.using("List has more than one element.", allGroupStates.size() < 2); - - IOUState inputIOU = tx.inputsOfType(IOUState.class).get(0); - Amount inputAmount = inputIOU.amount; - - // check that the output cash is being assigned to the lender - Party lenderIdentity = inputIOU.lender; - List acceptableCash = allOutputCash.stream().filter(cash -> cash.getOwner().getOwningKey().equals(lenderIdentity.getOwningKey())).collect(Collectors.toList()); - - require.using("There must be output cash paid to the recipient.", acceptableCash.size() > 0); - - // Sum the acceptable cash sent to the lender - Amount acceptableCashSum = new Amount<>(0, inputAmount.getToken()); - for (Cash.State cash: acceptableCash) { - Amount addCash = new Amount<>(cash.getAmount().getQuantity(), cash.getAmount().getToken().getProduct()); - acceptableCashSum = acceptableCashSum.plus(addCash); - } - - Amount amountOutstanding = inputIOU.amount.minus(inputIOU.paid); - require.using("The amount settled cannot be more than the amount outstanding.", amountOutstanding.getQuantity() >= acceptableCashSum.getQuantity()); - - if (amountOutstanding.equals(acceptableCashSum)) { - // If the IOU has been fully settled then there should be no IOU output state. - require.using("There must be no output IOU as it has been fully settled.", tx.outputsOfType(IOUState.class).isEmpty()); - - } else { - // If the IOU has been partially settled then it should still exist. - require.using("There must be one output IOU.", tx.outputsOfType(IOUState.class).size() == 1); - - IOUState outputIOU = tx.outputsOfType(IOUState.class).get(0); - - require.using("The amount may not change when settling.", inputIOU.amount.equals(outputIOU.amount)); - require.using("The lender may not change when settling.", inputIOU.lender.equals(outputIOU.lender)); - require.using("The borrower may not change when settling.", inputIOU.borrower.equals(outputIOU.borrower)); - } - - Set listOfParticipantPublicKeys = inputIOU.getParticipants().stream().map(AbstractParty::getOwningKey).collect(Collectors.toSet()); - List arrayOfSigners = command.getSigners(); - Set setOfSigners = new HashSet(arrayOfSigners); - require.using("Both lender and borrower must sign IOU settle transaction.", setOfSigners.equals(listOfParticipantPublicKeys)); - - return null; - }); - - } - - } - -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/obligation/contracts/IOUContract.java b/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/obligation/contracts/IOUContract.java new file mode 100644 index 00000000..b26cc7db --- /dev/null +++ b/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/obligation/contracts/IOUContract.java @@ -0,0 +1,180 @@ +package net.corda.samples.obligation.contracts; + +import net.corda.core.contracts.*; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.finance.contracts.asset.Cash; +import net.corda.samples.obligation.states.IOUState; + +import java.security.PublicKey; +import java.util.*; +import java.util.stream.Collectors; + +/** + * This is the contracts code which defines how the [IOUState] behaves. Looks at the unit tests in + * [IOUContractTests] for more insight on how this contracts verifies a transaction. + */ + +// LegalProseReference: this is just a dummy string for the time being. + +@LegalProseReference(uri = "") +public class IOUContract implements Contract { + public static final String IOU_CONTRACT_ID = "net.corda.samples.obligation.contracts.IOUContract"; + + /** + * The IOUContract can handle three transaction types involving [IOUState]s. + * - Issuance: Issuing a new [IOUState] on the ledger, which is a bilateral agreement between two parties. + * - Transfer: Re-assigning the lender/beneficiary. + * - Settle: Fully or partially settling the [IOUState] using the Corda [Cash] contracts. + */ + public interface Commands extends CommandData { + class Issue extends TypeOnlyCommandData implements Commands{} + class Transfer extends TypeOnlyCommandData implements Commands{} + class Settle extends TypeOnlyCommandData implements Commands{} + } + /** + * The contracts code for the [IOUContract]. + * The constraints are self documenting so don't require any additional explanation. + */ + @Override + public void verify(LedgerTransaction tx) { + + // We can use the requireSingleCommand function to extract command data from transaction. + final CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.class); + final Commands commandData = command.getValue(); + + /** + * This command data can then be used inside of a conditional statement to indicate which set of tests we + * should be performing - we will use different assertions to enable the contracts to verify the transaction + * for issuing, settling and transferring. + */ + if (commandData.equals(new Commands.Issue())) { + + requireThat(require -> { + + require.using("No inputs should be consumed when issuing an IOU.", tx.getInputStates().size() == 0); + require.using( "Only one output states should be created when issuing an IOU.", tx.getOutputStates().size() == 1); + + IOUState outputState = tx.outputsOfType(IOUState.class).get(0); + require.using( "A newly issued IOU must have a positive amount.", outputState.amount.getQuantity() > 0); + require.using( "The lender and borrower cannot have the same identity.", outputState.lender.getOwningKey() != outputState.borrower.getOwningKey()); + + List signers = tx.getCommands().get(0).getSigners(); + HashSet signersSet = new HashSet<>(); + for (PublicKey key: signers) { + signersSet.add(key); + } + + List participants = tx.getOutputStates().get(0).getParticipants(); + HashSet participantKeys = new HashSet<>(); + for (AbstractParty party: participants) { + participantKeys.add(party.getOwningKey()); + } + + require.using("Both lender and borrower together only may sign IOU issue transaction.", signersSet.containsAll(participantKeys) && signersSet.size() == 2); + + return null; + }); + + } + + else if (commandData.equals(new Commands.Transfer())) { + + requireThat(require -> { + + require.using("An IOU transfer transaction should only consume one input states.", tx.getInputStates().size() == 1); + require.using("An IOU transfer transaction should only create one output states.", tx.getOutputStates().size() == 1); + + // Copy of input with new lender; + IOUState inputState = tx.inputsOfType(IOUState.class).get(0); + IOUState outputState = tx.outputsOfType(IOUState.class).get(0); + IOUState checkOutputState = outputState.withNewLender(inputState.getLender()); + + require.using("Only the lender property may change.", + checkOutputState.amount.equals(inputState.amount) && checkOutputState.getLinearId().equals(inputState.getLinearId()) && checkOutputState.borrower.equals(inputState.borrower) && checkOutputState.paid.equals(inputState.paid)); + require.using("The lender property must change in a transfer.", !outputState.lender.getOwningKey().equals(inputState.lender.getOwningKey())); + + List listOfPublicKeys = new ArrayList<>(); + listOfPublicKeys.add(inputState.lender.getOwningKey()); + listOfPublicKeys.add(inputState.borrower.getOwningKey()); + listOfPublicKeys.add(checkOutputState.lender.getOwningKey()); + + Set listOfParticipantPublicKeys = inputState.getParticipants().stream().map(AbstractParty::getOwningKey).collect(Collectors.toSet()); + listOfParticipantPublicKeys.add(outputState.lender.getOwningKey()); + List arrayOfSigners = command.getSigners(); + Set setOfSigners = new HashSet(arrayOfSigners); + require.using("The borrower, old lender and new lender only must sign an IOU transfer transaction", setOfSigners.equals(listOfParticipantPublicKeys) && setOfSigners.size() == 3); + return null; + + }); + + } + + else if (commandData.equals(new Commands.Settle())) { + + requireThat(require -> { + + // Check there is only one group of IOUs and that there is always an input IOU. + List> groups = tx.groupStates(IOUState.class, IOUState::getLinearId); + require.using("There must be one input IOU.", groups.get(0).getInputs().size() > 0); + + // Check that there are output cash states. + List allOutputCash = tx.outputsOfType(Cash.State.class); + require.using("There must be output cash.", !allOutputCash.isEmpty()); + + // Check that there is only one group of input IOU's + List> allGroupStates = tx.groupStates(IOUState.class, IOUState::getLinearId); + require.using("List has more than one element.", allGroupStates.size() < 2); + + IOUState inputIOU = tx.inputsOfType(IOUState.class).get(0); + Amount inputAmount = inputIOU.amount; + + // check that the output cash is being assigned to the lender + Party lenderIdentity = inputIOU.lender; + List acceptableCash = allOutputCash.stream().filter(cash -> cash.getOwner().getOwningKey().equals(lenderIdentity.getOwningKey())).collect(Collectors.toList()); + + require.using("There must be output cash paid to the recipient.", acceptableCash.size() > 0); + + // Sum the acceptable cash sent to the lender + Amount acceptableCashSum = new Amount<>(0, inputAmount.getToken()); + for (Cash.State cash: acceptableCash) { + Amount addCash = new Amount<>(cash.getAmount().getQuantity(), cash.getAmount().getToken().getProduct()); + acceptableCashSum = acceptableCashSum.plus(addCash); + } + + Amount amountOutstanding = inputIOU.amount.minus(inputIOU.paid); + require.using("The amount settled cannot be more than the amount outstanding.", amountOutstanding.getQuantity() >= acceptableCashSum.getQuantity()); + + if (amountOutstanding.equals(acceptableCashSum)) { + // If the IOU has been fully settled then there should be no IOU output states. + require.using("There must be no output IOU as it has been fully settled.", tx.outputsOfType(IOUState.class).isEmpty()); + + } else { + // If the IOU has been partially settled then it should still exist. + require.using("There must be one output IOU.", tx.outputsOfType(IOUState.class).size() == 1); + + IOUState outputIOU = tx.outputsOfType(IOUState.class).get(0); + + require.using("The amount may not change when settling.", inputIOU.amount.equals(outputIOU.amount)); + require.using("The lender may not change when settling.", inputIOU.lender.equals(outputIOU.lender)); + require.using("The borrower may not change when settling.", inputIOU.borrower.equals(outputIOU.borrower)); + } + + Set listOfParticipantPublicKeys = inputIOU.getParticipants().stream().map(AbstractParty::getOwningKey).collect(Collectors.toSet()); + List arrayOfSigners = command.getSigners(); + Set setOfSigners = new HashSet(arrayOfSigners); + require.using("Both lender and borrower must sign IOU settle transaction.", setOfSigners.equals(listOfParticipantPublicKeys)); + + return null; + }); + + } + + } + +} diff --git a/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/obligation/states/IOUState.java b/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/obligation/states/IOUState.java new file mode 100644 index 00000000..35cb1941 --- /dev/null +++ b/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/obligation/states/IOUState.java @@ -0,0 +1,96 @@ +package net.corda.samples.obligation.states; + +import net.corda.core.contracts.*; +import net.corda.core.identity.Party; +import net.corda.core.identity.AbstractParty; + +import java.util.*; +import com.google.common.collect.ImmutableList; +import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.samples.obligation.contracts.IOUContract; + +/** + * The IOU State object, with the following properties: + * - [amount] The amount owed by the [borrower] to the [lender] + * - [lender] The lending party. + * - [borrower] The borrowing party. + * - [contracts] Holds a reference to the [IOUContract] + * - [paid] Records how much of the [amount] has been paid. + * - [linearId] A unique id shared by all LinearState states representing the same agreement throughout history within + * the vaults of all parties. Verify methods should check that one input and one output share the id in a transaction, + * except at issuance/termination. + */ + +@BelongsToContract(IOUContract.class) +public class IOUState implements ContractState, LinearState { + + public final Amount amount; + public final Party lender; + public final Party borrower; + public final Amount paid; + private final UniqueIdentifier linearId; + + // Private constructor used only for copying a State object + @ConstructorForDeserialization + private IOUState(Amount amount, Party lender, Party borrower, Amount paid, UniqueIdentifier linearId){ + this.amount = amount; + this.lender = lender; + this.borrower = borrower; + this.paid = paid; + this.linearId = linearId; + } + + public IOUState(Amount amount, Party lender, Party borrower) { + this(amount, lender, borrower, new Amount<>(0, amount.getToken()), new UniqueIdentifier()); + } + + public Amount getAmount() { + return amount; + } + + public Party getLender() { + return lender; + } + + public Party getBorrower() { + return borrower; + } + + public Amount getPaid() { + return paid; + } + + @Override + public UniqueIdentifier getLinearId() { + return linearId; + } + + /** + * This method will return a list of the nodes which can "use" this states in a valid transaction. In this case, the + * lender or the borrower. + */ + @Override + public List getParticipants() { + return ImmutableList.of(lender, borrower); + } + + /** + * Helper methods for when building transactions for settling and transferring IOUs. + * - [pay] adds an amount to the paid property. It does no validation. + * - [withNewLender] creates a copy of the current states with a newly specified lender. For use when transferring. + * - [copy] creates a copy of the states using the internal copy constructor ensuring the LinearId is preserved. + */ + public IOUState pay(Amount amountToPay) { + Amount newAmountPaid = this.paid.plus(amountToPay); + return new IOUState(amount, lender, borrower, newAmountPaid, linearId); + } + + public IOUState withNewLender(Party newLender) { + return new IOUState(amount, newLender, borrower, paid, linearId); + } + + public IOUState copy(Amount amount, Party lender, Party borrower, Amount paid) { + return new IOUState(amount, lender, borrower, paid, this.getLinearId()); + } + +} diff --git a/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/states/IOUState.java b/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/states/IOUState.java deleted file mode 100644 index 0b45b65a..00000000 --- a/Advanced/obligation-cordapp/contracts/src/main/java/net/corda/samples/states/IOUState.java +++ /dev/null @@ -1,96 +0,0 @@ -package net.corda.samples.states; - -import net.corda.core.contracts.*; -import net.corda.core.identity.Party; -import net.corda.core.identity.AbstractParty; - -import java.util.*; -import com.google.common.collect.ImmutableList; -import net.corda.core.serialization.ConstructorForDeserialization; -import net.corda.samples.contracts.IOUContract; - -/** - * The IOU State object, with the following properties: - * - [amount] The amount owed by the [borrower] to the [lender] - * - [lender] The lending party. - * - [borrower] The borrowing party. - * - [contract] Holds a reference to the [IOUContract] - * - [paid] Records how much of the [amount] has been paid. - * - [linearId] A unique id shared by all LinearState states representing the same agreement throughout history within - * the vaults of all parties. Verify methods should check that one input and one output share the id in a transaction, - * except at issuance/termination. - */ - -@BelongsToContract(IOUContract.class) -public class IOUState implements ContractState, LinearState { - - public final Amount amount; - public final Party lender; - public final Party borrower; - public final Amount paid; - private final UniqueIdentifier linearId; - - // Private constructor used only for copying a State object - @ConstructorForDeserialization - private IOUState(Amount amount, Party lender, Party borrower, Amount paid, UniqueIdentifier linearId){ - this.amount = amount; - this.lender = lender; - this.borrower = borrower; - this.paid = paid; - this.linearId = linearId; - } - - public IOUState(Amount amount, Party lender, Party borrower) { - this(amount, lender, borrower, new Amount<>(0, amount.getToken()), new UniqueIdentifier()); - } - - public Amount getAmount() { - return amount; - } - - public Party getLender() { - return lender; - } - - public Party getBorrower() { - return borrower; - } - - public Amount getPaid() { - return paid; - } - - @Override - public UniqueIdentifier getLinearId() { - return linearId; - } - - /** - * This method will return a list of the nodes which can "use" this state in a valid transaction. In this case, the - * lender or the borrower. - */ - @Override - public List getParticipants() { - return ImmutableList.of(lender, borrower); - } - - /** - * Helper methods for when building transactions for settling and transferring IOUs. - * - [pay] adds an amount to the paid property. It does no validation. - * - [withNewLender] creates a copy of the current state with a newly specified lender. For use when transferring. - * - [copy] creates a copy of the state using the internal copy constructor ensuring the LinearId is preserved. - */ - public IOUState pay(Amount amountToPay) { - Amount newAmountPaid = this.paid.plus(amountToPay); - return new IOUState(amount, lender, borrower, newAmountPaid, linearId); - } - - public IOUState withNewLender(Party newLender) { - return new IOUState(amount, newLender, borrower, paid, linearId); - } - - public IOUState copy(Amount amount, Party lender, Party borrower, Amount paid) { - return new IOUState(amount, lender, borrower, paid, this.getLinearId()); - } - -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/contracts/ContractTests.java b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/contracts/ContractTests.java deleted file mode 100644 index 733be464..00000000 --- a/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/contracts/ContractTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.corda.samples.contracts; - -import net.corda.testing.node.MockServices; -import org.junit.Test; - -public class ContractTests { - private final MockServices ledgerServices = new MockServices(); - - @Test - public void dummyTest() { - - } -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/TestUtils.java b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/TestUtils.java new file mode 100644 index 00000000..f08428df --- /dev/null +++ b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/TestUtils.java @@ -0,0 +1,13 @@ +package net.corda.samples.obligation; + +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; + +public class TestUtils { + public static TestIdentity ALICE = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + public static TestIdentity BOB = new TestIdentity(new CordaX500Name("Bob", "TestCity", "US")); + public static TestIdentity CHARLIE = new TestIdentity(new CordaX500Name("Charlie", "TestVillage", "US")); + public static TestIdentity MINICORP = new TestIdentity(new CordaX500Name("MiniCorp", "MiniLand", "US")); + public static TestIdentity MEGACORP = new TestIdentity(new CordaX500Name("MegaCorp", "MiniLand", "US")); + public static TestIdentity DUMMY = new TestIdentity(new CordaX500Name("Dummy", "FakeLand", "US")); +} diff --git a/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUIssueTests.java b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUIssueTests.java new file mode 100644 index 00000000..fa261f2a --- /dev/null +++ b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUIssueTests.java @@ -0,0 +1,281 @@ +package net.corda.samples.obligation.contracts; + + +import net.corda.core.contracts.*; +import net.corda.finance.*; +import net.corda.samples.obligation.states.IOUStateTests; +import net.corda.testing.contracts.DummyState; +import net.corda.testing.node.MockServices; +import static net.corda.testing.node.NodeTestUtils.ledger; +import net.corda.samples.obligation.TestUtils; +import net.corda.core.transactions.LedgerTransaction; +import java.util.Arrays; +import net.corda.samples.obligation.states.IOUState; +import org.junit.*; + + +/** + * Practical exercise instructions for Contracts Part 1. + * The objective here is to write some contracts code that verifies a transaction to issue an {@link IOUState}. + * As with the {@link IOUStateTests} uncomment each unit test and run them one at a time. Use the body of the tests and the + * task description to determine how to get the tests to pass. + */ +public class IOUIssueTests { + // A pre-defined dummy command. + public interface Commands extends CommandData { + class DummyCommand extends TypeOnlyCommandData implements Commands{} + } + + static private final MockServices ledgerServices = new MockServices( + Arrays.asList("net.corda.samples.obligation.contracts") + ); + + /** + * Task 1. + * Recall that Commands are required to hint to the intention of the transaction as well as take a list of + * public keys as parameters which correspond to the required signers for the transaction. + * Commands also become more important later on when multiple actions are possible with an IOUState, e.g. Transfer + * and Settle. + * TODO: Add an "Issue" command to the IOUContract and check for the existence of the command in the verify function. + * Hint: + * - For the Issue command we only care about the existence of it in a transaction, therefore it should extend + * the {@link TypeOnlyCommandData} class. + * - The command should be defined inside {@link IOUContract}. + * - We usually encapsulate our commands in an interface inside the contracts class called {@link Commands} which + * extends the {@link CommandData} interface. The Issue command itself should be defined inside the {@link Commands} + * interface as well as implement it, for example: + * + * public interface Commands extends CommandData { + * class X extends TypeOnlyCommandData implements Commands{} + * } + * + * - We can check for the existence of any command that implements [IOUContract.Commands] by using the + * [requireSingleCommand] function which takes a {@link Class} argument. + * - You can use the [requireSingleCommand] function on [tx.getCommands()] to check for the existence and type of the specified command + * in the transaction. [requireSingleCommand] requires a Class argument to identify the type of command required. + * + * requireSingleCommand(tx.getCommands(), REQUIRED_COMMAND.class) + */ + @Test + public void mustIncludeIssueCommand() { + IOUState iou = new IOUState(Currencies.POUNDS(1), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new Commands.DummyCommand()); // Wrong type. + return tx.failsWith("Contract verification failed"); + }); + l.transaction(tx -> { + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); // Correct type. + return tx.verifies(); + }); + return null; + }); + } + + /** + * Task 2. + * As previously observed, issue transactions should not have any input states references. Therefore we must check to + * ensure that no input states are included in a transaction to issue an IOU. + * TODO: Write a contracts constraint that ensures a transaction to issue an IOU does not include any input states. + * Hint: use a [requireThat] lambda with a constraint to inside the [IOUContract.verify] function to encapsulate your + * constraints: + * + * requireThat(requirement -> { + * requirement.using("Message when constraint fails", (boolean constraining expression)); + * // passes all cases + * return null; + * }); + * + * Note that the unit tests often expect contracts verification failure with a specific message which should be + * defined with your contracts constraints. If not then the unit test will fail! + * + * You can access the list of inputs via the {@link LedgerTransaction} object which is passed into + * [IOUContract.verify]. + */ + @Test + public void issueTransactionMustHaveNoInputs() { + IOUState iou = new IOUState(Currencies.POUNDS(1), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, new DummyState()); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.failsWith("No inputs should be consumed when issuing an IOU"); + }); + l.transaction(tx -> { + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + return tx.verifies(); // As there are no input sates + }); + return null; + }); + } + + /** + * Task 3. + * Now we need to ensure that only one {@link IOUState} is issued per transaction. + * TODO: Write a contracts constraint that ensures only one output states is created in a transaction. + * Hint: Write an additional constraint within the existing [requireThat] block which you created in the previous + * task. + */ + @Test + public void issueTransactionMustHaveOneOutput() { + IOUState iou = new IOUState(Currencies.POUNDS(1), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); // Two outputs fails. + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.failsWith("Only one output states should be created when issuing an IOU."); + }); + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); // One output passes. + return tx.verifies(); + }); + return null; + }); + } + + /** + * Task 4. + * Now we need to consider the properties of the {@link IOUState}. We need to ensure that an IOU should always have a + * positive value. + * TODO: Write a contracts constraint that ensures newly issued IOUs always have a positive value. + * Hint: You will need a number of hints to complete this task! + * - Create a new constant which will hold a reference to the output IOU states. + * - We need to obtain a reference to the proposed IOU for issuance from the [LedgerTransaction.getOutputStates()] list + * - You can use the function [get(0)] to grab the single element from the list. + * This list is typed as a list of {@link ContractState}s, therefore we need to cast the {@link ContractState} which we return + * to an {@link IOUState}. E.g. + * + * XState states = (XState)tx.getOutputStates().get(0) + * + * - When checking the [IOUState.getAmount()] property is greater than zero, you need to check the + * [IOUState.getAmount().getQuantity()] field. + */ + @Test + public void cannotCreateZeroValueIOUs() { + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, new IOUState(Currencies.POUNDS(0), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty())); // Zero amount fails. + return tx.failsWith("A newly issued IOU must have a positive amount."); + }); + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, new IOUState(Currencies.SWISS_FRANCS(100), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty())); + return tx.verifies(); + }); + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, new IOUState(Currencies.POUNDS(1), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty())); + return tx.verifies(); + }); + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty())); + return tx.verifies(); + }); + return null; + }); + } + + /** + * Task 5. + * For obvious reasons, the identity of the lender and borrower must be different. + * TODO: Add a contracts constraint to check the lender is not the borrower. + * Hint: + * - You can use the [IOUState.getLender()] and [IOUState.getBorrower()] properties. + * - This check must be made before the checking who has signed. + */ + @Test + public void lenderAndBorrowerCannotBeTheSame() { + IOUState iou = new IOUState(Currencies.POUNDS(1), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + IOUState borrowerIsLenderIou = new IOUState(Currencies.POUNDS(10), TestUtils.ALICE.getParty(), TestUtils.ALICE.getParty()); + ledger(ledgerServices, l-> { + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, borrowerIsLenderIou); + return tx.failsWith("The lender and borrower cannot have the same identity."); + }); + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.verifies(); + }); + return null; + }); + } + + /** + * Task 6. + * The list of public keys which the commands hold should contain all of the participants defined in the {@link IOUState}. + * This is because the IOU is a bilateral agreement where both parties involved are required to sign to issue an + * IOU or change the properties of an existing IOU. + * TODO: Add a contracts constraint to check that all the required signers are {@link IOUState} participants. + * Hint: + * - In Java, you can perform a set equality check of two sets with the .equals() + * - We need to check that the signers for the Command of this transaction equals the participants list. + * - We don't want any additional public keys not listed in the IOUs participants list. + * - You will need a reference to the Issue command to get access to the list of signers. + * - [requireSingleCommand] returns the single required [CommandWithParties] - you can assign the return + * value to a constant. + * - Next, you will need to retrieve the participants of the output states and ensure they are equal them. + * + * Java Hints + * - Java's map function allows for conversion of a Collection. However, it requires a Stream object (created by + * calling collection.stream()), which must then + * be converted back into a Collection using collect(Collectors.toCOLLECTION_TYPE). All together, this looks like: + * collection.stream().map(element -> (some operation on element)).collect(Collectors.toCOLLECTION_TYPE) + * This will be needed for mapping the List from getParticipants() to a List + * - https://zeroturnaround.com/rebellabs/java-8-explained-applying-lambdas-to-java-collections/ + * - A Collection can be turned into a set using: new HashSet<>(collection) +// */ + @Test + public void lenderAndBorrowerMustSignIssueTransaction() { + IOUState iou = new IOUState(Currencies.POUNDS(1), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + ledger(ledgerServices, l->{ + l.transaction(tx-> { + tx.command(TestUtils.DUMMY.getPublicKey(), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); + }); + l.transaction(tx-> { + tx.command(TestUtils.ALICE.getPublicKey(), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); + }); + l.transaction(tx-> { + tx.command(TestUtils.BOB.getPublicKey(), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); + }); + l.transaction(tx-> { + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); + }); + l.transaction(tx-> { + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.MINICORP.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); + }); + l.transaction(tx-> { + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.verifies(); + }); + l.transaction(tx-> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + return tx.verifies(); + }); + return null; + }); + } +} diff --git a/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUSettleTests.java b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUSettleTests.java new file mode 100644 index 00000000..c0def6f7 --- /dev/null +++ b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUSettleTests.java @@ -0,0 +1,514 @@ +package net.corda.samples.obligation.contracts; + + +import net.corda.core.contracts.*; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.PartyAndReference; +import net.corda.core.contracts.TypeOnlyCommandData; +import net.corda.core.identity.AbstractParty; +import net.corda.core.utilities.OpaqueBytes; +import net.corda.finance.Currencies; +import net.corda.finance.contracts.asset.Cash; +import net.corda.testing.node.MockServices; +import net.corda.samples.obligation.TestUtils; +import net.corda.samples.obligation.states.IOUState; +import org.junit.Test; +import java.util.Arrays; +import java.util.Currency; +import static net.corda.testing.node.NodeTestUtils.ledger; + + +/** + * Practical exercise instructions for Contracts Part 3. + * The objective here is to write some contracts code that verifies a transaction to settle an [IOUState]. + * Settling is more complicated than transferring and issuing as it requires you to use multiple states types in a + * transaction. + * As with the [IOUIssueTests] and [IOUTransferTests] uncomment each unit test and run them one at a time. Use the body + * of the tests and the task description to determine how to get the tests to pass. + */ + +public class IOUSettleTests { + + public interface Commands extends CommandData { + class DummyCommand extends TypeOnlyCommandData implements Commands{} + } + + static private final MockServices ledgerServices = new MockServices( + Arrays.asList("net.corda.samples.obligation.contracts", "net.corda.finance.contracts.asset") + ); + + private Cash.State createCashState(AbstractParty owner, Amount amount) { + OpaqueBytes defaultBytes = new OpaqueBytes(new byte[1]); + PartyAndReference partyAndReference = new PartyAndReference(owner, defaultBytes); + return new Cash.State(partyAndReference, amount, owner); + } + + /** + * Task 1. + * We need to add another case to deal with settling in the [IOUContract.verify] function. + * TODO: Add the [IOUContract.Commands.Settle] case to the verify function. + * Hint: You can leave the body empty for now. + */ + @Test + public void mustIncludeSettleCommand() { + IOUState iou = new IOUState(Currencies.POUNDS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State inputCash = createCashState(TestUtils.BOB.getParty(), Currencies.POUNDS(5)); + OwnableState outputCash = inputCash.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState(); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); + tx.input(Cash.class.getName(), inputCash); + tx.output(Cash.class.getName(), outputCash); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + return tx.failsWith("Contract Verification Failed"); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); + tx.input(Cash.class.getName(), inputCash); + tx.output(Cash.class.getName(), outputCash); + tx.command(TestUtils.BOB.getPublicKey(), new Commands.DummyCommand()); + return tx.failsWith("Contract verification failed"); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); + tx.input(Cash.class.getName(), inputCash); + tx.output(Cash.class.getName(), outputCash); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Settle()); + return tx.verifies(); + }); + return null; + }); + } + + /** + * Task 2. + * For now, we only want to settle one IOU at once. We can use the [TransactionForContract.groupStates] function + * to group the IOUs by their [linearId] property. We want to make sure there is only one group of input and output + * IOUs. + * TODO: Using [groupStates] add a constraint that checks for one group of input/output IOUs. + * Hint: + * - The [groupStates] method on a Transaction takes two type parameters: the type of the states you wish to group by and the type + * of the grouping key used (indicated by a method reference), in this case as you need to use the [linearId] and it is a [UniqueIdentifier]. + * + * tx.groupStates(State.class, State::getLinearId) + * + */ + @Test + public void mustBeOneGroupOfIOUs() { + IOUState iouONE = new IOUState(Currencies.POUNDS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + IOUState iouTWO = new IOUState(Currencies.POUNDS(5), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State inputCash = createCashState(TestUtils.BOB.getParty(), Currencies.POUNDS(5)); + CommandAndState outputCash = inputCash.withNewOwner(TestUtils.ALICE.getParty()); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iouONE); + tx.input(IOUContract.IOU_CONTRACT_ID, iouTWO); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Settle()); + tx.output(IOUContract.IOU_CONTRACT_ID, iouONE.pay(Currencies.POUNDS(5))); + tx.input(Cash.class.getName(), inputCash); + tx.output(Cash.class.getName(), outputCash.getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.failsWith("List has more than one element."); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iouONE); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Settle()); + tx.output(IOUContract.IOU_CONTRACT_ID, iouONE.pay(Currencies.POUNDS(5))); + tx.input(Cash.class.getName(), inputCash); + tx.output(Cash.class.getName(), outputCash.getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.verifies(); + return null; + }); + return null; + }); + } + + /** + * Task 3. + * There always has to be one input IOU in a settle transaction but there might not be an output IOU. + * TODO: Add a constraint to check there is always one input IOU. + */ + + @Test + public void mustHaveOneInputIOU() { + + IOUState iou = new IOUState(Currencies.POUNDS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + IOUState iouOne = new IOUState(Currencies.POUNDS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State tenPounds = createCashState( TestUtils.BOB.getParty(), Currencies.POUNDS(10)); + Cash.State fivePounds = createCashState( TestUtils.BOB.getParty(), Currencies.POUNDS(5)); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Settle()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + tx.failsWith("There must be one input IOU."); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Settle()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), fivePounds); + tx.output(Cash.class.getName(), fivePounds.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.verifies(); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iouOne); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Settle()); + tx.input(Cash.class.getName(), tenPounds); + tx.output(Cash.class.getName(), tenPounds.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.verifies(); + return null; + }); + return null; + }); + + } + + /** + * Task 4. + * Now we need to ensure that there are cash states present in the outputs list. The [IOUContract] doesn't care + * about input cash as the validity of the cash transaction will be checked by the [Cash] contracts. We do however + * need to count how much cash is being used to settle and update our [IOUState] accordingly. + * TODO: Filter out the cash states from the list of outputs list and assign them to a constant. + * Hint: + * - Use the [outputsOfType] extension function to filter the transaction's outputs by type, in this case [Cash.State]. + */ + @Test + public void mustBeCashOutputStatesPresent() { + + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State cash = createCashState(TestUtils.BOB.getParty(), Currencies.DOLLARS(5)); + CommandAndState cashPayment = cash.withNewOwner(TestUtils.ALICE.getParty()); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("There must be output cash."); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), cash); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); + tx.output(Cash.class.getName(), cashPayment.getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Settle()); + tx.verifies(); + return null; + }); + return null; + }); + + } + + /** + * Task 5. + * Not only to we need to check that [Cash] output states are present but we need to check that the payer is + * correctly assigning the lender as the new owner of these states. + * TODO: Add a constraint to check that the lender is the new owner of at least some output cash. + * Hint: + * - Not all of the cash may be assigned to the lender as some of the input cash may be sent back to the borrower as change. + * - We need to use the [Cash.State.getOwner()] method to check to see that it is the value of our public key. + * - Use [filter] to filter over the list of cash states to get the ones which are being assigned to us. + * - Once we have this filtered list, we can sum the cash being paid to us so we know how much is being settled. + */ + @Test + public void mustBeCashOutputStatesWithRecipientAsOwner() { + IOUState iou = new IOUState(Currencies.POUNDS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State cash = createCashState(TestUtils.BOB.getParty(), Currencies.POUNDS(5)); + CommandAndState invalidCashPayment = cash.withNewOwner(TestUtils.CHARLIE.getParty()); + CommandAndState validCashPayment = cash.withNewOwner(TestUtils.ALICE.getParty()); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), cash); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); + tx.output(Cash.class.getName(), invalidCashPayment.getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), invalidCashPayment.getCommand()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("There must be output cash paid to the recipient."); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), cash); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); + tx.output(Cash.class.getName(), validCashPayment.getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), validCashPayment.getCommand()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.verifies(); + return null; + }); + return null; + }); + + } + + /** + * Task 6. + * Now we need to sum the cash which is being assigned to the lender and compare this total against how much of the iou is + * left to pay. + * TODO: Add a constraint that checks the lender cannot be paid more than the remaining IOU amount left to pay. + * Hint: + * - The remaining amount of the IOU is the amount less the paid property. + * - To sum a list of [Cash.State]s, collect all [Cash.States] assigned to the lender and use a reduce method or + * = explicitly loop through the list to sum the individual states. + * - We can compare the amount left paid to the amount being paid to use, ensuring the amount being paid isn't too much. + */ + @Test + public void cashSettlementAmountMustBeLessThanRemainingIOUAmount() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State elevenDollars = createCashState( TestUtils.BOB.getParty(), Currencies.DOLLARS(11)); + Cash.State tenDollars = createCashState( TestUtils.BOB.getParty(), Currencies.DOLLARS(10)); + Cash.State fiveDollars = createCashState( TestUtils.BOB.getParty(), Currencies.DOLLARS(5)); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), elevenDollars); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(11))); + tx.output(Cash.class.getName(), elevenDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("The amount settled cannot be more than the amount outstanding."); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), fiveDollars); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); + tx.output(Cash.class.getName(), fiveDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.verifies(); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), tenDollars); + tx.output(Cash.class.getName(), tenDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.verifies(); + return null; + }); + return null; + }); + } + + /** + * Task 7. + * Your Java implementation should handle this for you but it goes without saying that we should only be able to settle + * in the currency that the IOU in denominated in. + * TODO: You shouldn't have anything to do here but here are some tests just to make sure! + */ + @Test + public void cashSettlementMustBeInTheCorrectCurrency() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State tenDollars = createCashState( TestUtils.BOB.getParty(), Currencies.DOLLARS(10)); + Cash.State tenPounds = createCashState( TestUtils.BOB.getParty(), Currencies.POUNDS(10)); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), tenPounds); + tx.output(Cash.class.getName(), tenPounds.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("Token mismatch: GBP vs USD"); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), tenDollars); + tx.output(Cash.class.getName(), tenDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.verifies(); + return null; + }); + return null; + }); + } + + /** + * Task 8. + * If we fully settle the IOU, then we are done and thus don't require one on ledgerServices.ledger anymore. However, if we only + * partially settle the IOU, then we want to keep the IOU on ledger with an amended [paid] property. + * TODO: Write a constraint that ensures the correct behaviour depending on the amount settled vs amount remaining. + * Hint: You can use a simple if statement and compare the total amount paid vs amount left to settle. + */ + @Test + public void mustOnlyHaveOutputIOUIfNotFullySettling() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State tenDollars = createCashState( TestUtils.BOB.getParty(), Currencies.DOLLARS(10)); + Cash.State fiveDollars = createCashState( TestUtils.BOB.getParty(), Currencies.DOLLARS(5)); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), fiveDollars); + tx.output(Cash.class.getName(), fiveDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("There must be one output IOU."); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), fiveDollars); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); + tx.output(Cash.class.getName(), fiveDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.verifies(); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), tenDollars); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(10))); + tx.output(Cash.class.getName(), tenDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("There must be no output IOU as it has been fully settled."); + return null; + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), tenDollars); + tx.output(Cash.class.getName(), tenDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.verifies(); + return null; + }); + return null; + }); + } + + /** + * Task 9. + * We want to make sure that the only property of the IOU which changes when we settle, is the paid amount. + * TODO: Write a constraint to check only the paid property of the [IOUState] changes when settling. + */ + @Test + public void onlyPaidPropertyMayChange() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State fiveDollars = createCashState( TestUtils.BOB.getParty(), Currencies.DOLLARS(5)); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), fiveDollars); + IOUState iouCopy = iou.copy(iou.amount, iou.lender, TestUtils.CHARLIE.getParty(), iou.paid).pay(Currencies.DOLLARS(5)); + tx.output(IOUContract.IOU_CONTRACT_ID, iouCopy); + tx.output(Cash.class.getName(), fiveDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("The borrower may not change when settling."); + return null; + }); + + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), fiveDollars); + tx.output(Cash.class.getName(), fiveDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + IOUState iouCopy = iou.copy(Currencies.DOLLARS(0), iou.lender, TestUtils.CHARLIE.getParty(), iou.paid).pay(Currencies.DOLLARS(5)); + tx.output(IOUContract.IOU_CONTRACT_ID, iouCopy); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("The amount may not change when settling."); + return null; + }); + + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), fiveDollars); + tx.output(Cash.class.getName(), fiveDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + IOUState iouCopy = iou.copy(iou.amount, TestUtils.CHARLIE.getParty(), iou.borrower, iou.paid).pay(Currencies.DOLLARS(5)); + tx.output(IOUContract.IOU_CONTRACT_ID, iouCopy); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("The lender may not change when settling."); + return null; + }); + + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(Cash.class.getName(), fiveDollars); + tx.output(Cash.class.getName(), fiveDollars.withNewOwner(TestUtils.ALICE.getParty()).getOwnableState()); + IOUState iouCopy = iou.copy(iou.amount, iou.lender, iou.borrower, iou.paid).pay(Currencies.DOLLARS(5)); + tx.output(IOUContract.IOU_CONTRACT_ID, iouCopy); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.verifies(); + return null; + }); + + return null; + }); + + } + + /** + * Task 10. + * Both the lender and the borrower must have signed an IOU issue transaction. + * TODO: Add a constraint to the contracts code that ensures this is the case. + */ + public void mustBeSignedByAllParticipants() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Cash.State cash = createCashState(TestUtils.BOB.getParty(), Currencies.DOLLARS(5)); + CommandAndState cashPayment = cash.withNewOwner(TestUtils.ALICE.getParty()); + + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(Cash.class.getName(), cash); + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(Cash.class.getName(), cashPayment.getOwnableState()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("Both lender and borrower together only must sign IOU settle transaction."); + return null; + }); + l.transaction(tx -> { + tx.input(Cash.class.getName(), cash); + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(Cash.class.getName(), cashPayment.getOwnableState()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(TestUtils.BOB.getPublicKey(), new IOUContract.Commands.Settle()); + tx.failsWith("Both lender and borrower together only must sign IOU settle transaction."); + return null; + }); + l.transaction(tx -> { + tx.input(Cash.class.getName(), cash); + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(Cash.class.getName(), cashPayment.getOwnableState()); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); + tx.command(TestUtils.BOB.getPublicKey(), new Cash.Commands.Move()); + tx.command(Arrays.asList(TestUtils.BOB.getPublicKey(), TestUtils.ALICE.getPublicKey()), new IOUContract.Commands.Settle()); + tx.failsWith("Both lender and borrower together only must sign IOU settle transaction."); + return null; + }); + return null; + }); + + } +} diff --git a/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUTransferTests.java b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUTransferTests.java new file mode 100644 index 00000000..801cacea --- /dev/null +++ b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/contracts/IOUTransferTests.java @@ -0,0 +1,263 @@ +package net.corda.samples.obligation.contracts; + + +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.PartyAndReference; +import net.corda.core.contracts.TypeOnlyCommandData; +import net.corda.core.identity.AbstractParty; +import net.corda.core.utilities.OpaqueBytes; +import net.corda.finance.Currencies; +import net.corda.finance.contracts.asset.Cash; +import net.corda.samples.obligation.TestUtils; +import net.corda.testing.node.MockServices; +import net.corda.samples.obligation.states.IOUState; +import org.junit.Test; +import java.util.Arrays; +import java.util.Currency; +import static net.corda.testing.node.NodeTestUtils.ledger; + +/** + * Practical exercise instructions for Contracts Part 2. + * The objective here is to write some contracts code that verifies a transaction to issue an [IOUState]. + * As with the [IOUIssueTests] uncomment each unit test and run them one at a time. Use the body of the tests and the + * task description to determine how to get the tests to pass. + */ + +public class IOUTransferTests { + // A pre-defined dummy command. + public interface Commands extends CommandData { + class DummyCommand extends TypeOnlyCommandData implements Commands{} + } + + static private final MockServices ledgerServices = new MockServices( + Arrays.asList("net.corda.samples.obligation.contracts") + ); + + /** + * Uncomment the testing setup below. + */ + // A dummy states + IOUState dummyState = new IOUState(Currencies.DOLLARS(0), TestUtils.CHARLIE.getParty(), TestUtils.CHARLIE.getParty()); + + // function to create new Cash states. + private Cash.State createCashState(AbstractParty owner, Amount amount) { + OpaqueBytes defaultBytes = new OpaqueBytes(new byte[1]); + PartyAndReference partyAndReference = new PartyAndReference(owner, defaultBytes); + return new Cash.State(partyAndReference, amount, owner); + } + + /** + * Task 1. + * Now things are going to get interesting! + * We need the [IOUContract] to not only handle Issues of IOUs but now also Transfers. + * Of course, we'll need to add a new Command and add some additional contracts code to handle Transfers. + * TODO: Add a "Transfer" command to the IOUState and update the verify() function to handle multiple commands. + * Hint: + * - As with the [Issue] command, add the [Transfer] command within the [IOUContract.Commands]. + * - Again, we only care about the existence of the [Transfer] command in a transaction, therefore it should + * subclass the [TypeOnlyCommandData]. + * - You can use the [requireSingleCommand] function to check for the existence of a command which implements a + * specified interface: + * + * final CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.class); + * final Commands commandData = command.getValue(); + * + * To match any command that implements [IOUContract.Commands] + * - We then need conditional logic based on the type of [Command.value], in Java you can do this using an "if-else" statement + * - For each "if", or "elseIf" block, you can check the type of [Command.value]: + * + * if (commandData.equals(new Commands.Issue())) { + * requireThat(require -> {...}) + * } else if (...) {} + * + * - The [requireSingleCommand] function will handle unrecognised types for you (see first unit test). + */ + @Test + public void mustHandleMultipleCommandValues() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new Commands.DummyCommand()); + return tx.failsWith("Required net.corda.samples.obligation.contracts.IOUContract.Commands command"); + }); + l.transaction(tx -> { + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Issue()); + return tx.verifies(); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(TestUtils.CHARLIE.getParty())); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.verifies(); + }); + return null; + }); + } + + /** + * Task 2. + * The transfer transaction should only have one input states and one output states. + * TODO: Add constraints to the contracts code to ensure a transfer transaction has only one input and output states. + * Hint: + * - Look at the contracts code for "Issue". + */ + @Test + public void mustHaveOneInputAndOneOutput() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.input(IOUContract.IOU_CONTRACT_ID, dummyState); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(TestUtils.CHARLIE.getParty())); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("An IOU transfer transaction should only consume one input states."); + }); + l.transaction(tx -> { + tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(TestUtils.CHARLIE.getParty())); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("An IOU transfer transaction should only consume one input states."); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("An IOU transfer transaction should only create one output states."); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(TestUtils.CHARLIE.getParty())); + tx.output(IOUContract.IOU_CONTRACT_ID, dummyState); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith(" An IOU transfer transaction should only create one output states."); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(TestUtils.CHARLIE.getParty())); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()),new IOUContract.Commands.Transfer()); + return tx.verifies(); + }); + return null; + }); + } + + /** + * Task 3. + * TODO: Add a constraint to the contracts code to ensure only the lender property can change when transferring IOUs. + * Hint: + * - You should create a private internal copy constructor, accessible via a copy method on your IOUState. + * - You can then compare a copy of the input to the output with the lender of the output as the lender of the input. + * - You'll need references to the input and output ious. + * - Remember you need to cast the [ContractState]s to [IOUState]s. + * - It's easier to take this approach then check all properties other than the lender haven't changed, including + * the [linearId] and the [contracts]! + */ + @Test + public void onlyTheLenderMayChange() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(1), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("Only the lender property may change."); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.CHARLIE.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("Only the lender property may change."); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(5))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("Only the lender property may change."); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.CHARLIE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.verifies(); + }); + return null; + }); + } + + + /** + * Task 4. + * It is fairly obvious that in a transfer IOU transaction the lender must change. + * TODO: Add a constraint to check the lender has changed in the output IOU. + */ + @Test + public void theLenderMustChange() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("The lender property must change in a transfer."); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.CHARLIE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.verifies(); + }); + return null; + }); + } + + /** + * Task 5. + * All the participants in a transfer IOU transaction must sign. + * TODO: Add a constraint to check the old lender, the new lender and the recipient have signed. + */ + @Test + public void allParticipantsMustSign() { + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.CHARLIE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.CHARLIE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.CHARLIE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.CHARLIE.getPublicKey(), TestUtils.BOB.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.CHARLIE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.MINICORP.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.CHARLIE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey(), TestUtils.MINICORP.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); + }); + l.transaction(tx -> { + tx.input(IOUContract.IOU_CONTRACT_ID, iou); + tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), TestUtils.CHARLIE.getParty(), TestUtils.BOB.getParty(), Currencies.DOLLARS(0))); + tx.command(Arrays.asList(TestUtils.ALICE.getPublicKey(), TestUtils.BOB.getPublicKey(), TestUtils.CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); + return tx.verifies(); + }); + return null; + }); + } + +} diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/state/IOUStateTests.java b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/states/IOUStateTests.java similarity index 87% rename from Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/state/IOUStateTests.java rename to Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/states/IOUStateTests.java index 7b744ef4..732521a4 100644 --- a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/state/IOUStateTests.java +++ b/Advanced/obligation-cordapp/contracts/src/test/java/net/corda/samples/obligation/states/IOUStateTests.java @@ -1,16 +1,13 @@ -package net.corda.samples.state; +package net.corda.samples.obligation.states; +import net.corda.finance.*; import net.corda.core.contracts.*; import net.corda.core.identity.Party; -import net.corda.finance.*; -import static net.corda.samples.TestUtils.*; - - import java.lang.reflect.Field; import java.lang.reflect.Constructor; - -import net.corda.samples.states.IOUState; +import net.corda.samples.obligation.TestUtils; +import org.junit.Assert; import org.junit.Test; import java.util.*; import static org.junit.Assert.*; @@ -26,6 +23,8 @@ * Continue until all the unit tests pass. * Hint: CMD / Ctrl + click on the brown type names in square brackets for that type's definition in the codebase. */ + + public class IOUStateTests { /** @@ -93,8 +92,8 @@ public void hasPaidFieldOfCorrectType() throws NoSuchFieldException { */ @Test public void lenderIsParticipant() { - IOUState iouState = new IOUState(Currencies.POUNDS(0), ALICE.getParty(), BOB.getParty()); - assertNotEquals(iouState.getParticipants().indexOf(ALICE.getParty()), -1); + IOUState iouState = new IOUState(Currencies.POUNDS(0), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + assertNotEquals(iouState.getParticipants().indexOf(TestUtils.ALICE.getParty()), -1); } /** @@ -103,8 +102,8 @@ public void lenderIsParticipant() { */ @Test public void borrowerIsParticipant() { - IOUState iouState = new IOUState(Currencies.POUNDS(0), ALICE.getParty(), BOB.getParty()); - assertNotEquals(iouState.getParticipants().indexOf(BOB.getParty()), -1); + IOUState iouState = new IOUState(Currencies.POUNDS(0), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + assertNotEquals(iouState.getParticipants().indexOf(TestUtils.BOB.getParty()), -1); } /** @@ -121,11 +120,11 @@ public void isLinearState() { /** * Task 8. - * TODO: Override the [LinearState.getLinearId()] method and have it return a value created via your state's constructor. + * TODO: Override the [LinearState.getLinearId()] method and have it return a value created via your states's constructor. * Hint: * - {@link LinearState#getLinearId} must return a [linearId] property of type {@link UniqueIdentifier}. You will need to create * a new instance field. - * - The [linearId] is designed to link all {@link LinearState}s (which represent the state of an + * - The [linearId] is designed to link all {@link LinearState}s (which represent the states of an * agreement at a specific point in time) together. All the {@link LinearState}s with the same [linearId] * represent the complete life-cycle to date of an agreement, asset or shared fact. * - Create a new public constructor that creates an {@link IOUState} with a newly generated [linearId]. @@ -171,13 +170,13 @@ public void checkIOUStateParameterOrdering() throws NoSuchFieldException { * Hint: * - You will need to increase the [IOUState.paid] property by the amount the borrower wishes to pay. * - Add a new function called [pay] in {@link IOUState}. This function will need to return an {@link IOUState}. - * - The existing state is immutable, so a new state must be created from the existing state. As this change represents + * - The existing states is immutable, so a new states must be created from the existing states. As this change represents * an update in the lifecycle of an asset, it should share the same [linearId]. To enforce this distinction between - * updating vs creating a new state, make the default constructor private, to be used as a copy constructor. + * updating vs creating a new states, make the default constructor private, to be used as a copy constructor. */ @Test public void checkPayHelperMethod() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); assertEquals(Currencies.DOLLARS(5), iou.pay(Currencies.DOLLARS(5)).getPaid()); assertEquals(Currencies.DOLLARS(3), iou.pay(Currencies.DOLLARS(1)).pay(Currencies.DOLLARS(2)).getPaid()); assertEquals(Currencies.DOLLARS(10), iou.pay(Currencies.DOLLARS(5)).pay(Currencies.DOLLARS(3)).pay(Currencies.DOLLARS(2)).getPaid()); @@ -190,9 +189,9 @@ public void checkPayHelperMethod() { */ @Test public void checkWithNewLenderHelperMethod() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - assertEquals(MINICORP.getParty(), iou.withNewLender(MINICORP.getParty()).getLender()); - assertEquals(MEGACORP.getParty(), iou.withNewLender(MEGACORP.getParty()).getLender()); + IOUState iou = new IOUState(Currencies.DOLLARS(10), TestUtils.ALICE.getParty(), TestUtils.BOB.getParty()); + Assert.assertEquals(TestUtils.MINICORP.getParty(), iou.withNewLender(TestUtils.MINICORP.getParty()).getLender()); + Assert.assertEquals(TestUtils.MEGACORP.getParty(), iou.withNewLender(TestUtils.MEGACORP.getParty()).getLender()); } /** @@ -215,4 +214,4 @@ public void correctConstructorsExist() { fail("The correct private copy constructor does not exist!"); } } -} \ No newline at end of file +} diff --git a/Advanced/obligation-cordapp/gradle.properties b/Advanced/obligation-cordapp/gradle.properties index 7d7dcd3a..9b808c6d 100644 --- a/Advanced/obligation-cordapp/gradle.properties +++ b/Advanced/obligation-cordapp/gradle.properties @@ -1,3 +1,3 @@ -name=Test -group=com.template -version=0.1 \ No newline at end of file +name=Obligation CorDapp +group=com.obligation +version=1.0 diff --git a/Advanced/obligation-cordapp/gradle/wrapper/gradle-wrapper.properties b/Advanced/obligation-cordapp/gradle/wrapper/gradle-wrapper.properties index 2a2a3a8e..ae01072d 100644 --- a/Advanced/obligation-cordapp/gradle/wrapper/gradle-wrapper.properties +++ b/Advanced/obligation-cordapp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/Advanced/obligation-cordapp/repositories.gradle b/Advanced/obligation-cordapp/repositories.gradle index 2874c2ab..8be7b630 100644 --- a/Advanced/obligation-cordapp/repositories.gradle +++ b/Advanced/obligation-cordapp/repositories.gradle @@ -1,8 +1,8 @@ repositories { mavenLocal() mavenCentral() - jcenter() + maven { url 'https://jitpack.io' } - maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } } diff --git a/Advanced/obligation-cordapp/runWebServer.sh b/Advanced/obligation-cordapp/runWebServer.sh new file mode 100644 index 00000000..a7235465 --- /dev/null +++ b/Advanced/obligation-cordapp/runWebServer.sh @@ -0,0 +1,4 @@ +#!/bin/sh +./gradlew runPartyAServer & +./gradlew runPartyBServer & +./gradlew runPartyCServer & diff --git a/Advanced/obligation-cordapp/settings.gradle b/Advanced/obligation-cordapp/settings.gradle index 2514aca2..2b08bb58 100644 --- a/Advanced/obligation-cordapp/settings.gradle +++ b/Advanced/obligation-cordapp/settings.gradle @@ -1,3 +1,3 @@ include 'workflows' include 'contracts' -include 'clients' \ No newline at end of file +include 'clients' diff --git a/Advanced/obligation-cordapp/workflows/build.gradle b/Advanced/obligation-cordapp/workflows/build.gradle index 5ddcf43a..cc82ae58 100644 --- a/Advanced/obligation-cordapp/workflows/build.gradle +++ b/Advanced/obligation-cordapp/workflows/build.gradle @@ -6,7 +6,7 @@ cordapp { targetPlatformVersion corda_platform_version minimumPlatformVersion corda_platform_version workflow { - name "Template Flows" + name "Obligation CorDapp Flows" vendor "Corda Open Source" licence "Apache License, Version 2.0" versionId 1 @@ -51,10 +51,14 @@ dependencies { // Corda dependencies. cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaCompile "$corda_core_release_group:corda-finance-contracts:$corda_release_version" + cordaCompile "$corda_core_release_group:corda-finance-workflows:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" cordaCompile "$corda_release_group:corda-finance-contracts:$corda_release_version" cordaCompile "$corda_release_group:corda-finance-workflows:$corda_release_version" cordaCompile "$corda_release_group:corda-jackson:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" // CorDapp dependencies. @@ -63,7 +67,6 @@ dependencies { cordapp "$corda_release_group:corda-finance-workflows:$corda_release_version" cordapp("$corda_release_group:corda-confidential-identities:$corda_release_version") - } @@ -71,4 +74,4 @@ dependencies { task integrationTest(type: Test, dependsOn: []) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath -} \ No newline at end of file +} diff --git a/Advanced/obligation-cordapp/workflows/src/integrationTest/java/net/corda/samples/NodeDriver.java b/Advanced/obligation-cordapp/workflows/src/integrationTest/java/net/corda/samples/NodeDriver.java index 0bb26472..df3d4cbe 100644 --- a/Advanced/obligation-cordapp/workflows/src/integrationTest/java/net/corda/samples/NodeDriver.java +++ b/Advanced/obligation-cordapp/workflows/src/integrationTest/java/net/corda/samples/NodeDriver.java @@ -46,4 +46,4 @@ public void nodeTest() { return null; }); } -} \ No newline at end of file +} diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUIssueFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUIssueFlow.java deleted file mode 100644 index 5fe604c4..00000000 --- a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUIssueFlow.java +++ /dev/null @@ -1,132 +0,0 @@ -package net.corda.samples.flows; - -import co.paralleluniverse.fibers.Suspendable; -import java.util.List; -import java.util.stream.Collectors; - -import net.corda.core.contracts.Command; -import net.corda.core.contracts.ContractState; -import net.corda.core.crypto.SecureHash; -import net.corda.core.flows.*; -import net.corda.core.identity.AbstractParty; -import net.corda.core.identity.Party; -import net.corda.core.transactions.SignedTransaction; -import net.corda.core.transactions.TransactionBuilder; -import static net.corda.core.contracts.ContractsDSL.requireThat; -import net.corda.core.utilities.ProgressTracker; - -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.states.IOUState; -import static net.corda.samples.contracts.IOUContract.Commands.*; - -/** - * This is the flow which handles issuance of new IOUs on the ledger. - * Gathering the counterparty's signature is handled by the [CollectSignaturesFlow]. - * Notarisation (if required) and commitment to the ledger is handled by the [FinalityFlow]. - * The flow returns the [SignedTransaction] that was committed to the ledger. - */ -public class IOUIssueFlow { - - @InitiatingFlow - @StartableByRPC - public static class InitiatorFlow extends FlowLogic { - private final IOUState state; - public InitiatorFlow(IOUState state) { - this.state = state; - } - - @Suspendable - @Override - public SignedTransaction call() throws FlowException { - // Step 1. Get a reference to the notary service on our network and our key pair. - // Note: ongoing work to support multiple notary identities is still in progress. - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); - - // Step 2. Create a new issue command. - // Remember that a command is a CommandData object and a list of CompositeKeys - final Command issueCommand = new Command<>( - new Issue(), state.getParticipants() - .stream().map(AbstractParty::getOwningKey) - .collect(Collectors.toList())); - - // Step 3. Create a new TransactionBuilder object. - final TransactionBuilder builder = new TransactionBuilder(notary); - - // Step 4. Add the iou as an output state, as well as a command to the transaction builder. - builder.addOutputState(state, IOUContract.IOU_CONTRACT_ID); - builder.addCommand(issueCommand); - - - // Step 5. Verify and sign it with our KeyPair. - builder.verify(getServiceHub()); - final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); - - - // Step 6. Collect the other party's signature using the SignTransactionFlow. - List otherParties = state.getParticipants() - .stream().map(el -> (Party)el) - .collect(Collectors.toList()); - - otherParties.remove(getOurIdentity()); - - List sessions = otherParties - .stream().map(el -> initiateFlow(el)) - .collect(Collectors.toList()); - - SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); - - // Step 7. Assuming no exceptions, we can now finalise the transaction - return subFlow(new FinalityFlow(stx, sessions)); - } - } - - /** - * This is the flow which signs IOU issuances. - * The signing is handled by the [SignTransactionFlow]. - */ - @InitiatedBy(IOUIssueFlow.InitiatorFlow.class) - public static class ResponderFlow extends FlowLogic { - - private final FlowSession flowSession; - private SecureHash txWeJustSigned; - - public ResponderFlow(FlowSession flowSession){ - this.flowSession = flowSession; - } - - @Suspendable - @Override - public SignedTransaction call() throws FlowException { - - class SignTxFlow extends SignTransactionFlow { - - private SignTxFlow(FlowSession flowSession, ProgressTracker progressTracker) { - super(flowSession, progressTracker); - } - - @Override - protected void checkTransaction(SignedTransaction stx) { - requireThat(req -> { - ContractState output = stx.getTx().getOutputs().get(0).getData(); - req.using("This must be an IOU transaction", output instanceof IOUState); - return null; - }); - // Once the transaction has verified, initialize txWeJustSignedID variable. - txWeJustSigned = stx.getId(); - } - } - - flowSession.getCounterpartyFlowInfo().getFlowVersion(); - - // Create a sign transaction flow - SignTxFlow signTxFlow = new SignTxFlow(flowSession, SignTransactionFlow.Companion.tracker()); - - // Run the sign transaction flow to sign the transaction - subFlow(signTxFlow); - - // Run the ReceiveFinalityFlow to finalize the transaction and persist it to the vault. - return subFlow(new ReceiveFinalityFlow(flowSession, txWeJustSigned)); - - } - } -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/SelfIssueCashFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/SelfIssueCashFlow.java deleted file mode 100644 index ce1b1011..00000000 --- a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/SelfIssueCashFlow.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.corda.samples.flows; - -import co.paralleluniverse.fibers.Suspendable; -import net.corda.core.contracts.Amount; -import net.corda.core.flows.*; -import net.corda.core.identity.Party; -import net.corda.core.transactions.SignedTransaction; -import net.corda.core.utilities.OpaqueBytes; -import net.corda.finance.contracts.asset.Cash; -import net.corda.finance.flows.CashIssueFlow; -import java.util.Currency; - -@InitiatingFlow -@StartableByRPC -public class SelfIssueCashFlow extends FlowLogic { - - private Amount amount; - - public SelfIssueCashFlow(Amount amount) { - this.amount = amount; - } - - @Suspendable - public Cash.State call() throws FlowException { - /** Create the cash issue command. */ - OpaqueBytes issueRef = OpaqueBytes.of("1".getBytes()); - /** Note: ongoing work to support multiple notary identities is still in progress. */ - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); - /** Create the cash issuance transaction. */ - SignedTransaction cashIssueTransaction = subFlow(new CashIssueFlow(amount, issueRef, notary)).getStx(); - /** Return the cash output. */ - return (Cash.State) cashIssueTransaction.getTx().getOutputs().get(0).getData(); - } - -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUIssueFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUIssueFlow.java new file mode 100644 index 00000000..9a608cf3 --- /dev/null +++ b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUIssueFlow.java @@ -0,0 +1,133 @@ +package net.corda.samples.obligation.flows; + +import co.paralleluniverse.fibers.Suspendable; +import java.util.List; +import java.util.stream.Collectors; + +import net.corda.core.contracts.Command; +import net.corda.core.contracts.ContractState; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import static net.corda.core.contracts.ContractsDSL.requireThat; +import net.corda.core.utilities.ProgressTracker; + +import net.corda.samples.obligation.contracts.IOUContract; +import net.corda.samples.obligation.states.IOUState; +import static net.corda.samples.obligation.contracts.IOUContract.Commands.*; +import net.corda.core.identity.CordaX500Name; + +/** + * This is the flows which handles issuance of new IOUs on the ledger. + * Gathering the counterparty's signature is handled by the [CollectSignaturesFlow]. + * Notarisation (if required) and commitment to the ledger is handled by the [FinalityFlow]. + * The flows returns the [SignedTransaction] that was committed to the ledger. + */ +public class IOUIssueFlow { + + @InitiatingFlow + @StartableByRPC + public static class InitiatorFlow extends FlowLogic { + private final IOUState state; + public InitiatorFlow(IOUState state) { + this.state = state; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + // Step 2. Create a new issue command. + // Remember that a command is a CommandData object and a list of CompositeKeys + final Command issueCommand = new Command<>( + new Issue(), state.getParticipants() + .stream().map(AbstractParty::getOwningKey) + .collect(Collectors.toList())); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the iou as an output states, as well as a command to the transaction builder. + builder.addOutputState(state, IOUContract.IOU_CONTRACT_ID); + builder.addCommand(issueCommand); + + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + + // Step 6. Collect the other party's signature using the SignTransactionFlow. + List otherParties = state.getParticipants() + .stream().map(el -> (Party)el) + .collect(Collectors.toList()); + + otherParties.remove(getOurIdentity()); + + List sessions = otherParties + .stream().map(el -> initiateFlow(el)) + .collect(Collectors.toList()); + + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, sessions)); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, sessions)); + } + } + + /** + * This is the flows which signs IOU issuances. + * The signing is handled by the [SignTransactionFlow]. + */ + @InitiatedBy(IOUIssueFlow.InitiatorFlow.class) + public static class ResponderFlow extends FlowLogic { + + private final FlowSession flowSession; + private SecureHash txWeJustSigned; + + public ResponderFlow(FlowSession flowSession){ + this.flowSession = flowSession; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + + class SignTxFlow extends SignTransactionFlow { + + private SignTxFlow(FlowSession flowSession, ProgressTracker progressTracker) { + super(flowSession, progressTracker); + } + + @Override + protected void checkTransaction(SignedTransaction stx) { + requireThat(req -> { + ContractState output = stx.getTx().getOutputs().get(0).getData(); + req.using("This must be an IOU transaction", output instanceof IOUState); + return null; + }); + // Once the transaction has verified, initialize txWeJustSignedID variable. + txWeJustSigned = stx.getId(); + } + } + + flowSession.getCounterpartyFlowInfo().getFlowVersion(); + + // Create a sign transaction flows + SignTxFlow signTxFlow = new SignTxFlow(flowSession, SignTransactionFlow.Companion.tracker()); + + // Run the sign transaction flows to sign the transaction + subFlow(signTxFlow); + + // Run the ReceiveFinalityFlow to finalize the transaction and persist it to the vault. + return subFlow(new ReceiveFinalityFlow(flowSession, txWeJustSigned)); + + } + } +} diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUSettleFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUSettleFlow.java similarity index 81% rename from Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUSettleFlow.java rename to Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUSettleFlow.java index 30b6822c..714a0d3e 100644 --- a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUSettleFlow.java +++ b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUSettleFlow.java @@ -1,4 +1,4 @@ -package net.corda.samples.flows; +package net.corda.samples.obligation.flows; import co.paralleluniverse.fibers.Suspendable; import net.corda.confidential.IdentitySyncFlow; @@ -16,8 +16,8 @@ import net.corda.finance.flows.AbstractCashFlow; import net.corda.finance.flows.CashIssueFlow; import net.corda.finance.workflows.asset.CashUtils; -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.states.IOUState; +import net.corda.samples.obligation.contracts.IOUContract; +import net.corda.samples.obligation.states.IOUState; import java.lang.IllegalArgumentException; import java.security.PublicKey; @@ -32,10 +32,10 @@ public class IOUSettleFlow { /** - * This is the flow which handles the settlement (partial or complete) of existing IOUs on the ledger. + * This is the flows which handles the settlement (partial or complete) of existing IOUs on the ledger. * Gathering the counterparty's signature is handled by the [CollectSignaturesFlow]. * Notarisation (if required) and commitment to the ledger is handled by the [FinalityFlow]. - * The flow returns the [SignedTransaction] that was committed to the ledger. + * The flows returns the [SignedTransaction] that was committed to the ledger. */ @InitiatingFlow @StartableByRPC @@ -61,12 +61,15 @@ public SignedTransaction call() throws FlowException { IOUState inputStateToSettle = (IOUState) ((StateAndRef) results.getStates().get(0)).getState().getData(); Party counterparty = inputStateToSettle.lender; - // Step 2. Check the party running this flow is the borrower. + // Step 2. Check the party running this flows is the borrower. if (!inputStateToSettle.borrower.getOwningKey().equals(getOurIdentity().getOwningKey())) { - throw new IllegalArgumentException("The borrower must issue the flow"); + throw new IllegalArgumentException("The borrower must issue the flows"); } // Step 3. Create a transaction builder. - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + // Obtain a reference to a notary we wish to use. + Party notary = inputStateAndRefToSettle.getState().getNotary(); + TransactionBuilder tb = new TransactionBuilder(notary); // Step 4. Check we have enough cash to settle the requested amount. @@ -82,7 +85,7 @@ public SignedTransaction call() throws FlowException { // generateSpend returns all public keys which have to be used to sign transaction List keyList = CashUtils.generateSpend(getServiceHub(), tb, amount, getOurIdentityAndCert(), counterparty).getSecond(); - // Step 6. Add the IOU input state and settle command to the transaction builder. + // Step 6. Add the IOU input states and settle command to the transaction builder. Command command = new Command<>( new IOUContract.Commands.Settle(), @@ -91,7 +94,7 @@ public SignedTransaction call() throws FlowException { tb.addCommand(command); tb.addInputState(inputStateAndRefToSettle); - // Step 7. Only add an output IOU state of the IOU has not been fully settled. + // Step 7. Only add an output IOU states of the IOU has not been fully settled. if (amount.getQuantity() < inputStateToSettle.amount.getQuantity()) { tb.addOutputState(inputStateToSettle.pay(amount), IOUContract.IOU_CONTRACT_ID); } @@ -117,7 +120,7 @@ public SignedTransaction call() throws FlowException { } /** - * This is the flow which signs IOU settlements. + * This is the flows which signs IOU settlements. * The signing is handled by the [SignTransactionFlow]. */ @InitiatedBy(IOUSettleFlow.InitiatorFlow.class) @@ -145,10 +148,10 @@ protected void checkTransaction(SignedTransaction stx) { } } - // Create a sign transaction flow + // Create a sign transaction flows SignTxFlow signTxFlow = new SignTxFlow(otherPartyFlow, SignTransactionFlow.Companion.tracker()); - // Run the sign transaction flow to sign the transaction + // Run the sign transaction flows to sign the transaction subFlow(signTxFlow); // Run the ReceiveFinalityFlow to finalize the transaction and persist it to the vault. @@ -177,8 +180,16 @@ public static class SelfIssueCashFlow extends FlowLogic { public Cash.State call() throws FlowException { // Create the cash issue command. OpaqueBytes issueRef = OpaqueBytes.of(new byte[0]); - // Note: ongoing work to support multiple notary identities is still in progress. */ - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + // Obtain a reference to a notary we wish to use. + /** METHOD 1: Take first notary on network, WARNING: use for test, non-prod environments, and single-notary networks only!* + * METHOD 2: Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred) + * + * * - For production you always want to use Method 2 as it guarantees the expected notary is returned. + */ + final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // METHOD 1 + // final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // METHOD 2 + // Create the cash issuance transaction. AbstractCashFlow.Result cashIssueTransaction = subFlow(new CashIssueFlow(amount, issueRef, notary)); return (Cash.State) cashIssueTransaction.getStx().getTx().getOutput(0); @@ -186,4 +197,4 @@ public Cash.State call() throws FlowException { } -} \ No newline at end of file +} diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUTransferFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUTransferFlow.java similarity index 86% rename from Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUTransferFlow.java rename to Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUTransferFlow.java index a07cee5d..4186aabb 100644 --- a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/flows/IOUTransferFlow.java +++ b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/IOUTransferFlow.java @@ -1,4 +1,4 @@ -package net.corda.samples.flows; +package net.corda.samples.obligation.flows; import co.paralleluniverse.fibers.Suspendable; import net.corda.core.contracts.Command; @@ -7,7 +7,7 @@ import net.corda.core.crypto.SecureHash; import net.corda.core.flows.*; import net.corda.core.utilities.ProgressTracker; -import net.corda.samples.contracts.IOUContract.Commands.Transfer; +import net.corda.samples.obligation.contracts.IOUContract.Commands.Transfer; import net.corda.core.contracts.UniqueIdentifier; import net.corda.core.identity.AbstractParty; import net.corda.core.identity.Party; @@ -15,8 +15,8 @@ import net.corda.core.node.services.vault.QueryCriteria; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.states.IOUState; +import net.corda.samples.obligation.contracts.IOUContract; +import net.corda.samples.obligation.states.IOUState; import org.jetbrains.annotations.NotNull; import java.security.PublicKey; @@ -29,10 +29,10 @@ /** - * This is the flow which handles transfers of existing IOUs on the ledger. + * This is the flows which handles transfers of existing IOUs on the ledger. * Gathering the counterparty's signature is handled by the [CollectSignaturesFlow]. * Notarisation (if required) and commitment to the ledger is handled by the [FinalityFlow]. - * The flow returns the [SignedTransaction] that was committed to the ledger. + * The flows returns the [SignedTransaction] that was committed to the ledger. */ public class IOUTransferFlow{ @@ -64,7 +64,10 @@ public SignedTransaction call() throws FlowException { // 3. We should now get some of the components required for to execute the transaction // Here we get a reference to the default notary and instantiate a transaction builder. - Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + + // Obtain a reference to a notary we wish to use. + Party notary = inputStateAndRefToTransfer.getState().getNotary(); + TransactionBuilder tb = new TransactionBuilder(notary); // 4. Construct a transfer command to be added to the transaction. @@ -81,13 +84,13 @@ public SignedTransaction call() throws FlowException { // 5. Add the command to the transaction using the TransactionBuilder. tb.addCommand(command); - // 6. Add input and output states to flow using the TransactionBuilder. + // 6. Add input and output states to flows using the TransactionBuilder. tb.addInputState(inputStateAndRefToTransfer); tb.addOutputState(inputStateToTransfer.withNewLender(newLender), IOUContract.IOU_CONTRACT_ID); - // 7. Ensure that this flow is being executed by the current lender. + // 7. Ensure that this flows is being executed by the current lender. if (!inputStateToTransfer.lender.getOwningKey().equals(getOurIdentity().getOwningKey())) { - throw new IllegalArgumentException("This flow must be run by the current lender."); + throw new IllegalArgumentException("This flows must be run by the current lender."); } // 8. Verify and sign the transaction @@ -114,7 +117,7 @@ public SignedTransaction call() throws FlowException { /** - * This is the flow which signs IOU settlements. + * This is the flows which signs IOU settlements. * The signing is handled by the [SignTransactionFlow]. */ @InitiatedBy(IOUTransferFlow.InitiatorFlow.class) @@ -148,10 +151,10 @@ protected void checkTransaction(SignedTransaction stx) { } } - // Create a sign transaction flow + // Create a sign transaction flows SignTxFlow signTxFlow = new SignTxFlow(otherPartyFlow, SignTransactionFlow.Companion.tracker()); - // Run the sign transaction flow to sign the transaction + // Run the sign transaction flows to sign the transaction subFlow(signTxFlow); // Run the ReceiveFinalityFlow to finalize the transaction and persist it to the vault. @@ -160,4 +163,4 @@ protected void checkTransaction(SignedTransaction stx) { } -} \ No newline at end of file +} diff --git a/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/SelfIssueCashFlow.java b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/SelfIssueCashFlow.java new file mode 100644 index 00000000..2f0094dc --- /dev/null +++ b/Advanced/obligation-cordapp/workflows/src/main/java/net/corda/samples/obligation/flows/SelfIssueCashFlow.java @@ -0,0 +1,39 @@ +package net.corda.samples.obligation.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.Amount; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.utilities.OpaqueBytes; +import net.corda.finance.contracts.asset.Cash; +import net.corda.finance.flows.CashIssueFlow; +import java.util.Currency; +import net.corda.core.identity.CordaX500Name; + +@InitiatingFlow +@StartableByRPC +public class SelfIssueCashFlow extends FlowLogic { + + private Amount amount; + + public SelfIssueCashFlow(Amount amount) { + this.amount = amount; + } + + @Suspendable + public Cash.State call() throws FlowException { + /** Create the cash issue command. */ + OpaqueBytes issueRef = OpaqueBytes.of("1".getBytes()); + + // Obtain a reference to a notary we wish to use. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + /** Create the cash issuance transaction. */ + SignedTransaction cashIssueTransaction = subFlow(new CashIssueFlow(amount, issueRef, notary)).getStx(); + /** Return the cash output. */ + return (Cash.State) cashIssueTransaction.getTx().getOutputs().get(0).getData(); + } + +} diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/TestUtils.java b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/TestUtils.java deleted file mode 100644 index e74eb8c9..00000000 --- a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/TestUtils.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.corda.samples; - -import net.corda.core.identity.CordaX500Name; -import net.corda.testing.core.TestIdentity; - -public class TestUtils { - - public static TestIdentity ALICE = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); - public static TestIdentity BOB = new TestIdentity(new CordaX500Name("Bob", "TestCity", "US")); - public static TestIdentity CHARLIE = new TestIdentity(new CordaX500Name("Charlie", "TestVillage", "US")); - public static TestIdentity MINICORP = new TestIdentity(new CordaX500Name("MiniCorp", "MiniLand", "US")); - public static TestIdentity MEGACORP = new TestIdentity(new CordaX500Name("MegaCorp", "MiniLand", "US")); - public static TestIdentity DUMMY = new TestIdentity(new CordaX500Name("Dummy", "FakeLand", "US")); -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUIssueTests.java b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUIssueTests.java deleted file mode 100644 index 61396eb4..00000000 --- a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUIssueTests.java +++ /dev/null @@ -1,287 +0,0 @@ -package net.corda.samples.contract; - - -import net.corda.core.contracts.*; -import net.corda.finance.*; -import net.corda.testing.contracts.DummyState; -import net.corda.testing.node.MockServices; - -import static net.corda.testing.node.NodeTestUtils.ledger; -import net.corda.core.transactions.LedgerTransaction; - -import static net.corda.samples.TestUtils.*; - -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.state.IOUStateTests; - -import java.util.Arrays; - -import net.corda.samples.states.IOUState; -import org.junit.*; - - -/** - * Practical exercise instructions for Contracts Part 1. - * The objective here is to write some contract code that verifies a transaction to issue an {@link IOUState}. - * As with the {@link IOUStateTests} uncomment each unit test and run them one at a time. Use the body of the tests and the - * task description to determine how to get the tests to pass. - */ -public class IOUIssueTests { - // A pre-defined dummy command. - public interface Commands extends CommandData { - class DummyCommand extends TypeOnlyCommandData implements Commands{} - } - - static private final MockServices ledgerServices = new MockServices( - Arrays.asList("net.corda.samples", "net.corda.finance.contracts") - ); - - /** - * Task 1. - * Recall that Commands are required to hint to the intention of the transaction as well as take a list of - * public keys as parameters which correspond to the required signers for the transaction. - * Commands also become more important later on when multiple actions are possible with an IOUState, e.g. Transfer - * and Settle. - * TODO: Add an "Issue" command to the IOUContract and check for the existence of the command in the verify function. - * Hint: - * - For the Issue command we only care about the existence of it in a transaction, therefore it should extend - * the {@link TypeOnlyCommandData} class. - * - The command should be defined inside {@link IOUContract}. - * - We usually encapsulate our commands in an interface inside the contract class called {@link Commands} which - * extends the {@link CommandData} interface. The Issue command itself should be defined inside the {@link Commands} - * interface as well as implement it, for example: - * - * public interface Commands extends CommandData { - * class X extends TypeOnlyCommandData implements Commands{} - * } - * - * - We can check for the existence of any command that implements [IOUContract.Commands] by using the - * [requireSingleCommand] function which takes a {@link Class} argument. - * - You can use the [requireSingleCommand] function on [tx.getCommands()] to check for the existence and type of the specified command - * in the transaction. [requireSingleCommand] requires a Class argument to identify the type of command required. - * - * requireSingleCommand(tx.getCommands(), REQUIRED_COMMAND.class) - */ - @Test - public void mustIncludeIssueCommand() { - IOUState iou = new IOUState(Currencies.POUNDS(1), ALICE.getParty(), BOB.getParty()); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new Commands.DummyCommand()); // Wrong type. - return tx.failsWith("Contract verification failed"); - }); - l.transaction(tx -> { - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); // Correct type. - return tx.verifies(); - }); - return null; - }); - } - - /** - * Task 2. - * As previously observed, issue transactions should not have any input state references. Therefore we must check to - * ensure that no input states are included in a transaction to issue an IOU. - * TODO: Write a contract constraint that ensures a transaction to issue an IOU does not include any input states. - * Hint: use a [requireThat] lambda with a constraint to inside the [IOUContract.verify] function to encapsulate your - * constraints: - * - * requireThat(requirement -> { - * requirement.using("Message when constraint fails", (boolean constraining expression)); - * // passes all cases - * return null; - * }); - * - * Note that the unit tests often expect contract verification failure with a specific message which should be - * defined with your contract constraints. If not then the unit test will fail! - * - * You can access the list of inputs via the {@link LedgerTransaction} object which is passed into - * [IOUContract.verify]. - */ - @Test - public void issueTransactionMustHaveNoInputs() { - IOUState iou = new IOUState(Currencies.POUNDS(1), ALICE.getParty(), BOB.getParty()); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, new DummyState()); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.failsWith("No inputs should be consumed when issuing an IOU"); - }); - l.transaction(tx -> { - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - return tx.verifies(); // As there are no input sates - }); - return null; - }); - } - - /** - * Task 3. - * Now we need to ensure that only one {@link IOUState} is issued per transaction. - * TODO: Write a contract constraint that ensures only one output state is created in a transaction. - * Hint: Write an additional constraint within the existing [requireThat] block which you created in the previous - * task. - */ - @Test - public void issueTransactionMustHaveOneOutput() { - IOUState iou = new IOUState(Currencies.POUNDS(1), ALICE.getParty(), BOB.getParty()); - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); // Two outputs fails. - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.failsWith("Only one output state should be created when issuing an IOU."); - }); - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); // One output passes. - return tx.verifies(); - }); - return null; - }); - } - - /** - * Task 4. - * Now we need to consider the properties of the {@link IOUState}. We need to ensure that an IOU should always have a - * positive value. - * TODO: Write a contract constraint that ensures newly issued IOUs always have a positive value. - * Hint: You will need a number of hints to complete this task! - * - Create a new constant which will hold a reference to the output IOU state. - * - We need to obtain a reference to the proposed IOU for issuance from the [LedgerTransaction.getOutputStates()] list - * - You can use the function [get(0)] to grab the single element from the list. - * This list is typed as a list of {@link ContractState}s, therefore we need to cast the {@link ContractState} which we return - * to an {@link IOUState}. E.g. - * - * XState state = (XState)tx.getOutputStates().get(0) - * - * - When checking the [IOUState.getAmount()] property is greater than zero, you need to check the - * [IOUState.getAmount().getQuantity()] field. - */ - @Test - public void cannotCreateZeroValueIOUs() { - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, new IOUState(Currencies.POUNDS(0), ALICE.getParty(), BOB.getParty())); // Zero amount fails. - return tx.failsWith("A newly issued IOU must have a positive amount."); - }); - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, new IOUState(Currencies.SWISS_FRANCS(100), ALICE.getParty(), BOB.getParty())); - return tx.verifies(); - }); - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, new IOUState(Currencies.POUNDS(1), ALICE.getParty(), BOB.getParty())); - return tx.verifies(); - }); - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty())); - return tx.verifies(); - }); - return null; - }); - } - - /** - * Task 5. - * For obvious reasons, the identity of the lender and borrower must be different. - * TODO: Add a contract constraint to check the lender is not the borrower. - * Hint: - * - You can use the [IOUState.getLender()] and [IOUState.getBorrower()] properties. - * - This check must be made before the checking who has signed. - */ - @Test - public void lenderAndBorrowerCannotBeTheSame() { - IOUState iou = new IOUState(Currencies.POUNDS(1), ALICE.getParty(), BOB.getParty()); - IOUState borrowerIsLenderIou = new IOUState(Currencies.POUNDS(10), ALICE.getParty(), ALICE.getParty()); - ledger(ledgerServices, l-> { - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, borrowerIsLenderIou); - return tx.failsWith("The lender and borrower cannot have the same identity."); - }); - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.verifies(); - }); - return null; - }); - } - - /** - * Task 6. - * The list of public keys which the commands hold should contain all of the participants defined in the {@link IOUState}. - * This is because the IOU is a bilateral agreement where both parties involved are required to sign to issue an - * IOU or change the properties of an existing IOU. - * TODO: Add a contract constraint to check that all the required signers are {@link IOUState} participants. - * Hint: - * - In Java, you can perform a set equality check of two sets with the .equals() - * - We need to check that the signers for the Command of this transaction equals the participants list. - * - We don't want any additional public keys not listed in the IOUs participants list. - * - You will need a reference to the Issue command to get access to the list of signers. - * - [requireSingleCommand] returns the single required [CommandWithParties] - you can assign the return - * value to a constant. - * - Next, you will need to retrieve the participants of the output state and ensure they are equal them. - * - * Java Hints - * - Java's map function allows for conversion of a Collection. However, it requires a Stream object (created by - * calling collection.stream()), which must then - * be converted back into a Collection using collect(Collectors.toCOLLECTION_TYPE). All together, this looks like: - * collection.stream().map(element -> (some operation on element)).collect(Collectors.toCOLLECTION_TYPE) - * This will be needed for mapping the List from getParticipants() to a List - * - https://zeroturnaround.com/rebellabs/java-8-explained-applying-lambdas-to-java-collections/ - * - A Collection can be turned into a set using: new HashSet<>(collection) -// */ - @Test - public void lenderAndBorrowerMustSignIssueTransaction() { - IOUState iou = new IOUState(Currencies.POUNDS(1), ALICE.getParty(), BOB.getParty()); - ledger(ledgerServices, l->{ - l.transaction(tx-> { - tx.command(DUMMY.getPublicKey(), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); - }); - l.transaction(tx-> { - tx.command(ALICE.getPublicKey(), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); - }); - l.transaction(tx-> { - tx.command(BOB.getPublicKey(), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); - }); - l.transaction(tx-> { - tx.command(Arrays.asList(BOB.getPublicKey(), BOB.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); - }); - l.transaction(tx-> { - tx.command(Arrays.asList(BOB.getPublicKey(), BOB.getPublicKey(), MINICORP.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.failsWith("Both lender and borrower together only may sign IOU issue transaction."); - }); - l.transaction(tx-> { - tx.command(Arrays.asList(BOB.getPublicKey(), BOB.getPublicKey(), BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.verifies(); - }); - l.transaction(tx-> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - return tx.verifies(); - }); - return null; - }); - } -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUSettleTests.java b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUSettleTests.java deleted file mode 100644 index 7563712d..00000000 --- a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUSettleTests.java +++ /dev/null @@ -1,518 +0,0 @@ -package net.corda.samples.contract; - -import net.corda.core.contracts.*; -import net.corda.core.contracts.Amount; -import net.corda.core.contracts.CommandData; -import net.corda.core.contracts.PartyAndReference; -import net.corda.core.contracts.TypeOnlyCommandData; -import net.corda.core.identity.AbstractParty; -import net.corda.core.utilities.OpaqueBytes; -import net.corda.finance.Currencies; -import net.corda.finance.contracts.asset.Cash; -import net.corda.testing.node.MockServices; -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.states.IOUState; -import org.junit.Test; - -import java.util.Arrays; -import java.util.Currency; - -import static net.corda.testing.node.NodeTestUtils.ledger; -import static net.corda.samples.TestUtils.BOB; -import static net.corda.samples.TestUtils.ALICE; -import static net.corda.samples.TestUtils.CHARLIE; - - -/** - * Practical exercise instructions for Contracts Part 3. - * The objective here is to write some contract code that verifies a transaction to settle an [IOUState]. - * Settling is more complicated than transferring and issuing as it requires you to use multiple state types in a - * transaction. - * As with the [IOUIssueTests] and [IOUTransferTests] uncomment each unit test and run them one at a time. Use the body - * of the tests and the task description to determine how to get the tests to pass. - */ - -public class IOUSettleTests { - - public interface Commands extends CommandData { - class DummyCommand extends TypeOnlyCommandData implements Commands{} - } - - static private final MockServices ledgerServices = new MockServices( - Arrays.asList("net.corda.samples.contracts", "net.corda.finance.contracts") - ); - - private Cash.State createCashState(AbstractParty owner, Amount amount) { - OpaqueBytes defaultBytes = new OpaqueBytes(new byte[1]); - PartyAndReference partyAndReference = new PartyAndReference(owner, defaultBytes); - return new Cash.State(partyAndReference, amount, owner); - } - - /** - * Task 1. - * We need to add another case to deal with settling in the [IOUContract.verify] function. - * TODO: Add the [IOUContract.Commands.Settle] case to the verify function. - * Hint: You can leave the body empty for now. - */ - @Test - public void mustIncludeSettleCommand() { - IOUState iou = new IOUState(Currencies.POUNDS(10), ALICE.getParty(), BOB.getParty()); - Cash.State inputCash = createCashState(BOB.getParty(), Currencies.POUNDS(5)); - OwnableState outputCash = inputCash.withNewOwner(ALICE.getParty()).getOwnableState(); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); - tx.input(Cash.class.getName(), inputCash); - tx.output(Cash.class.getName(), outputCash); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - return tx.failsWith("Contract Verification Failed"); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); - tx.input(Cash.class.getName(), inputCash); - tx.output(Cash.class.getName(), outputCash); - tx.command(BOB.getPublicKey(), new Commands.DummyCommand()); - return tx.failsWith("Contract verification failed"); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); - tx.input(Cash.class.getName(), inputCash); - tx.output(Cash.class.getName(), outputCash); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Settle()); - return tx.verifies(); - }); - return null; - }); - } - - /** - * Task 2. - * For now, we only want to settle one IOU at once. We can use the [TransactionForContract.groupStates] function - * to group the IOUs by their [linearId] property. We want to make sure there is only one group of input and output - * IOUs. - * TODO: Using [groupStates] add a constraint that checks for one group of input/output IOUs. - * Hint: - * - The [groupStates] method on a Transaction takes two type parameters: the type of the state you wish to group by and the type - * of the grouping key used (indicated by a method reference), in this case as you need to use the [linearId] and it is a [UniqueIdentifier]. - * - * tx.groupStates(State.class, State::getLinearId) - * - */ - @Test - public void mustBeOneGroupOfIOUs() { - IOUState iouONE = new IOUState(Currencies.POUNDS(10), ALICE.getParty(), BOB.getParty()); - IOUState iouTWO = new IOUState(Currencies.POUNDS(5), ALICE.getParty(), BOB.getParty()); - Cash.State inputCash = createCashState(BOB.getParty(), Currencies.POUNDS(5)); - CommandAndState outputCash = inputCash.withNewOwner(ALICE.getParty()); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iouONE); - tx.input(IOUContract.IOU_CONTRACT_ID, iouTWO); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Settle()); - tx.output(IOUContract.IOU_CONTRACT_ID, iouONE.pay(Currencies.POUNDS(5))); - tx.input(Cash.class.getName(), inputCash); - tx.output(Cash.class.getName(), outputCash.getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.failsWith("List has more than one element."); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iouONE); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Settle()); - tx.output(IOUContract.IOU_CONTRACT_ID, iouONE.pay(Currencies.POUNDS(5))); - tx.input(Cash.class.getName(), inputCash); - tx.output(Cash.class.getName(), outputCash.getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.verifies(); - return null; - }); - return null; - }); - } - - /** - * Task 3. - * There always has to be one input IOU in a settle transaction but there might not be an output IOU. - * TODO: Add a constraint to check there is always one input IOU. - */ - - @Test - public void mustHaveOneInputIOU() { - - IOUState iou = new IOUState(Currencies.POUNDS(10), ALICE.getParty(), BOB.getParty()); - IOUState iouOne = new IOUState(Currencies.POUNDS(10), ALICE.getParty(), BOB.getParty()); - Cash.State tenPounds = createCashState( BOB.getParty(), Currencies.POUNDS(10)); - Cash.State fivePounds = createCashState( BOB.getParty(), Currencies.POUNDS(5)); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Settle()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - tx.failsWith("There must be one input IOU."); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Settle()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), fivePounds); - tx.output(Cash.class.getName(), fivePounds.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.verifies(); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iouOne); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Settle()); - tx.input(Cash.class.getName(), tenPounds); - tx.output(Cash.class.getName(), tenPounds.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.verifies(); - return null; - }); - return null; - }); - - } - - /** - * Task 4. - * Now we need to ensure that there are cash states present in the outputs list. The [IOUContract] doesn't care - * about input cash as the validity of the cash transaction will be checked by the [Cash] contract. We do however - * need to count how much cash is being used to settle and update our [IOUState] accordingly. - * TODO: Filter out the cash states from the list of outputs list and assign them to a constant. - * Hint: - * - Use the [outputsOfType] extension function to filter the transaction's outputs by type, in this case [Cash.State]. - */ - @Test - public void mustBeCashOutputStatesPresent() { - - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - Cash.State cash = createCashState(BOB.getParty(), Currencies.DOLLARS(5)); - CommandAndState cashPayment = cash.withNewOwner(ALICE.getParty()); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("There must be output cash."); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), cash); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); - tx.output(Cash.class.getName(), cashPayment.getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Settle()); - tx.verifies(); - return null; - }); - return null; - }); - - } - - /** - * Task 5. - * Not only to we need to check that [Cash] output states are present but we need to check that the payer is - * correctly assigning the lender as the new owner of these states. - * TODO: Add a constraint to check that the lender is the new owner of at least some output cash. - * Hint: - * - Not all of the cash may be assigned to the lender as some of the input cash may be sent back to the borrower as change. - * - We need to use the [Cash.State.getOwner()] method to check to see that it is the value of our public key. - * - Use [filter] to filter over the list of cash states to get the ones which are being assigned to us. - * - Once we have this filtered list, we can sum the cash being paid to us so we know how much is being settled. - */ - @Test - public void mustBeCashOutputStatesWithRecipientAsOwner() { - IOUState iou = new IOUState(Currencies.POUNDS(10), ALICE.getParty(), BOB.getParty()); - Cash.State cash = createCashState(BOB.getParty(), Currencies.POUNDS(5)); - CommandAndState invalidCashPayment = cash.withNewOwner(CHARLIE.getParty()); - CommandAndState validCashPayment = cash.withNewOwner(ALICE.getParty()); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), cash); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); - tx.output(Cash.class.getName(), invalidCashPayment.getOwnableState()); - tx.command(BOB.getPublicKey(), invalidCashPayment.getCommand()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("There must be output cash paid to the recipient."); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), cash); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.POUNDS(5))); - tx.output(Cash.class.getName(), validCashPayment.getOwnableState()); - tx.command(BOB.getPublicKey(), validCashPayment.getCommand()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.verifies(); - return null; - }); - return null; - }); - - } - - /** - * Task 6. - * Now we need to sum the cash which is being assigned to the lender and compare this total against how much of the iou is - * left to pay. - * TODO: Add a constraint that checks the lender cannot be paid more than the remaining IOU amount left to pay. - * Hint: - * - The remaining amount of the IOU is the amount less the paid property. - * - To sum a list of [Cash.State]s, collect all [Cash.States] assigned to the lender and use a reduce method or - * = explicitly loop through the list to sum the individual states. - * - We can compare the amount left paid to the amount being paid to use, ensuring the amount being paid isn't too much. - */ - @Test - public void cashSettlementAmountMustBeLessThanRemainingIOUAmount() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - Cash.State elevenDollars = createCashState( BOB.getParty(), Currencies.DOLLARS(11)); - Cash.State tenDollars = createCashState( BOB.getParty(), Currencies.DOLLARS(10)); - Cash.State fiveDollars = createCashState( BOB.getParty(), Currencies.DOLLARS(5)); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), elevenDollars); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(11))); - tx.output(Cash.class.getName(), elevenDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("The amount settled cannot be more than the amount outstanding."); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), fiveDollars); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); - tx.output(Cash.class.getName(), fiveDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.verifies(); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), tenDollars); - tx.output(Cash.class.getName(), tenDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.verifies(); - return null; - }); - return null; - }); - } - - /** - * Task 7. - * Your Java implementation should handle this for you but it goes without saying that we should only be able to settle - * in the currency that the IOU in denominated in. - * TODO: You shouldn't have anything to do here but here are some tests just to make sure! - */ - @Test - public void cashSettlementMustBeInTheCorrectCurrency() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - Cash.State tenDollars = createCashState( BOB.getParty(), Currencies.DOLLARS(10)); - Cash.State tenPounds = createCashState( BOB.getParty(), Currencies.POUNDS(10)); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), tenPounds); - tx.output(Cash.class.getName(), tenPounds.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("Token mismatch: GBP vs USD"); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), tenDollars); - tx.output(Cash.class.getName(), tenDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.verifies(); - return null; - }); - return null; - }); - } - - /** - * Task 8. - * If we fully settle the IOU, then we are done and thus don't require one on ledgerServices.ledger anymore. However, if we only - * partially settle the IOU, then we want to keep the IOU on ledger with an amended [paid] property. - * TODO: Write a constraint that ensures the correct behaviour depending on the amount settled vs amount remaining. - * Hint: You can use a simple if statement and compare the total amount paid vs amount left to settle. - */ - @Test - public void mustOnlyHaveOutputIOUIfNotFullySettling() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - Cash.State tenDollars = createCashState( BOB.getParty(), Currencies.DOLLARS(10)); - Cash.State fiveDollars = createCashState( BOB.getParty(), Currencies.DOLLARS(5)); - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), fiveDollars); - tx.output(Cash.class.getName(), fiveDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("There must be one output IOU."); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), fiveDollars); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); - tx.output(Cash.class.getName(), fiveDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.verifies(); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), tenDollars); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(10))); - tx.output(Cash.class.getName(), tenDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("There must be no output IOU as it has been fully settled."); - return null; - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), tenDollars); - tx.output(Cash.class.getName(), tenDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.verifies(); - return null; - }); - return null; - }); - } - - /** - * Task 9. - * We want to make sure that the only property of the IOU which changes when we settle, is the paid amount. - * TODO: Write a constraint to check only the paid property of the [IOUState] changes when settling. - */ - @Test - public void onlyPaidPropertyMayChange() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - Cash.State fiveDollars = createCashState( BOB.getParty(), Currencies.DOLLARS(5)); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), fiveDollars); - IOUState iouCopy = iou.copy(iou.amount, iou.lender, CHARLIE.getParty(), iou.paid).pay(Currencies.DOLLARS(5)); - tx.output(IOUContract.IOU_CONTRACT_ID, iouCopy); - tx.output(Cash.class.getName(), fiveDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("The borrower may not change when settling."); - return null; - }); - - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), fiveDollars); - tx.output(Cash.class.getName(), fiveDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - IOUState iouCopy = iou.copy(Currencies.DOLLARS(0), iou.lender, CHARLIE.getParty(), iou.paid).pay(Currencies.DOLLARS(5)); - tx.output(IOUContract.IOU_CONTRACT_ID, iouCopy); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("The amount may not change when settling."); - return null; - }); - - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), fiveDollars); - tx.output(Cash.class.getName(), fiveDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - IOUState iouCopy = iou.copy(iou.amount, CHARLIE.getParty(), iou.borrower, iou.paid).pay(Currencies.DOLLARS(5)); - tx.output(IOUContract.IOU_CONTRACT_ID, iouCopy); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("The lender may not change when settling."); - return null; - }); - - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(Cash.class.getName(), fiveDollars); - tx.output(Cash.class.getName(), fiveDollars.withNewOwner(ALICE.getParty()).getOwnableState()); - IOUState iouCopy = iou.copy(iou.amount, iou.lender, iou.borrower, iou.paid).pay(Currencies.DOLLARS(5)); - tx.output(IOUContract.IOU_CONTRACT_ID, iouCopy); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.verifies(); - return null; - }); - - return null; - }); - - } - - /** - * Task 10. - * Both the lender and the borrower must have signed an IOU issue transaction. - * TODO: Add a constraint to the contract code that ensures this is the case. - */ - public void mustBeSignedByAllParticipants() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - Cash.State cash = createCashState(BOB.getParty(), Currencies.DOLLARS(5)); - CommandAndState cashPayment = cash.withNewOwner(ALICE.getParty()); - - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(Cash.class.getName(), cash); - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(Cash.class.getName(), cashPayment.getOwnableState()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(ALICE.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("Both lender and borrower together only must sign IOU settle transaction."); - return null; - }); - l.transaction(tx -> { - tx.input(Cash.class.getName(), cash); - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(Cash.class.getName(), cashPayment.getOwnableState()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(BOB.getPublicKey(), new IOUContract.Commands.Settle()); - tx.failsWith("Both lender and borrower together only must sign IOU settle transaction."); - return null; - }); - l.transaction(tx -> { - tx.input(Cash.class.getName(), cash); - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(Cash.class.getName(), cashPayment.getOwnableState()); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.pay(Currencies.DOLLARS(5))); - tx.command(BOB.getPublicKey(), new Cash.Commands.Move()); - tx.command(Arrays.asList(BOB.getPublicKey(), ALICE.getPublicKey()), new IOUContract.Commands.Settle()); - tx.failsWith("Both lender and borrower together only must sign IOU settle transaction."); - return null; - }); - return null; - }); - - } -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUTransferTests.java b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUTransferTests.java deleted file mode 100644 index 89ee1bd2..00000000 --- a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/contract/IOUTransferTests.java +++ /dev/null @@ -1,266 +0,0 @@ -package net.corda.samples.contract; - - -import net.corda.core.contracts.Amount; -import net.corda.core.contracts.CommandData; -import net.corda.core.contracts.PartyAndReference; -import net.corda.core.contracts.TypeOnlyCommandData; -import net.corda.core.identity.AbstractParty; -import net.corda.core.utilities.OpaqueBytes; -import net.corda.finance.Currencies; -import net.corda.finance.contracts.asset.Cash; -import net.corda.testing.node.MockServices; -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.states.IOUState; -import org.junit.Test; - -import java.util.Arrays; -import java.util.Currency; - -import static net.corda.testing.node.NodeTestUtils.ledger; -import static net.corda.samples.TestUtils.*; - -/** - * Practical exercise instructions for Contracts Part 2. - * The objective here is to write some contract code that verifies a transaction to issue an [IOUState]. - * As with the [IOUIssueTests] uncomment each unit test and run them one at a time. Use the body of the tests and the - * task description to determine how to get the tests to pass. - */ - -public class IOUTransferTests { - - public interface Commands extends CommandData { - class DummyCommand extends TypeOnlyCommandData implements Commands{} - } - - static private final MockServices ledgerServices = new MockServices( - Arrays.asList("net.corda.samples.contracts", "net.corda.finance.contracts") - ); - - /** - * Uncomment the testing setup below. - */ - // A dummy state - IOUState dummyState = new IOUState(Currencies.DOLLARS(0), CHARLIE.getParty(), CHARLIE.getParty()); - - // function to create new Cash states. - private Cash.State createCashState(AbstractParty owner, Amount amount) { - OpaqueBytes defaultBytes = new OpaqueBytes(new byte[1]); - PartyAndReference partyAndReference = new PartyAndReference(owner, defaultBytes); - return new Cash.State(partyAndReference, amount, owner); - } - - /** - * Task 1. - * Now things are going to get interesting! - * We need the [IOUContract] to not only handle Issues of IOUs but now also Transfers. - * Of course, we'll need to add a new Command and add some additional contract code to handle Transfers. - * TODO: Add a "Transfer" command to the IOUState and update the verify() function to handle multiple commands. - * Hint: - * - As with the [Issue] command, add the [Transfer] command within the [IOUContract.Commands]. - * - Again, we only care about the existence of the [Transfer] command in a transaction, therefore it should - * subclass the [TypeOnlyCommandData]. - * - You can use the [requireSingleCommand] function to check for the existence of a command which implements a - * specified interface: - * - * final CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.class); - * final Commands commandData = command.getValue(); - * - * To match any command that implements [IOUContract.Commands] - * - We then need conditional logic based on the type of [Command.value], in Java you can do this using an "if-else" statement - * - For each "if", or "elseIf" block, you can check the type of [Command.value]: - * - * if (commandData.equals(new Commands.Issue())) { - * requireThat(require -> {...}) - * } else if (...) {} - * - * - The [requireSingleCommand] function will handle unrecognised types for you (see first unit test). - */ - @Test - public void mustHandleMultipleCommandValues() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new Commands.DummyCommand()); - return tx.failsWith("Required net.corda.samples.contracts.IOUContract.Commands command"); - }); - l.transaction(tx -> { - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Issue()); - return tx.verifies(); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(CHARLIE.getParty())); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.verifies(); - }); - return null; - }); - } - - /** - * Task 2. - * The transfer transaction should only have one input state and one output state. - * TODO: Add constraints to the contract code to ensure a transfer transaction has only one input and output state. - * Hint: - * - Look at the contract code for "Issue". - */ - @Test - public void mustHaveOneInputAndOneOutput() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.input(IOUContract.IOU_CONTRACT_ID, dummyState); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(CHARLIE.getParty())); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("An IOU transfer transaction should only consume one input state."); - }); - l.transaction(tx -> { - tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(CHARLIE.getParty())); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("An IOU transfer transaction should only consume one input state."); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("An IOU transfer transaction should only create one output state."); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(CHARLIE.getParty())); - tx.output(IOUContract.IOU_CONTRACT_ID, dummyState); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith(" An IOU transfer transaction should only create one output state."); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.withNewLender(CHARLIE.getParty())); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()),new IOUContract.Commands.Transfer()); - return tx.verifies(); - }); - return null; - }); - } - - /** - * Task 3. - * TODO: Add a constraint to the contract code to ensure only the lender property can change when transferring IOUs. - * Hint: - * - You should create a private internal copy constructor, accessible via a copy method on your IOUState. - * - You can then compare a copy of the input to the output with the lender of the output as the lender of the input. - * - You'll need references to the input and output ious. - * - Remember you need to cast the [ContractState]s to [IOUState]s. - * - It's easier to take this approach then check all properties other than the lender haven't changed, including - * the [linearId] and the [contract]! - */ - @Test - public void onlyTheLenderMayChange() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(1), ALICE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("Only the lender property may change."); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), ALICE.getParty(), CHARLIE.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("Only the lender property may change."); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty(), Currencies.DOLLARS(5))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("Only the lender property may change."); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), CHARLIE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.verifies(); - }); - return null; - }); - } - - - /** - * Task 4. - * It is fairly obvious that in a transfer IOU transaction the lender must change. - * TODO: Add a constraint to check the lender has changed in the output IOU. - */ - @Test - public void theLenderMustChange() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("The lender property must change in a transfer."); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), CHARLIE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.verifies(); - }); - return null; - }); - } - - /** - * Task 5. - * All the participants in a transfer IOU transaction must sign. - * TODO: Add a constraint to check the old lender, the new lender and the recipient have signed. - */ - @Test - public void allParticipantsMustSign() { - IOUState iou = new IOUState(Currencies.DOLLARS(10), ALICE.getParty(), BOB.getParty()); - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), CHARLIE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), CHARLIE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), CHARLIE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(CHARLIE.getPublicKey(), BOB.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), CHARLIE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), MINICORP.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), CHARLIE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey(), MINICORP.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.failsWith("The borrower, old lender and new lender only must sign an IOU transfer transaction"); - }); - l.transaction(tx -> { - tx.input(IOUContract.IOU_CONTRACT_ID, iou); - tx.output(IOUContract.IOU_CONTRACT_ID, iou.copy(Currencies.DOLLARS(10), CHARLIE.getParty(), BOB.getParty(), Currencies.DOLLARS(0))); - tx.command(Arrays.asList(ALICE.getPublicKey(), BOB.getPublicKey(), CHARLIE.getPublicKey()), new IOUContract.Commands.Transfer()); - return tx.verifies(); - }); - return null; - }); - } - -} \ No newline at end of file diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/TestUtils.java b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/TestUtils.java new file mode 100644 index 00000000..5b661368 --- /dev/null +++ b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/TestUtils.java @@ -0,0 +1,14 @@ +package net.corda.samples.obligation; + +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; + +public class TestUtils { + + public static TestIdentity ALICE = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + public static TestIdentity BOB = new TestIdentity(new CordaX500Name("Bob", "TestCity", "US")); + public static TestIdentity CHARLIE = new TestIdentity(new CordaX500Name("Charlie", "TestVillage", "US")); + public static TestIdentity MINICORP = new TestIdentity(new CordaX500Name("MiniCorp", "MiniLand", "US")); + public static TestIdentity MEGACORP = new TestIdentity(new CordaX500Name("MegaCorp", "MiniLand", "US")); + public static TestIdentity DUMMY = new TestIdentity(new CordaX500Name("Dummy", "FakeLand", "US")); +} diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUIssueFlowTests.java b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUIssueFlowTests.java similarity index 92% rename from Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUIssueFlowTests.java rename to Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUIssueFlowTests.java index 17de17df..691ec424 100644 --- a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUIssueFlowTests.java +++ b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUIssueFlowTests.java @@ -1,29 +1,22 @@ -package net.corda.samples.flow; - +package net.corda.samples.obligation.flows; import net.corda.core.contracts.Command; import net.corda.core.contracts.TransactionVerificationException; import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.SignedTransaction; import net.corda.finance.*; +import net.corda.samples.obligation.states.IOUState; +import net.corda.samples.obligation.contracts.IOUContract; import net.corda.testing.node.*; import net.corda.core.identity.Party; import net.corda.core.crypto.SecureHash; import net.corda.core.flows.*; import net.corda.core.transactions.TransactionBuilder; - - -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.flows.IOUIssueFlow; -import net.corda.samples.states.IOUState; - import java.util.stream.Collectors; import java.util.concurrent.Future; import java.util.*; - import org.junit.*; import org.junit.rules.ExpectedException; - import static org.junit.Assert.*; import static org.hamcrest.core.IsInstanceOf.*; @@ -40,7 +33,7 @@ public class IOUIssueFlowTests { public void setup() { MockNetworkParameters mockNetworkParameters = new MockNetworkParameters().withCordappsForAllNodes( Arrays.asList( - TestCordapp.findCordapp("net.corda.samples.contracts") + TestCordapp.findCordapp("net.corda.samples.obligation.contracts") ) ).withNotarySpecs(Arrays.asList(new MockNetworkNotarySpec(new CordaX500Name("Notary", "London", "GB")))); mockNetwork = new MockNetwork(mockNetworkParameters); @@ -53,7 +46,7 @@ public void setup() { startedNodes.add(a); startedNodes.add(b); - // For real nodes this happens automatically, but we have to manually register the flow for tests + // For real nodes this happens automatically, but we have to manually register the flows for tests startedNodes.forEach(el -> el.registerInitiatedFlow(IOUIssueFlow.ResponderFlow.class)); mockNetwork.runNetwork(); } @@ -69,16 +62,16 @@ public void tearDown() { /** * Task 1. * Build out the {@link IOUIssueFlow}! - * TODO: Implement the {@link IOUIssueFlow} flow which builds and returns a partially {@link SignedTransaction}. + * TODO: Implement the {@link IOUIssueFlow} flows which builds and returns a partially {@link SignedTransaction}. * Hint: * - There's a lot to do to get this unit test to pass! * - Create a {@link TransactionBuilder} and pass it a notary reference. * -- A notary {@link Party} object can be obtained from [FlowLogic.getServiceHub().getNetworkMapCache().getNotaryIdentities()]. * -- In this training project there is only one notary * - Create a new {@link Command} object with the [IOUContract.Commands.Issue] type - * -- The required signers will be the same as the state's participants + * -- The required signers will be the same as the states's participants * -- Add the {@link Command} to the transaction builder [addCommand]. - * - Use the flow's {@link IOUState} parameter as the output state with [addOutputState] + * - Use the flows's {@link IOUState} parameter as the output states with [addOutputState] * - Extra credit: use [TransactionBuilder.withItems] to create the transaction instead * - Sign the transaction and convert it to a {@link SignedTransaction} using the [getServiceHub().signInitialTransaction] method. * - Return the {@link SignedTransaction}. @@ -161,8 +154,8 @@ public void flowReturnsVerifiedPartiallySignedTransaction() throws Exception { * - Get a set of the required signers from the participants who are not the node - refer to Task 6 of IOUIssueTests * - - [getOurIdentity()] will give you the identity of the node you are operating as * - Use [initateFlow] to get a set of {@link FlowSession} objects - * - - Using [state.participants] as a base to determine the sessions needed is recommended. [participants] is on - * - - the state interface so it is guaranteed to to exist where [lender] and [borrower] are not. + * - - Using [states.participants] as a base to determine the sessions needed is recommended. [participants] is on + * - - the states interface so it is guaranteed to to exist where [lender] and [borrower] are not. * - Use [subFlow] to start the {@link CollectSignaturesFlow} * - Pass it a {@link SignedTransaction} object and {@link FlowSession} set * - It will return a {@link SignedTransaction} with all the required signatures @@ -172,7 +165,7 @@ public void flowReturnsVerifiedPartiallySignedTransaction() throws Exception { * - Create a subclass of {@link SignTransactionFlow} * - Override [SignTransactionFlow.checkTransaction] to impose any constraints on the transaction *

- * Using this flow you abstract away all the back-and-forth communication required for parties to sign a + * Using this flows you abstract away all the back-and-forth communication required for parties to sign a * transaction. */ @Test @@ -221,4 +214,4 @@ public void flowRecordsTheSameTransactionInBothPartyVaults() throws Exception { assertEquals(stx.getId(), txHash); }); } -} \ No newline at end of file +} diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUSettleFlowTests.java b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUSettleFlowTests.java similarity index 93% rename from Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUSettleFlowTests.java rename to Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUSettleFlowTests.java index 900c2b8e..8498fdd1 100644 --- a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUSettleFlowTests.java +++ b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUSettleFlowTests.java @@ -1,26 +1,21 @@ -package net.corda.samples.flow; +package net.corda.samples.obligation.flows; - -import net.corda.core.concurrent.CordaFuture; +import net.corda.testing.node.*; import net.corda.core.contracts.Amount; import net.corda.core.contracts.CommandWithParties; +import net.corda.core.concurrent.CordaFuture; import net.corda.core.identity.CordaX500Name; import net.corda.core.transactions.LedgerTransaction; import net.corda.core.transactions.SignedTransaction; import net.corda.finance.Currencies; import net.corda.finance.contracts.asset.Cash; -import net.corda.testing.node.*; -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.flows.IOUIssueFlow; -import net.corda.samples.flows.IOUSettleFlow; -import net.corda.samples.flows.SelfIssueCashFlow; -import net.corda.samples.states.IOUState; +import net.corda.samples.obligation.states.IOUState; +import net.corda.samples.obligation.contracts.IOUContract; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; - import java.util.ArrayList; import java.util.Arrays; import java.util.Currency; @@ -29,23 +24,20 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; -import static net.corda.testing.node.NodeTestUtils.ledger; - /** * Practical exercise instructions Flows part 3. * Uncomment the unit tests and use the hints + unit test body to complete the FLows such that the unit tests pass. */ -public class IOUSettleFlowTests{ +public class IOUSettleFlowTests { private MockNetwork mockNetwork; private StartedMockNode a, b, c; @Before public void setup() { - MockNetworkParameters mockNetworkParameters = new MockNetworkParameters().withCordappsForAllNodes( Arrays.asList( - TestCordapp.findCordapp("net.corda.samples.contracts"), + TestCordapp.findCordapp("net.corda.samples.obligation.contracts"), TestCordapp.findCordapp("net.corda.finance.schemas") ) ).withNotarySpecs(Arrays.asList(new MockNetworkNotarySpec(new CordaX500Name("Notary", "London", "GB")))); @@ -61,7 +53,7 @@ public void setup() { startedNodes.add(b); startedNodes.add(c); - // For real nodes this happens automatically, but we have to manually register the flow for tests + // For real nodes this happens automatically, but we have to manually register the flows for tests startedNodes.forEach(el -> el.registerInitiatedFlow(IOUSettleFlow.Responder.class)); startedNodes.forEach(el -> el.registerInitiatedFlow(IOUIssueFlow.ResponderFlow.class)); mockNetwork.runNetwork(); @@ -163,9 +155,9 @@ public void flowReturnsCorrectlyFormedPartiallySignedTransaction() throws Except /** * Task 2. - * Only the borrower should be running this flow for a particular IOU. - * TODO: Grab the IOU for the given [linearId] from the vault and check the node running the flow is the borrower. - * Hint: Use the data within the iou obtained from the vault to check the right node is running the flow. + * Only the borrower should be running this flows for a particular IOU. + * TODO: Grab the IOU for the given [linearId] from the vault and check the node running the flows is the borrower. + * Hint: Use the data within the iou obtained from the vault to check the right node is running the flows. */ @Test public void settleFlowCanOnlyBeRunByBorrower() throws Exception { @@ -179,14 +171,14 @@ public void settleFlowCanOnlyBeRunByBorrower() throws Exception { mockNetwork.runNetwork(); futureSettleResult.get(); } catch (Exception exception) { - assert exception.getMessage().equals("java.lang.IllegalArgumentException: The borrower must issue the flow"); + assert exception.getMessage().equals("java.lang.IllegalArgumentException: The borrower must issue the flows"); } } /** * Task 3. * The borrower must have at least SOME cash in the right currency to pay the lender. - * TODO: Add a check in the flow to ensure that the borrower has a balance of cash in the right currency. + * TODO: Add a check in the flows to ensure that the borrower has a balance of cash in the right currency. * Hint: * - Use [getCashBalance(getServiceHub(), (Currency) amount.getToken())]. * - Use an if statement to check there is cash in the right currency present. @@ -210,7 +202,7 @@ public void borrowerMustHaveCashInRightCurrency() throws Exception { /** * Task 4. * The borrower must have enough cash in the right currency to pay the lender. - * TODO: Add a check in the flow to ensure that the borrower has enough cash to pay the lender. + * TODO: Add a check in the flows to ensure that the borrower has enough cash to pay the lender. * Hint: Add another if statement similar to the one required above. */ @Test @@ -270,4 +262,4 @@ public void flowReturnsCommittedTransaction() throws Exception { } } -} \ No newline at end of file +} diff --git a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUTransferFlowTests.java b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUTransferFlowTests.java similarity index 91% rename from Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUTransferFlowTests.java rename to Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUTransferFlowTests.java index b54caac8..c25b86bf 100644 --- a/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/flow/IOUTransferFlowTests.java +++ b/Advanced/obligation-cordapp/workflows/src/test/java/net/corda/samples/obligation/flows/IOUTransferFlowTests.java @@ -1,4 +1,4 @@ -package net.corda.samples.flow; +package net.corda.samples.obligation.flows; import net.corda.core.concurrent.CordaFuture; import net.corda.core.contracts.Command; @@ -8,13 +8,10 @@ import net.corda.core.transactions.SignedTransaction; import net.corda.finance.Currencies; import net.corda.testing.node.*; -import net.corda.samples.contracts.IOUContract; -import net.corda.samples.flows.IOUIssueFlow; -import net.corda.samples.flows.IOUTransferFlow; -import net.corda.samples.states.IOUState; +import net.corda.samples.obligation.states.IOUState; +import net.corda.samples.obligation.contracts.IOUContract; import org.junit.*; import org.junit.rules.ExpectedException; - import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.ExecutionException; @@ -29,7 +26,7 @@ public class IOUTransferFlowTests { public void setup() { MockNetworkParameters mockNetworkParameters = new MockNetworkParameters().withCordappsForAllNodes( Arrays.asList( - TestCordapp.findCordapp("net.corda.samples.contracts") + TestCordapp.findCordapp("net.corda.samples.obligation.contracts") ) ).withNotarySpecs(Arrays.asList(new MockNetworkNotarySpec(new CordaX500Name("Notary", "London", "GB")))); mockNetwork = new MockNetwork(mockNetworkParameters); @@ -44,7 +41,7 @@ public void setup() { startedNodes.add(b); startedNodes.add(c); - // For real nodes this happens automatically, but we have to manually register the flow for tests + // For real nodes this happens automatically, but we have to manually register the flows for tests startedNodes.forEach(el -> el.registerInitiatedFlow(IOUTransferFlow.Responder.class)); startedNodes.forEach(el -> el.registerInitiatedFlow(IOUIssueFlow.ResponderFlow.class)); mockNetwork.runNetwork(); @@ -68,14 +65,14 @@ private SignedTransaction issueIOU(IOUState iouState) throws InterruptedExceptio /** * Task 1. * Build out the beginnings of [IOUTransferFlow]! - * TODO: Implement the [IOUTransferFlow] flow which builds and returns a partially [SignedTransaction]. + * TODO: Implement the [IOUTransferFlow] flows which builds and returns a partially [SignedTransaction]. * Hint: - * - This flow will look similar to the [IOUIssueFlow]. - * - This time our transaction has an input state, so we need to retrieve it from the vault! + * - This flows will look similar to the [IOUIssueFlow]. + * - This time our transaction has an input states, so we need to retrieve it from the vault! * - You can use the [getServiceHub().getVaultService().queryBy(Class, queryCriteria)] method to get the latest linear states of a particular * type from the vault. It returns a list of states matching your query. - * - Use the [UniqueIdentifier] which is passed into the flow to create the appropriate Query Criteria. - * - Use the [IOUState.withNewLender] method to create a copy of the state with a new lender. + * - Use the [UniqueIdentifier] which is passed into the flows to create the appropriate Query Criteria. + * - Use the [IOUState.withNewLender] method to create a copy of the states with a new lender. * - Create a Command - we will need to use the Transfer command. * - Remember, as we are involving three parties we will need to collect three signatures, so need to add three * [PublicKey]s to the Command's signers list. We can get the signers from the input IOU and the new IOU you @@ -97,7 +94,7 @@ public void flowReturnsCorrectlyFormedPartiallySignedTransaction() throws Except SignedTransaction ptx = future.get(); // Check the transaction is well formed... - // One output IOUState, one input state reference and a Transfer command with the right properties. + // One output IOUState, one input states reference and a Transfer command with the right properties. assert (ptx.getTx().getInputs().size() == 1); assert (ptx.getTx().getOutputs().size() == 1); assert (ptx.getTx().getOutputs().get(0).getData() instanceof IOUState); @@ -112,12 +109,12 @@ public void flowReturnsCorrectlyFormedPartiallySignedTransaction() throws Except /** * Task 2. - * We need to make sure that only the current lender can execute this flow. - * TODO: Amend the [IOUTransferFlow] to only allow the current lender to execute the flow. + * We need to make sure that only the current lender can execute this flows. + * TODO: Amend the [IOUTransferFlow] to only allow the current lender to execute the flows. * Hint: * - Remember: You can use the node's identity and compare it to the [Party] object within the [IOUState] you * retrieved from the vault. - * - Throw an [IllegalArgumentException] if the wrong party attempts to run the flow! + * - Throw an [IllegalArgumentException] if the wrong party attempts to run the flows! */ @Test public void flowCanOnlyBeRunByCurrentLender() throws Exception { @@ -131,7 +128,7 @@ public void flowCanOnlyBeRunByCurrentLender() throws Exception { mockNetwork.runNetwork(); future.get(); } catch (Exception exception) { - assert exception.getMessage().equals("java.lang.IllegalArgumentException: This flow must be run by the current lender."); + assert exception.getMessage().equals("java.lang.IllegalArgumentException: This flows must be run by the current lender."); } } @@ -201,4 +198,4 @@ public void flowReturnsTransactionSignedByAllPartiesAndNotary() throws Exception System.out.println(exception.getMessage()); } } -} \ No newline at end of file +} diff --git a/Basic/spring-webserver/LICENCE b/Advanced/secretsanta-cordapp/LICENCE similarity index 100% rename from Basic/spring-webserver/LICENCE rename to Advanced/secretsanta-cordapp/LICENCE diff --git a/Advanced/secretsanta-cordapp/README.md b/Advanced/secretsanta-cordapp/README.md new file mode 100644 index 00000000..8f823c8c --- /dev/null +++ b/Advanced/secretsanta-cordapp/README.md @@ -0,0 +1,85 @@ +![](./clients/src/main/webapp/src/Components/img/secret_corda.png) + +# Corda Secret Santa + +This is an imlementation of Secret Santa using Corda as a tool to store multiple game states. + +It has a material-ui frontend that lets users create and self-service their own secret santa games. The frontend is implemented in ReactJS and the backend is implemented with a Spring Boot server and some corda flows. + +You can create a game using the web frontend (or just calling the api directly with Postman), and once the game is stored, players can look up their assignments using their game id, and the app also supports an optional sendgrid integration so that you can have emails sent to the players as well. + +> One tip if you're using intellij is to open the project from the intellij dialog, don't import the project directly. + +## Usage + +There's essentially five processes you'll need to be aware of. + +- Three Corda nodes, a notary, santa, and an elf +- The backend webserver that runs the REST endpoints for the corda nodes +- The frontend webserver, a react app that sends requests to the backend. + + +#### Pre-Requisites + +If you've never built a cordapp before you may need to configure gradle and java in order for this code example to run. See [our setup guide](https://docs.corda.net/getting-set-up.html). + + +### Running these services + +#### The three Corda nodes +To run the corda nodes you just need to run the `deployNodes` gradle task and the nodes will be available for you to run directly. + +``` +./gradlew deployNodes +./build/nodes/runnodes +``` + +#### The backend webserver + +Run the `runSantaServer` Gradle task. By default, it connects to the node with RPC address `localhost:10006` with +the username `user1` and the password `test`, and serves the webserver on port `localhost:10056`. + +``` +./gradlew runSantaServer +``` + +The frontend will be visible on [localhost:10056](http://localhost:10056) + +##### Background Information + +`clients/src/main/java/com/secretsanta/webserver/` defines a simple Spring webserver that connects to a node via RPC and allows you to interact with the node over HTTP. + +The API endpoints are defined in `clients/src/main/java/com/secretsanta/webserver/Controller.java` + + +#### The frontend webserver + +The react server can be started by going to `clients/src/main/webapp`, running `npm install` and then `npm start`. + +``` +cd clients/src/main/webapp +npm install +npm start +``` + +The frontend will be visible on [localhost:8888](http://localhost:8888) + +#### Configuring Email with SendGrid + +If you'd like to start sending email you'll need to make an account on [sendgrid.com](http://sendgrid.com) and configure a verified sender identity. + +Once you've done that, create an API key and place it into `Controller.java`(the webserver for the corda nodes). After which point you can set the `sendEmail` param to `true` in your requests. In order to configure the frontend to send emails, just open `CONSTANTS.js` and set the `SEND_EMAIL` param to `true` instead of `false`. + + +### Testing Utilities + + +#### Using Postman for backend testing + +I've included some simple postman tests to run against the santa server that will be helpful to you if you plan on using this. You'll find them in the `postman` folder. + + +#### Running tests inside IntelliJ + +There are unit tests for the corda state, contract, and tests for both flows used here. You'll find them inside of the various test folders. + diff --git a/Advanced/secretsanta-cordapp/TRADEMARK b/Advanced/secretsanta-cordapp/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Advanced/secretsanta-cordapp/TRADEMARK @@ -0,0 +1,4 @@ +Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates. All rights reserved. + +For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy at +https://www.r3.com/trademark-policy/. \ No newline at end of file diff --git a/Advanced/secretsanta-cordapp/build.gradle b/Advanced/secretsanta-cordapp/build.gradle new file mode 100644 index 00000000..622a2636 --- /dev/null +++ b/Advanced/secretsanta-cordapp/build.gradle @@ -0,0 +1,162 @@ +buildscript { //properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + corda_gradle_plugins_version = constants.getProperty("gradlePluginsVersion") + kotlin_version = constants.getProperty("kotlinVersion") + junit_version = constants.getProperty("junitVersion") + quasar_version = constants.getProperty("quasarVersion") + log4j_version = constants.getProperty("log4jVersion") + slf4j_version = constants.getProperty("slf4jVersion") + corda_platform_version = constants.getProperty("platformVersion").toInteger() + + // springboot + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + + accounts_release_version = '1.0' + accounts_release_group = 'com.r3.corda.lib.accounts' + confidential_id_release_group = "com.r3.corda.lib.ci" + confidential_id_release_version = "1.0" + } + + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://jitpack.io' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } + maven { url 'http://software.r3.com/artifactory/corda-lib' } + maven { url 'https://download.corda.net/maven/corda-releases' } + } + + dependencies { + classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" + classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" + classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" + classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" + + classpath "com.sendgrid:sendgrid-java:4.7.0" + classpath "com.sendgrid:java-http-client:4.3.6" + } +} + +allprojects {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. + } + +// tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { +// kotlinOptions { +// languageVersion = "1.2" +// apiVersion = "1.2" +// jvmTarget = "1.8" +// javaParameters = true // Useful for reflection. +// } +// } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":workflows") + cordapp project(":contracts") + + cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" + cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaDriver "net.corda:corda-shell:4.10" + +} + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + + //Java version check + if (JavaVersion.current() != JavaVersion.VERSION_1_8){ + throw new GradleException("This build must be run with java 8") + } + + nodeDefaults { + projectCordapp { deploy = true } + cordapp project(':contracts') + cordapp project(':workflows') + } + + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=Santa,L=London,C=GB" + p2pPort 10006 + rpcSettings { + address("localhost:10016") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + + node { + name "O=Elf,L=London,C=GB" + p2pPort 10007 + rpcSettings { + address("localhost:10017") + adminAddress("localhost:10027") + } + + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + +} diff --git a/Advanced/secretsanta-cordapp/clients/build.gradle b/Advanced/secretsanta-cordapp/clients/build.gradle new file mode 100644 index 00000000..2b32e7dc --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/build.gradle @@ -0,0 +1,63 @@ +buildscript { + repositories { + maven { + url = uri("https://plugins.gradle.org/m2/") + } + } +} + +apply plugin: 'org.springframework.boot' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +dependencies { + implementation 'com.google.code.gson:gson:2.8.5' + + // Corda dependencies. + compile "$corda_release_group:corda-rpc:$corda_release_version" + + // CorDapp dependencies. + compile project(":contracts") + compile project(":workflows") + compile("org.springframework.boot:spring-boot-starter-websocket:$spring_boot_version") { + exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" + } + compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + compile "org.apache.logging.log4j:log4j-web:${log4j_version}" + compile "org.slf4j:jul-to-slf4j:$slf4j_version" + + // SendGrid + implementation 'com.sendgrid:sendgrid-java:4.7.0' + compile 'com.sendgrid:sendgrid-java:4.7.0' +} + +springBoot { + mainClassName = "net.corda.samples.secretsanta.webserver.Server" +} + +/* The Client is the communication channel between the external and the node. This task will help you immediately + * execute your rpc methods in the main method of the client.kt. You can somewhat see this as a quick test of making + * RPC calls to your nodes. + */ +task runSantaClient(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.secretsanta.Client' + args 'localhost:10016', 'user1', 'test' +} + +/** + * This task will start the springboot server that connects to the santa node (via RPC connection). All of the http requests + * are in the Controller file. + * + */ +task runSantaServer(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.secretsanta.webserver.Starter' + args '--server.port=10056', '--config.rpc.host=localhost', '--config.rpc.port=10016', '--config.rpc.username=user1', '--config.rpc.password=test' +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/Client.java b/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/Client.java new file mode 100644 index 00000000..cf4b0598 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/Client.java @@ -0,0 +1,48 @@ +package net.corda.samples.secretsanta; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; +import net.corda.core.utilities.NetworkHostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static net.corda.core.utilities.NetworkHostAndPort.parse; + +/** + * Connects to a Corda node via RPC and performs RPC operations on the node. + * + * The RPC connection is configured using command line arguments. + */ +public class Client { + private static final Logger logger = LoggerFactory.getLogger(Client.class); + + public static void main(String[] args) { + // Create an RPC connection to the node. + if (args.length != 3) throw new IllegalArgumentException("Usage: Client "); + final NetworkHostAndPort nodeAddress = parse(args[0]); + final String rpcUsername = args[1]; + final String rpcPassword = args[2]; + final CordaRPCClient client = new CordaRPCClient(nodeAddress); + final CordaRPCConnection clientConnection = client.start(rpcUsername, rpcPassword); + final CordaRPCOps proxy = clientConnection.getProxy(); + + // Interact with the node. + // Example #1, here we print the nodes on the network. + final List nodes = proxy.networkMapSnapshot(); + System.out.println("\n-- Here is the networkMap snapshot --"); + logger.info("{}", nodes); + + // Example #2, here we print the PartyA's node info + CordaX500Name name = proxy.nodeInfo().getLegalIdentities().get(0).getName();//nodeInfo().legalIdentities.first().name + System.out.println("\n-- Here is the node info of the node that the client connected to --"); + logger.info("{}", name); + + //Close the client connection + clientConnection.close(); + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/Controller.java b/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/Controller.java new file mode 100644 index 00000000..6ffbed7a --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/Controller.java @@ -0,0 +1,216 @@ +package net.corda.samples.secretsanta.webserver; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import net.corda.samples.secretsanta.flows.CheckAssignedSantaFlow; +import net.corda.samples.secretsanta.flows.CreateSantaSessionFlow; +import net.corda.samples.secretsanta.states.SantaSessionState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.*; + +import com.sendgrid.*; +import com.sendgrid.helpers.mail.Mail; +import com.sendgrid.helpers.mail.objects.Content; +import com.sendgrid.helpers.mail.objects.Email; + + +/** + * Define your API endpoints here. + * + * Note we allow all origins for convenience, this is NOT a good production practice. + */ +@RestController +@RequestMapping("/") // The paths for HTTP requests are relative to this base path. +@CrossOrigin(origins = "*") +public class Controller { + private final CordaRPCOps proxy; + private final CordaX500Name me; + private final static Logger logger = LoggerFactory.getLogger(Controller.class); + + private boolean isMe(NodeInfo nodeInfo){ + return nodeInfo.getLegalIdentities().get(0).getName().equals(me); + } + + private Party getPartyFromNodeInfo(NodeInfo nodeInfo) { + Party target = nodeInfo.getLegalIdentities().get(0); + return target; + } + + public Controller(NodeRPCConnection rpc) { + this.proxy = rpc.proxy; + this.me = proxy.nodeInfo().getLegalIdentities().get(0).getName(); + } + + @RequestMapping(value = "/node", method = RequestMethod.GET) + private ResponseEntity returnName() { + JsonObject resp = new JsonObject(); + resp.addProperty("name", me.toString()); + return ResponseEntity.status(HttpStatus.CREATED).contentType(MediaType.APPLICATION_JSON).body(resp.toString()); + } + + @RequestMapping(value = "/games/check", method = RequestMethod.POST) + public ResponseEntity checkGame(@RequestBody String payload) { + + System.out.println(payload); + + JsonObject convertedObject = new Gson().fromJson(payload, JsonObject.class); + + UniqueIdentifier gameId = UniqueIdentifier.Companion.fromString(convertedObject.get("gameId").getAsString()); + // NOTE lowercase the name for easy retrieve + String playerName = "\"" + convertedObject.get("name").getAsString().toLowerCase().trim() + "\""; + + // optional param + Boolean sendEmail = convertedObject.get("sendEmail").getAsBoolean(); + + JsonObject resp = new JsonObject(); + + try { + SantaSessionState output = proxy.startTrackedFlowDynamic(CheckAssignedSantaFlow.class, gameId).getReturnValue().get(); + + if (output.getAssignments().get(playerName) == null) { + resp.addProperty("target", "target not found in this game"); + return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(resp.toString()); + } + + List playerNames = output.getPlayerNames(); + List playerEmails = output.getPlayerEmails(); + + String playerEmail = playerEmails.get(playerNames.indexOf(playerName)); + String targetName = output.getAssignments().get(playerName).replace("\"", ""); + + resp.addProperty("target", targetName); + + if (sendEmail) { + System.out.println("sending email to "+ playerEmail); + boolean b = sendEmail(playerEmail, craftNotice(playerName, targetName, gameId)); + + if (!b) { + System.out.println("ERROR: Failed to send email."); + } + } + + return ResponseEntity.status(HttpStatus.ACCEPTED).contentType(MediaType.APPLICATION_JSON).body(resp.toString()); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + } + + @RequestMapping(value = "/games", method = RequestMethod.POST) + public ResponseEntity createGame(@RequestBody String payload) { + + System.out.println(payload); + JsonObject convertedObject = new Gson().fromJson(payload, JsonObject.class); + + JsonArray pNames = convertedObject.getAsJsonArray("playerNames"); + JsonArray pMails = convertedObject.getAsJsonArray("playerEmails"); + + // optional param + Boolean sendEmail = convertedObject.get("sendEmail").getAsBoolean(); + + List playerNames = new ArrayList<>(); + List playerEmails = new ArrayList<>(); + + // NOTE: we lowercase all names internally for clarity + for (JsonElement jo : pNames) { + String newName = jo.toString().toLowerCase(); + + if (!playerNames.contains(newName)) { + playerNames.add(newName); + } + } + for (JsonElement jo : pMails) { + playerEmails.add(jo.toString()); + } + + try { + Party elf = getPartyFromNodeInfo(proxy.networkMapSnapshot().get(2)); + + // run the flow to create our game + SantaSessionState output = proxy.startTrackedFlowDynamic(CreateSantaSessionFlow.class, playerNames, playerEmails, elf).getReturnValue().get().getTx().outputsOfType(SantaSessionState.class).get(0); + UniqueIdentifier gameId = output.getLinearId(); + LinkedHashMap assignments = output.getAssignments(); + + System.out.println("Created Secret Santa Game ID# " + output.getLinearId().toString()); + + // send email to each player with the assignments + for (String p: playerNames) { + String t = assignments.get(p).replace("\"", ""); + String msg = craftNotice(p, t, gameId); + int ind = playerNames.indexOf(p); + String email = playerEmails.get(ind).replace("\"", ""); + + if (sendEmail) { + boolean b = sendEmail(email, msg); + + if (!b) { + System.out.println("ERROR: Failed to send email."); + } + } + } + + JsonObject resp = new JsonObject(); + resp.addProperty("gameId", gameId.toString()); + return ResponseEntity.status(HttpStatus.CREATED).contentType(MediaType.APPLICATION_JSON).body(resp.toString()); + + } catch (Exception e) { + System.out.println("Exception : " + e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + } + + + public String craftNotice(String p, String t, UniqueIdentifier gameId) { + return "Hello, " + p + "!" + "\n" + "Your super secret santa target for game # " + gameId.toString() + " is " + t + "."; + } + + public boolean sendEmail(String recipient, String msg) { + + // TODO replace the below line with your *REAL* api key! + final String SENDGRID_API_KEY = "SG.xxxxxx_xxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxx_xxxxxxx"; + + if (SENDGRID_API_KEY.equals("SG.xxxxxx_xxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxx_xxxxxxx")) { + System.out.println("You haven't added your sendgrid api key!"); + return true; + } + + // you'll need to specify your sendgrid verified sender identity for this to mail out. + Email from = new Email("test@example.com"); + String subject = "Secret Assignment from the Elves"; + Email to = new Email(recipient); + Content content = new Content("text/plain", msg); + Mail mail = new Mail(from, subject, to, content); + + SendGrid sg = new SendGrid(SENDGRID_API_KEY); + Request request = new Request(); + + try { + request.setMethod(Method.POST); + request.setEndpoint("mail/send"); + request.setBody(mail.build()); + Response response = sg.api(request); + System.out.println(response.getStatusCode()); + System.out.println(response.getBody()); + System.out.println(response.getHeaders()); + return true; + } catch (IOException ex) { + System.out.println(ex.toString()); + return false; + } + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/NodeRPCConnection.java b/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/NodeRPCConnection.java new file mode 100644 index 00000000..2bfa4e74 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/NodeRPCConnection.java @@ -0,0 +1,48 @@ +package net.corda.samples.secretsanta.webserver; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/** + * Wraps an RPC connection to a Corda node. + * + * The RPC connection is configured using command line arguments. + */ +@Component +public class NodeRPCConnection implements AutoCloseable { + // The host of the node we are connecting to. + @Value("${config.rpc.host}") + private String host; + // The RPC port of the node we are connecting to. + @Value("${config.rpc.username}") + private String username; + // The username for logging into the RPC client. + @Value("${config.rpc.password}") + private String password; + // The password for logging into the RPC client. + @Value("${config.rpc.port}") + private int rpcPort; + + private CordaRPCConnection rpcConnection; + CordaRPCOps proxy; + + @PostConstruct + public void initialiseNodeRPCConnection() { + NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); + CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); + rpcConnection = rpcClient.start(username, password); + proxy = rpcConnection.getProxy(); + } + + @PreDestroy + public void close() { + rpcConnection.notifyServerAndClose(); + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/Starter.java b/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/Starter.java new file mode 100644 index 00000000..c480ca56 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/java/net/corda/samples/secretsanta/webserver/Starter.java @@ -0,0 +1,23 @@ +package net.corda.samples.secretsanta.webserver; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import static org.springframework.boot.WebApplicationType.SERVLET; + +/** + * Our Spring Boot application. + */ +@SpringBootApplication +public class Starter { + /** + * Starts our Spring Boot application. + */ + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Starter.class); + app.setBannerMode(Banner.Mode.OFF); + app.setWebApplicationType(SERVLET); + app.run(args); + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/.eslintcache b/Advanced/secretsanta-cordapp/clients/src/main/webapp/.eslintcache new file mode 100644 index 00000000..bd1813ae --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/.eslintcache @@ -0,0 +1 @@ +[{"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/reportWebVitals.js":"1","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/index.js":"2","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/CONSTANTS.js":"3","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/serviceWorker.js":"4","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/theme.js":"5","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/CreateSantaGame/CreateSantaGame.js":"6","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/CheckSantaGame/CheckSantaGame.js":"7","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/SantaGameCreated/SantaGameCreated.js":"8","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/SantaCheckSent/SantaCheckSent.js":"9","/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/App/App.js":"10"},{"size":362,"mtime":1606769571077,"results":"11","hashOfConfig":"12"},{"size":317,"mtime":1607466206837,"results":"13","hashOfConfig":"14"},{"size":377,"mtime":1607457517284,"results":"15","hashOfConfig":"14"},{"size":5086,"mtime":1606777278010,"results":"16","hashOfConfig":"14"},{"size":745,"mtime":1606777288842,"results":"17","hashOfConfig":"18"},{"size":8659,"mtime":1607529764682,"results":"19","hashOfConfig":"14"},{"size":5445,"mtime":1607539841228,"results":"20","hashOfConfig":"18"},{"size":2402,"mtime":1607529771917,"results":"21","hashOfConfig":"14"},{"size":2102,"mtime":1607529306031,"results":"22","hashOfConfig":"14"},{"size":864,"mtime":1607529826449,"results":"23","hashOfConfig":"14"},{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"xu1ola",{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1p76djd",{"filePath":"28","messages":"29","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"30","messages":"31","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"32","messages":"33","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1bquvw9",{"filePath":"34","messages":"35","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"36","messages":"37","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"38","messages":"39","errorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"40","messages":"41","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"42","messages":"43","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/reportWebVitals.js",[],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/index.js",[],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/CONSTANTS.js",[],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/serviceWorker.js",[],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/theme.js",[],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/CreateSantaGame/CreateSantaGame.js",[],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/CheckSantaGame/CheckSantaGame.js",[],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/SantaGameCreated/SantaGameCreated.js",["44","45"],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/SantaCheckSent/SantaCheckSent.js",["46"],"/Users/davidawad/Projects/R3/Cordapp Samples/secret-corda-santa/clients/src/main/webapp/src/Components/App/App.js",[],{"ruleId":"47","severity":1,"message":"48","line":55,"column":7,"nodeType":"49","endLine":55,"endColumn":32},{"ruleId":"47","severity":1,"message":"48","line":75,"column":9,"nodeType":"49","endLine":75,"endColumn":34},{"ruleId":"47","severity":1,"message":"48","line":52,"column":7,"nodeType":"49","endLine":52,"endColumn":32},"jsx-a11y/alt-text","img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.","JSXOpeningElement"] \ No newline at end of file diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/.gitignore b/Advanced/secretsanta-cordapp/clients/src/main/webapp/.gitignore new file mode 100644 index 00000000..4d29575d --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/package-lock.json b/Advanced/secretsanta-cordapp/clients/src/main/webapp/package-lock.json new file mode 100644 index 00000000..ed5f0838 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/package-lock.json @@ -0,0 +1,17197 @@ +{ + "name": "frontend", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/compat-data": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz", + "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==" + }, + "@babel/core": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/generator": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", + "requires": { + "@babel/types": "^7.12.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", + "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-react-jsx-experimental": { + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.4.tgz", + "integrity": "sha512-AjEa0jrQqNk7eDQOo0pTfUOwQBMF+xVqrausQwT9/rTKy0g04ggFNaJpaE09IQMn9yExluigWMJcj0WC7bq+Og==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-module-imports": "^7.12.1", + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", + "integrity": "sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==", + "requires": { + "@babel/compat-data": "^7.12.5", + "@babel/helper-validator-option": "^7.12.1", + "browserslist": "^4.14.5", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", + "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", + "integrity": "sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "regexpu-core": "^4.7.1" + } + }, + "@babel/helper-define-map": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", + "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", + "requires": { + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", + "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", + "requires": { + "@babel/types": "^7.12.7" + } + }, + "@babel/helper-module-imports": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "requires": { + "@babel/types": "^7.12.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.7.tgz", + "integrity": "sha512-I5xc9oSJ2h59OwyUqjv95HRyzxj53DAubUERgQMrpcCEYQyToeHA+NEcUEsVWB4j53RDeskeBJ0SgRAYHDBckw==", + "requires": { + "@babel/types": "^7.12.7" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", + "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-replace-supers": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", + "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "requires": { + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "requires": { + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + }, + "@babel/helper-validator-option": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", + "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==" + }, + "@babel/helper-wrap-function": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", + "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helpers": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz", + "integrity": "sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", + "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.12.1.tgz", + "integrity": "sha512-knNIuusychgYN8fGJHONL0RbFxLGawhXOJNLBk75TniTsZZeA+wdkDuv6wp4lGwzQEKjZi6/WYtnb3udNPmQmQ==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-decorators": "^7.12.1" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", + "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", + "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", + "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", + "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", + "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", + "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", + "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", + "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", + "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", + "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz", + "integrity": "sha512-ir9YW5daRrTYiy9UJ2TzdNIJEZu8KclVzDcfSt4iEmOtwQ4llPtWInNKJyKnVXp1vE4bbVd5S31M/im3mYMO1w==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-flow": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.1.tgz", + "integrity": "sha512-1lBLLmtxrwpm4VKmtVFselI/P3pX+G63fAtUUt6b2Nzgao77KNDwyuRt90Mj2/9pKobtt68FdvjfqohZjg/FCA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz", + "integrity": "sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", + "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", + "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", + "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", + "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", + "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", + "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", + "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", + "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", + "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", + "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-flow-strip-types": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.12.1.tgz", + "integrity": "sha512-8hAtkmsQb36yMmEtk2JZ9JnVyDSnDOdlB+0nEGzIDLuK4yR3JcEjfuFPYkdEPSh8Id+rAMeBEn+X0iVEyho6Hg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", + "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", + "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", + "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", + "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", + "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", + "requires": { + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", + "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", + "requires": { + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.12.1", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", + "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", + "requires": { + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-identifier": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", + "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", + "requires": { + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", + "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", + "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", + "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", + "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.12.1.tgz", + "integrity": "sha512-KOHd0tIRLoER+J+8f9DblZDa1fLGPwaaN1DI1TVHuQFOpjHV22C3CUB3obeC4fexHY9nx+fH0hQNvLFFfA1mxA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz", + "integrity": "sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.7.tgz", + "integrity": "sha512-YFlTi6MEsclFAPIDNZYiCRbneg1MFGao9pPG9uD5htwE0vDbPaMUMeYd6itWjw7K4kro4UbdQf3ljmFl9y48dQ==", + "requires": { + "@babel/helper-builder-react-jsx": "^7.10.4", + "@babel/helper-builder-react-jsx-experimental": "^7.12.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.12.1" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.7.tgz", + "integrity": "sha512-Rs3ETtMtR3VLXFeYRChle5SsP/P9Jp/6dsewBQfokDSzKJThlsuFcnzLTDRALiUmTC48ej19YD9uN1mupEeEDg==", + "requires": { + "@babel/helper-builder-react-jsx-experimental": "^7.12.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.12.1" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz", + "integrity": "sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz", + "integrity": "sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", + "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", + "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "resolve": "^1.8.1", + "semver": "^5.5.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", + "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", + "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", + "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", + "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz", + "integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz", + "integrity": "sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-typescript": "^7.12.1" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", + "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", + "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/preset-env": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.7.tgz", + "integrity": "sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew==", + "requires": { + "@babel/compat-data": "^7.12.7", + "@babel/helper-compilation-targets": "^7.12.5", + "@babel/helper-module-imports": "^7.12.5", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-option": "^7.12.1", + "@babel/plugin-proposal-async-generator-functions": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-dynamic-import": "^7.12.1", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", + "@babel/plugin-proposal-json-strings": "^7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.7", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.7", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.12.1", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.12.1", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-async-to-generator": "^7.12.1", + "@babel/plugin-transform-block-scoped-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.1", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-computed-properties": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-dotall-regex": "^7.12.1", + "@babel/plugin-transform-duplicate-keys": "^7.12.1", + "@babel/plugin-transform-exponentiation-operator": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-function-name": "^7.12.1", + "@babel/plugin-transform-literals": "^7.12.1", + "@babel/plugin-transform-member-expression-literals": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/plugin-transform-modules-commonjs": "^7.12.1", + "@babel/plugin-transform-modules-systemjs": "^7.12.1", + "@babel/plugin-transform-modules-umd": "^7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", + "@babel/plugin-transform-new-target": "^7.12.1", + "@babel/plugin-transform-object-super": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-property-literals": "^7.12.1", + "@babel/plugin-transform-regenerator": "^7.12.1", + "@babel/plugin-transform-reserved-words": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/plugin-transform-sticky-regex": "^7.12.7", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.1", + "@babel/plugin-transform-unicode-escapes": "^7.12.1", + "@babel/plugin-transform-unicode-regex": "^7.12.1", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.12.7", + "core-js-compat": "^3.7.0", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-react": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.12.7.tgz", + "integrity": "sha512-wKeTdnGUP5AEYCYQIMeXMMwU7j+2opxrG0WzuZfxuuW9nhKvvALBjl67653CWamZJVefuJGI219G591RSldrqQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-react-display-name": "^7.12.1", + "@babel/plugin-transform-react-jsx": "^7.12.7", + "@babel/plugin-transform-react-jsx-development": "^7.12.7", + "@babel/plugin-transform-react-jsx-self": "^7.12.1", + "@babel/plugin-transform-react-jsx-source": "^7.12.1", + "@babel/plugin-transform-react-pure-annotations": "^7.12.1" + } + }, + "@babel/preset-typescript": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.1.tgz", + "integrity": "sha512-hNK/DhmoJPsksdHuI/RVrcEws7GN5eamhi28JkO52MqIxU8Z0QpmiSOQxZHWOHV7I3P4UjHV97ay4TcamMA6Kw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.12.1" + } + }, + "@babel/runtime": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", + "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/runtime-corejs3": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz", + "integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==", + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/traverse": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz", + "integrity": "sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz", + "integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" + }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@csstools/convert-colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" + }, + "@csstools/normalize.css": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", + "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" + }, + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "@eslint/eslintrc": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz", + "integrity": "sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "requires": { + "type-fest": "^0.8.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" + }, + "@hapi/bourne": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", + "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==" + }, + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" + }, + "@hapi/joi": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", + "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", + "requires": { + "@hapi/address": "2.x.x", + "@hapi/bourne": "1.x.x", + "@hapi/hoek": "8.x.x", + "@hapi/topo": "3.x.x" + } + }, + "@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "requires": { + "@hapi/hoek": "^8.3.0" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==" + }, + "@jest/console": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", + "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.6.2", + "jest-util": "^26.6.2", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/core": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", + "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", + "requires": { + "@jest/console": "^26.6.2", + "@jest/reporters": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^26.6.2", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-resolve-dependencies": "^26.6.3", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "jest-watcher": "^26.6.2", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "@jest/environment": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", + "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "requires": { + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2" + } + }, + "@jest/fake-timers": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", + "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "requires": { + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "@jest/globals": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", + "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "requires": { + "@jest/environment": "^26.6.2", + "@jest/types": "^26.6.2", + "expect": "^26.6.2" + } + }, + "@jest/reporters": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", + "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "node-notifier": "^8.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "@jest/source-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", + "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@jest/test-result": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", + "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "requires": { + "@jest/console": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", + "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "requires": { + "@jest/test-result": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3" + } + }, + "@jest/transform": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", + "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^26.6.2", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.6.2", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@material-ui/core": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.1.tgz", + "integrity": "sha512-aesI8lOaaw0DRIfNG+Anepf61NH5Q+cmkxJOZvI1oHkmD5cKubkZ0C7INqFKjfFpSFlFnqHkTasoM7ogFAvzOg==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.11.1", + "@material-ui/system": "^4.9.14", + "@material-ui/types": "^5.1.0", + "@material-ui/utils": "^4.10.2", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.4", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "1.16.1-lts", + "prop-types": "^15.7.2", + "react-is": "^16.8.0", + "react-transition-group": "^4.4.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "@material-ui/icons": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz", + "integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, + "@material-ui/styles": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.1.tgz", + "integrity": "sha512-GqzsFsVWT8jXa8OWAd1WD6WIaqtXr2mUPbRZ1EjkiM3Dlta4mCRaToDxkFVv6ZHfXlFjMJzdaIEvCpZOCvZTvg==", + "requires": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "^5.1.0", + "@material-ui/utils": "^4.9.6", + "clsx": "^1.0.4", + "csstype": "^2.5.2", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.0.3", + "jss-plugin-camel-case": "^10.0.3", + "jss-plugin-default-unit": "^10.0.3", + "jss-plugin-global": "^10.0.3", + "jss-plugin-nested": "^10.0.3", + "jss-plugin-props-sort": "^10.0.3", + "jss-plugin-rule-value-function": "^10.0.3", + "jss-plugin-vendor-prefixer": "^10.0.3", + "prop-types": "^15.7.2" + } + }, + "@material-ui/system": { + "version": "4.9.14", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz", + "integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.9.6", + "csstype": "^2.5.2", + "prop-types": "^15.7.2" + } + }, + "@material-ui/types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==" + }, + "@material-ui/utils": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz", + "integrity": "sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==", + "requires": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "requires": { + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, + "@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.2.tgz", + "integrity": "sha512-Loc4UDGutcZ+Bd56hBInkm6JyjyCwWy4t2wcDXzN8EDPANgVRj0VP8Nxn0Zq2pc+WKauZwEivQgbDGg4xZO20A==", + "requires": { + "ansi-html": "^0.0.7", + "error-stack-parser": "^2.0.6", + "html-entities": "^1.2.1", + "native-url": "^0.2.6", + "schema-utils": "^2.6.5", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, + "@rollup/plugin-node-resolve": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", + "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", + "requires": { + "@rollup/pluginutils": "^3.0.8", + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.14.2" + } + }, + "@rollup/plugin-replace": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz", + "integrity": "sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ==", + "requires": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + } + } + }, + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@surma/rollup-plugin-off-main-thread": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz", + "integrity": "sha512-yBMPqmd1yEJo/280PAMkychuaALyQ9Lkb5q1ck3mjJrFuEobIfhnQ4J3mbvBoISmR3SWMWV+cGB/I0lCQee79A==", + "requires": { + "ejs": "^2.6.1", + "magic-string": "^0.25.0" + } + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==" + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==" + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==" + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==" + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==" + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==" + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==" + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==" + }, + "@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + } + }, + "@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "requires": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "requires": { + "@babel/types": "^7.12.6" + } + }, + "@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "requires": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + } + }, + "@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "requires": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + } + }, + "@svgr/webpack": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.4.0.tgz", + "integrity": "sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg==", + "requires": { + "@babel/core": "^7.9.0", + "@babel/plugin-transform-react-constant-elements": "^7.9.0", + "@babel/preset-env": "^7.9.5", + "@babel/preset-react": "^7.9.4", + "@svgr/core": "^5.4.0", + "@svgr/plugin-jsx": "^5.4.0", + "@svgr/plugin-svgo": "^5.4.0", + "loader-utils": "^2.0.0" + } + }, + "@testing-library/dom": { + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.28.1.tgz", + "integrity": "sha512-acv3l6kDwZkQif/YqJjstT3ks5aaI33uxGNVIQmdKzbZ2eMKgg3EV2tB84GDdc72k3Kjhl6mO8yUt6StVIdRDg==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^4.2.2", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.4", + "lz-string": "^1.4.4", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "5.11.6", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.6.tgz", + "integrity": "sha512-cVZyUNRWwUKI0++yepYpYX7uhrP398I+tGz4zOlLVlUYnZS+Svuxv4fwLeCIy7TnBYKXUaOlQr3vopxL8ZfEnA==", + "requires": { + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^4.2.2", + "chalk": "^3.0.0", + "css": "^3.0.0", + "css.escape": "^1.5.1", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "requires": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.2.tgz", + "integrity": "sha512-jaxm0hwUjv+hzC+UFEywic7buDC9JQ1q3cDsrWVSDAPmLotfA6E6kUHlYm/zOeGCac6g48DR36tFHxl7Zb+N5A==", + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^7.28.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "@testing-library/user-event": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.3.0.tgz", + "integrity": "sha512-A4TZofjkOH42ydTtHZcGNhwYjonkVIGBi4pmNweUgjDEGmWHuZf4k7hLd6QTL+rkSOgrd3TOCFDNyK9KO0reeA==", + "requires": { + "@babel/runtime": "^7.10.2" + } + }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" + }, + "@types/aria-query": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", + "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==" + }, + "@types/babel__core": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", + "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", + "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", + "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.16.tgz", + "integrity": "sha512-S63Dt4CZOkuTmpLGGWtT/mQdVORJOpx6SZWGVaP56dda/0Nx5nEe82K7/LAm8zYr6SfMq+1N2OreIOrHAx656w==", + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/eslint": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.5.tgz", + "integrity": "sha512-Dc6ar9x16BdaR3NSxSF7T4IjL9gxxViJq8RmFd+2UAyA+K6ck2W+gUwfgpG/y9TPyUuBL35109bbULpEynvltA==", + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/estree": { + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", + "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==" + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", + "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", + "requires": { + "@types/node": "*" + } + }, + "@types/html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "26.0.15", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz", + "integrity": "sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog==", + "requires": { + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" + } + }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, + "@types/node": { + "version": "14.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz", + "integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==" + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "@types/prettier": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", + "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==" + }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" + }, + "@types/react": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz", + "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==" + } + } + }, + "@types/react-transition-group": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz", + "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==", + "requires": { + "@types/react": "*" + } + }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==" + }, + "@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==" + }, + "@types/tapable": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", + "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==" + }, + "@types/testing-library__jest-dom": { + "version": "5.9.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz", + "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==", + "requires": { + "@types/jest": "*" + } + }, + "@types/uglify-js": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.1.tgz", + "integrity": "sha512-7npvPKV+jINLu1SpSYVWG8KvyJBhBa8tmzMMdDoVc2pWUYHN8KIXlPJhjJ4LT97c4dXJA2SHL/q6ADbDriZN+Q==", + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@types/webpack": { + "version": "4.41.25", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.25.tgz", + "integrity": "sha512-cr6kZ+4m9lp86ytQc1jPOJXgINQyz3kLLunZ57jznW+WIAL0JqZbGubQk4GlD42MuQL5JGOABrxdpqqWeovlVQ==", + "requires": { + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@types/webpack-sources": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.0.0.tgz", + "integrity": "sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg==", + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, + "@types/yargs": { + "version": "15.0.10", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.10.tgz", + "integrity": "sha512-z8PNtlhrj7eJNLmrAivM7rjBESG6JwC5xP3RVk12i/8HVP7Xnx/sEmERnRImyEuUaJfO942X0qMOYsoupaJbZQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + }, + "@typescript-eslint/eslint-plugin": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.0.tgz", + "integrity": "sha512-WrVzGMzzCrgrpnQMQm4Tnf+dk+wdl/YbgIgd5hKGa2P+lnJ2MON+nQnbwgbxtN9QDLi8HO+JAq0/krMnjQK6Cw==", + "requires": { + "@typescript-eslint/experimental-utils": "4.9.0", + "@typescript-eslint/scope-manager": "4.9.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.0.tgz", + "integrity": "sha512-0p8GnDWB3R2oGhmRXlEnCvYOtaBCijtA5uBfH5GxQKsukdSQyI4opC4NGTUb88CagsoNQ4rb/hId2JuMbzWKFQ==", + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.9.0", + "@typescript-eslint/types": "4.9.0", + "@typescript-eslint/typescript-estree": "4.9.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.9.0.tgz", + "integrity": "sha512-QRSDAV8tGZoQye/ogp28ypb8qpsZPV6FOLD+tbN4ohKUWHD2n/u0Q2tIBnCsGwQCiD94RdtLkcqpdK4vKcLCCw==", + "requires": { + "@typescript-eslint/scope-manager": "4.9.0", + "@typescript-eslint/types": "4.9.0", + "@typescript-eslint/typescript-estree": "4.9.0", + "debug": "^4.1.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.9.0.tgz", + "integrity": "sha512-q/81jtmcDtMRE+nfFt5pWqO0R41k46gpVLnuefqVOXl4QV1GdQoBWfk5REcipoJNQH9+F5l+dwa9Li5fbALjzg==", + "requires": { + "@typescript-eslint/types": "4.9.0", + "@typescript-eslint/visitor-keys": "4.9.0" + } + }, + "@typescript-eslint/types": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.9.0.tgz", + "integrity": "sha512-luzLKmowfiM/IoJL/rus1K9iZpSJK6GlOS/1ezKplb7MkORt2dDcfi8g9B0bsF6JoRGhqn0D3Va55b+vredFHA==" + }, + "@typescript-eslint/typescript-estree": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.0.tgz", + "integrity": "sha512-rmDR++PGrIyQzAtt3pPcmKWLr7MA+u/Cmq9b/rON3//t5WofNR4m/Ybft2vOLj0WtUzjn018ekHjTsnIyBsQug==", + "requires": { + "@typescript-eslint/types": "4.9.0", + "@typescript-eslint/visitor-keys": "4.9.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.0.tgz", + "integrity": "sha512-sV45zfdRqQo1A97pOSx3fsjR+3blmwtdCt8LDrXgCX36v4Vmz4KHrhpV6Fo2cRdXmyumxx11AHw0pNJqCNpDyg==", + "requires": { + "@typescript-eslint/types": "4.9.0", + "eslint-visitor-keys": "^2.0.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==" + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==" + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==" + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" + }, + "address": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", + "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==" + }, + "adjust-sourcemap-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", + "integrity": "sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw==", + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "arity-n": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", + "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=" + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + }, + "array-includes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", + "integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "get-intrinsic": "^1.0.1", + "is-string": "^1.0.5" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "array.prototype.flat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", + "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "array.prototype.flatmap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", + "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "function-bind": "^1.1.1" + } + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=" + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "autoprefixer": { + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "axe-core": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz", + "integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==" + }, + "axios": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" + }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } + } + }, + "babel-extract-comments": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz", + "integrity": "sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ==", + "requires": { + "babylon": "^6.18.0" + } + }, + "babel-jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", + "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", + "requires": { + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "babel-loader": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", + "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", + "requires": { + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", + "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + } + } + }, + "babel-plugin-named-asset-import": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz", + "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==" + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" + } + }, + "babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" + }, + "babel-preset-current-node-syntax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.0.tgz", + "integrity": "sha512-mGkvkpocWJes1CmMKtgGUwCeeq0pOhALyymozzDWYomHTbDLwueDYG6p4TK1YOeYHCzBzYPsWkgTto10JubI1Q==", + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", + "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "requires": { + "babel-plugin-jest-hoist": "^26.6.2", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "babel-preset-react-app": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.0.tgz", + "integrity": "sha512-itL2z8v16khpuKutx5IH8UdCdSTuzrOhRFTEdIhveZ2i1iBKDrVE0ATa4sFVy+02GLucZNVBWtoarXBy0Msdpg==", + "requires": { + "@babel/core": "7.12.3", + "@babel/plugin-proposal-class-properties": "7.12.1", + "@babel/plugin-proposal-decorators": "7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.12.1", + "@babel/plugin-proposal-numeric-separator": "7.12.1", + "@babel/plugin-proposal-optional-chaining": "7.12.1", + "@babel/plugin-transform-flow-strip-types": "7.12.1", + "@babel/plugin-transform-react-display-name": "7.12.1", + "@babel/plugin-transform-runtime": "7.12.1", + "@babel/preset-env": "7.12.1", + "@babel/preset-react": "7.12.1", + "@babel/preset-typescript": "7.12.1", + "@babel/runtime": "7.12.1", + "babel-plugin-macros": "2.8.0", + "babel-plugin-transform-react-remove-prop-types": "0.4.24" + }, + "dependencies": { + "@babel/plugin-proposal-numeric-separator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz", + "integrity": "sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz", + "integrity": "sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/preset-env": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.1.tgz", + "integrity": "sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg==", + "requires": { + "@babel/compat-data": "^7.12.1", + "@babel/helper-compilation-targets": "^7.12.1", + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-option": "^7.12.1", + "@babel/plugin-proposal-async-generator-functions": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-dynamic-import": "^7.12.1", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", + "@babel/plugin-proposal-json-strings": "^7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.1", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.1", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.12.1", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.12.1", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-async-to-generator": "^7.12.1", + "@babel/plugin-transform-block-scoped-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.1", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-computed-properties": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-dotall-regex": "^7.12.1", + "@babel/plugin-transform-duplicate-keys": "^7.12.1", + "@babel/plugin-transform-exponentiation-operator": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-function-name": "^7.12.1", + "@babel/plugin-transform-literals": "^7.12.1", + "@babel/plugin-transform-member-expression-literals": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/plugin-transform-modules-commonjs": "^7.12.1", + "@babel/plugin-transform-modules-systemjs": "^7.12.1", + "@babel/plugin-transform-modules-umd": "^7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", + "@babel/plugin-transform-new-target": "^7.12.1", + "@babel/plugin-transform-object-super": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-property-literals": "^7.12.1", + "@babel/plugin-transform-regenerator": "^7.12.1", + "@babel/plugin-transform-reserved-words": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/plugin-transform-sticky-regex": "^7.12.1", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.1", + "@babel/plugin-transform-unicode-escapes": "^7.12.1", + "@babel/plugin-transform-unicode-regex": "^7.12.1", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.12.1", + "core-js-compat": "^3.6.2", + "semver": "^5.5.0" + } + }, + "@babel/preset-react": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.12.1.tgz", + "integrity": "sha512-euCExymHCi0qB9u5fKw7rvlw7AZSjw/NaB9h7EkdTt5+yHRrXdiRTh7fkG3uBPpJg82CqLfp1LHLqWGSCrab+g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-react-display-name": "^7.12.1", + "@babel/plugin-transform-react-jsx": "^7.12.1", + "@babel/plugin-transform-react-jsx-development": "^7.12.1", + "@babel/plugin-transform-react-jsx-self": "^7.12.1", + "@babel/plugin-transform-react-jsx-source": "^7.12.1", + "@babel/plugin-transform-react-pure-annotations": "^7.12.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bfj": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", + "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", + "requires": { + "bluebird": "^3.5.5", + "check-types": "^11.1.1", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "optional": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.14.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.7.tgz", + "integrity": "sha512-BSVRLCeG3Xt/j/1cCGj1019Wbty0H+Yvu2AOuZSuoaUWn3RatbL33Cxk+Q4jRMRAbOm0p7SLravLjpnT6s0vzQ==", + "requires": { + "caniuse-lite": "^1.0.30001157", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.591", + "escalade": "^3.1.1", + "node-releases": "^1.1.66" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==" + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "camel-case": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", + "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", + "requires": { + "pascal-case": "^3.1.1", + "tslib": "^1.10.0" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001163", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001163.tgz", + "integrity": "sha512-QQbOGkHWnvhn3Dlf4scPlXTZVhGOK+2qCOP5gPxqzXHhtn3tZHwNdH9qNcQRWN0f3tDYrsyXFJCFiP/GLzI5Vg==" + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "requires": { + "rsvp": "^4.8.4" + } + }, + "case-sensitive-paths-webpack-plugin": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz", + "integrity": "sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" + }, + "check-types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", + "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==" + }, + "chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "dependencies": { + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "optional": true + } + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cjs-module-lexer": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", + "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, + "clean-css": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", + "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.4" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "compose-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz", + "integrity": "sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=", + "requires": { + "arity-n": "^1.0.4" + } + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "confusing-browser-globals": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", + "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==" + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-js": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.0.tgz", + "integrity": "sha512-W2VYNB0nwQQE7tKS7HzXd7r2y/y2SVJl4ga6oH/dnaLFzM0o2lB2P3zCkWj5Wc/zyMYjtgd5Hmhk0ObkQFZOIA==" + }, + "core-js-compat": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.0.tgz", + "integrity": "sha512-o9QKelQSxQMYWHXc/Gc4L8bx/4F7TTraE5rhuN8I7mKBt5dBIUpXpIR3omv70ebr8ST5R3PqbDQr+ZI3+Tt1FQ==", + "requires": { + "browserslist": "^4.14.7", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" + } + } + }, + "core-js-pure": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.0.tgz", + "integrity": "sha512-fRjhg3NeouotRoIV0L1FdchA6CK7ZD+lyINyMoz19SyV+ROpC4noS1xItWHFtwZdlqfMfVPJEyEGdfri2bD1pA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "requires": { + "postcss": "^7.0.5" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "css-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", + "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==", + "requires": { + "camelcase": "^6.0.0", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^2.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.3", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.1", + "semver": "^7.3.2" + } + }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "requires": { + "postcss": "^7.0.5" + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "requires": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" + }, + "cssdb": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "requires": { + "css-tree": "^1.1.2" + }, + "dependencies": { + "css-tree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", + "integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + } + } + }, + "csstype": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.14.tgz", + "integrity": "sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A==" + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "requires": { + "d3-time": "1" + } + }, + "damerau-levenshtein": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + }, + "decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==" + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + }, + "detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-accessibility-api": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", + "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==" + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "requires": { + "utila": "~0.4" + } + }, + "dom-helpers": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz", + "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==" + } + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==" + } + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + } + } + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", + "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", + "requires": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "requires": { + "is-obj": "^2.0.0" + }, + "dependencies": { + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + } + } + }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" + }, + "electron-to-chromium": { + "version": "1.3.611", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.611.tgz", + "integrity": "sha512-YhqTzCXtEO2h0foGLGS60ortd6yY/yUQhqDEp1VWG3DIyHvckFFyaRwR41M0/M3m7Yb8Exqh+nzyb2TuxaoMTw==" + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==" + }, + "emoji-regex": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.0.tgz", + "integrity": "sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "requires": { + "stackframe": "^1.1.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.14.0.tgz", + "integrity": "sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.2.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "eslint-config-react-app": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", + "integrity": "sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==", + "requires": { + "confusing-browser-globals": "^1.0.10" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "eslint-plugin-flowtype": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.2.0.tgz", + "integrity": "sha512-z7ULdTxuhlRJcEe1MVljePXricuPOrsWfScRXFhNzVD5dmTHWjIF57AxD0e7AbEoLSbjSsaA5S+hCg43WvpXJQ==", + "requires": { + "lodash": "^4.17.15", + "string-natural-compare": "^3.0.1" + } + }, + "eslint-plugin-import": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", + "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.1", + "read-pkg-up": "^2.0.0", + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "eslint-plugin-jest": { + "version": "24.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.3.tgz", + "integrity": "sha512-dNGGjzuEzCE3d5EPZQ/QGtmlMotqnYWD/QpCZ1UuZlrMAdhG5rldh0N0haCvhGnUkSeuORS5VNROwF9Hrgn3Lg==", + "requires": { + "@typescript-eslint/experimental-utils": "^4.0.1" + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz", + "integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==", + "requires": { + "@babel/runtime": "^7.11.2", + "aria-query": "^4.2.2", + "array-includes": "^3.1.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.0.2", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.6", + "emoji-regex": "^9.0.0", + "has": "^1.0.3", + "jsx-ast-utils": "^3.1.0", + "language-tags": "^1.0.5" + } + }, + "eslint-plugin-react": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz", + "integrity": "sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==", + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "object.entries": "^1.1.2", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.18.1", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", + "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==" + }, + "eslint-plugin-testing-library": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.1.tgz", + "integrity": "sha512-nQIFe2muIFv2oR2zIuXE4vTbcFNx8hZKRzgHZqJg8rfopIWwoTwtlbCCNELT/jXzVe1uZF68ALGYoDXjLczKiQ==", + "requires": { + "@typescript-eslint/experimental-utils": "^3.10.1" + }, + "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==" + }, + "@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "requires": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } + } + }, + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==" + }, + "eslint-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-j0lAJj3RnStAFdIH2P0+nsEImiBijwogZhL1go4bI6DE+9OhQuOmJ/xtmxkLtNr1w0cf5SRNkDlmIe8t/pHgww==", + "requires": { + "@types/eslint": "^7.2.4", + "arrify": "^2.0.1", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "espree": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==" + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==" + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "expect": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", + "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "requires": { + "@jest/types": "^26.6.2", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fastq": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", + "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "requires": { + "bser": "2.1.1" + } + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "requires": { + "flat-cache": "^2.0.1" + } + }, + "file-loader": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz", + "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "filesize": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", + "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==" + }, + "flatten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", + "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==" + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "fork-ts-checker-webpack-plugin": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", + "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", + "requires": { + "@babel/code-frame": "^7.5.5", + "chalk": "^2.4.1", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0", + "worker-rpc": "^0.1.0" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", + "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "optional": true + }, + "gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "requires": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + } + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "harmony-reflect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz", + "integrity": "sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" + }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==" + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "html-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==" + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "requires": { + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + } + }, + "html-webpack-plugin": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz", + "integrity": "sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw==", + "requires": { + "@types/html-minifier-terser": "^5.0.0", + "@types/tapable": "^1.0.5", + "@types/webpack": "^4.41.8", + "html-minifier-terser": "^5.0.1", + "loader-utils": "^1.2.3", + "lodash": "^4.17.15", + "pretty-error": "^2.1.1", + "tapable": "^1.1.3", + "util.promisify": "1.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + } + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" + }, + "hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "requires": { + "postcss": "^7.0.14" + } + }, + "identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=", + "requires": { + "harmony-reflect": "^1.4.6" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + }, + "immer": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz", + "integrity": "sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==" + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", + "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + } + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "dependencies": { + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "indefinite-observable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-2.0.1.tgz", + "integrity": "sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ==", + "requires": { + "symbol-observable": "1.2.0" + } + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==" + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=" + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=" + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, + "is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==" + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==" + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "26.6.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.0.tgz", + "integrity": "sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA==", + "requires": { + "@jest/core": "^26.6.0", + "import-local": "^3.0.2", + "jest-cli": "^26.6.0" + } + }, + "jest-changed-files": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", + "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", + "requires": { + "@jest/types": "^26.6.2", + "execa": "^4.0.0", + "throat": "^5.0.0" + } + }, + "jest-circus": { + "version": "26.6.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.6.0.tgz", + "integrity": "sha512-L2/Y9szN6FJPWFK8kzWXwfp+FOR7xq0cUL4lIsdbIdwz3Vh6P1nrpcqOleSzr28zOtSHQNV9Z7Tl+KkuK7t5Ng==", + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.0", + "@jest/test-result": "^26.6.0", + "@jest/types": "^26.6.0", + "@types/babel__traverse": "^7.0.4", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^26.6.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.0", + "jest-matcher-utils": "^26.6.0", + "jest-message-util": "^26.6.0", + "jest-runner": "^26.6.0", + "jest-runtime": "^26.6.0", + "jest-snapshot": "^26.6.0", + "jest-util": "^26.6.0", + "pretty-format": "^26.6.0", + "stack-utils": "^2.0.2", + "throat": "^5.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-cli": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", + "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", + "requires": { + "@jest/core": "^26.6.3", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.6.3", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "prompts": "^2.0.1", + "yargs": "^15.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-config": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", + "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.6.3", + "@jest/types": "^26.6.2", + "babel-jest": "^26.6.3", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.6.2", + "jest-environment-node": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.6.3", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", + "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-jsdom": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", + "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2", + "jsdom": "^16.4.0" + } + }, + "jest-environment-node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", + "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "jest-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "requires": { + "@jest/types": "^26.6.2", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", + "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "throat": "^5.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-leak-detector": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", + "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "requires": { + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-matcher-utils": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-mock": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", + "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" + }, + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==" + }, + "jest-resolve": { + "version": "26.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.0.tgz", + "integrity": "sha512-tRAz2bwraHufNp+CCmAD8ciyCpXCs1NQxB5EJAmtCFy6BN81loFEGWKzYu26Y62lAJJe4X4jg36Kf+NsQyiStQ==", + "requires": { + "@jest/types": "^26.6.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.0", + "read-pkg-up": "^7.0.1", + "resolve": "^1.17.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "jest-resolve-dependencies": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", + "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", + "requires": { + "@jest/types": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-snapshot": "^26.6.2" + } + }, + "jest-runner": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", + "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", + "requires": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.7.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.6.2", + "jest-leak-detector": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "jest-runtime": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", + "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", + "requires": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/globals": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^0.6.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "jest-serializer": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", + "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, + "jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-validate": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", + "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", + "requires": { + "@jest/types": "^26.6.2", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "leven": "^3.1.0", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watch-typeahead": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.1.tgz", + "integrity": "sha512-ITVnHhj3Jd/QkqQcTqZfRgjfyRhDFM/auzgVo2RKvSwi18YMvh0WvXDJFoFED6c7jd/5jxtu4kSOb9PTu2cPVg==", + "requires": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^26.0.0", + "jest-watcher": "^26.3.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watcher": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", + "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", + "requires": { + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^26.6.2", + "string-length": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jss": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.5.0.tgz", + "integrity": "sha512-B6151NvG+thUg3murLNHRPLxTLwQ13ep4SH5brj4d8qKtogOx/jupnpfkPGSHPqvcwKJaCLctpj2lEk+5yGwMw==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "indefinite-observable": "^2.0.1", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==" + } + } + }, + "jss-plugin-camel-case": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.5.0.tgz", + "integrity": "sha512-GSjPL0adGAkuoqeYiXTgO7PlIrmjv5v8lA6TTBdfxbNYpxADOdGKJgIEkffhlyuIZHlPuuiFYTwUreLUmSn7rg==", + "requires": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.5.0" + } + }, + "jss-plugin-default-unit": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.5.0.tgz", + "integrity": "sha512-rsbTtZGCMrbcb9beiDd+TwL991NGmsAgVYH0hATrYJtue9e+LH/Gn4yFD1ENwE+3JzF3A+rPnM2JuD9L/SIIWw==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.5.0" + } + }, + "jss-plugin-global": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.5.0.tgz", + "integrity": "sha512-FZd9+JE/3D7HMefEG54fEC0XiQ9rhGtDHAT/ols24y8sKQ1D5KIw6OyXEmIdKFmACgxZV2ARQ5pAUypxkk2IFQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.5.0" + } + }, + "jss-plugin-nested": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.5.0.tgz", + "integrity": "sha512-ejPlCLNlEGgx8jmMiDk/zarsCZk+DV0YqXfddpgzbO9Toamo0HweCFuwJ3ZO40UFOfqKwfpKMVH/3HUXgxkTMg==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.5.0", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-props-sort": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.5.0.tgz", + "integrity": "sha512-kTLRvrOetFKz5vM88FAhLNeJIxfjhCepnvq65G7xsAQ/Wgy7HwO1BS/2wE5mx8iLaAWC6Rj5h16mhMk9sKdZxg==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.5.0" + } + }, + "jss-plugin-rule-value-function": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.5.0.tgz", + "integrity": "sha512-jXINGr8BSsB13JVuK274oEtk0LoooYSJqTBCGeBu2cG/VJ3+4FPs1gwLgsq24xTgKshtZ+WEQMVL34OprLidRA==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.5.0", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-vendor-prefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.5.0.tgz", + "integrity": "sha512-rux3gmfwDdOKCLDx0IQjTwTm03IfBa+Rm/hs747cOw5Q7O3RaTUIMPKjtVfc31Xr/XI9Abz2XEupk1/oMQ7zRA==", + "requires": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.5.0" + } + }, + "jsx-ast-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz", + "integrity": "sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==", + "requires": { + "array-includes": "^3.1.1", + "object.assign": "^4.1.1" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + }, + "language-subtag-registry": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", + "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==" + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, + "last-call-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", + "requires": { + "lodash": "^4.17.5", + "webpack-sources": "^1.1.0" + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "loglevel": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", + "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lower-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", + "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", + "requires": { + "tslib": "^1.10.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=" + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "requires": { + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "material-ui-image": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/material-ui-image/-/material-ui-image-3.3.0.tgz", + "integrity": "sha512-6Gec+ZtcCnIH+WzcDFT4ayCoUeQouVs6WfZW5ZN7PFU3GITXQ974rJjM5laoMyK0pga1ZiHLMeBTaGxCYexYhg==", + "requires": { + "prop-types": "^15.5.8" + } + }, + "math-expression-evaluator": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.3.tgz", + "integrity": "sha512-geKTlqoxnjqHoWqB71h0kchWIC23a3yfwwbZu4E2amjvGLF+fTjCCwBQOHkE0/oHc6KdnSVmMt3QB82KaPmKEA==" + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "microevent.ts": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", + "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==" + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" + }, + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "requires": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + } + }, + "mini-css-extract-plugin": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", + "integrity": "sha512-n9BA8LonkOkW1/zn+IbLPQmovsL0wMb9yx75fMJQZf2X1Zoec9yTZtyMePcyu19wPkmFbzZZA6fLTotpFhQsOA==", + "requires": { + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" + }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "optional": true + }, + "nanoid": { + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.18.tgz", + "integrity": "sha512-rndlDjbbHbcV3xi+R2fpJ+PbGMdfBxz5v1fATIQFq0DP64FsicQdwnKLy47K4kZHdRpmQXtz24eGsxQqamzYTA==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "native-url": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/native-url/-/native-url-0.2.6.tgz", + "integrity": "sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA==", + "requires": { + "querystring": "^0.2.0" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "no-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", + "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", + "requires": { + "lower-case": "^2.0.1", + "tslib": "^1.10.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" + }, + "node-notifier": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz", + "integrity": "sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==", + "optional": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.2", + "shellwords": "^0.1.1", + "uuid": "^8.3.0", + "which": "^2.0.2" + } + }, + "node-releases": { + "version": "1.1.67", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", + "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==" + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "requires": { + "path-key": "^3.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-is": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", + "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", + "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.3.tgz", + "integrity": "sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz", + "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", + "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", + "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "requires": { + "is-wsl": "^1.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + } + } + }, + "optimize-css-assets-webpack-plugin": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz", + "integrity": "sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==", + "requires": { + "cssnano": "^4.1.10", + "last-call-webpack-plugin": "^3.0.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "requires": { + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "param-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", + "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", + "requires": { + "dot-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascal-case": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz", + "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==", + "requires": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "requires": { + "find-up": "^2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, + "pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, + "pnp-webpack-plugin": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", + "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", + "requires": { + "ts-pnp": "^1.1.6" + } + }, + "popper.js": { + "version": "1.16.1-lts", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", + "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-attribute-case-insensitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", + "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^6.0.2" + } + }, + "postcss-browser-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz", + "integrity": "sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig==", + "requires": { + "postcss": "^7" + } + }, + "postcss-calc": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "postcss-color-functional-notation": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-hex-alpha": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", + "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-color-mod-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-custom-media": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", + "requires": { + "postcss": "^7.0.14" + } + }, + "postcss-custom-properties": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", + "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", + "requires": { + "postcss": "^7.0.17", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-custom-selectors": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-double-position-gradients": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-env-function": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-flexbugs-fixes": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz", + "integrity": "sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==", + "requires": { + "postcss": "^7.0.26" + } + }, + "postcss-focus-visible": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-focus-within": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-font-variant": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz", + "integrity": "sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-gap-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-image-set-function": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-initial": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", + "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "requires": { + "lodash.template": "^4.5.0", + "postcss": "^7.0.2" + } + }, + "postcss-lab-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", + "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "postcss-logical": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-media-minmax": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "postcss-nesting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-normalize": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-8.0.1.tgz", + "integrity": "sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ==", + "requires": { + "@csstools/normalize.css": "^10.1.0", + "browserslist": "^4.6.2", + "postcss": "^7.0.17", + "postcss-browser-comments": "^3.0.0", + "sanitize.css": "^10.0.0" + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-page-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-place": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-preset-env": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", + "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", + "requires": { + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", + "postcss-attribute-case-insensitive": "^4.0.1", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.3", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" + } + }, + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-safe-parser": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-5.0.2.tgz", + "integrity": "sha512-jDUfCPJbKOABhwpUKcqCVbbXiloe/QXMcbJ6Iipf3sDIihEzTqRCeMBfRaOHxhBuTYqtASrI1KJWxzztZU4qUQ==", + "requires": { + "postcss": "^8.1.0" + }, + "dependencies": { + "postcss": { + "version": "8.1.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.1.10.tgz", + "integrity": "sha512-iBXEV5VTTYaRRdxiFYzTtuv2lGMQBExqkZKSzkJe+Fl6rvQrA/49UVGKqB+LG54hpW/TtDBMGds8j33GFNW7pg==", + "requires": { + "colorette": "^1.2.1", + "nanoid": "^3.1.18", + "source-map": "^0.6.1", + "vfile-location": "^3.2.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-selector-matches": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-not": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", + "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "postcss-values-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", + "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "pretty-bytes": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz", + "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==" + }, + "pretty-error": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", + "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "requires": { + "lodash": "^4.17.20", + "renderkid": "^2.0.4" + } + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "promise": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", + "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", + "requires": { + "asap": "~2.0.6" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" + }, + "prompts": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", + "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + } + } + }, + "react": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", + "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-app-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz", + "integrity": "sha512-0sF4ny9v/B7s6aoehwze9vJNWcmCemAUYBVasscVr92+UYiEqDXOxfKjXN685mDaMRNF3WdhHQs76oTODMocFA==", + "requires": { + "core-js": "^3.6.5", + "object-assign": "^4.1.1", + "promise": "^8.1.0", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "whatwg-fetch": "^3.4.1" + } + }, + "react-dev-utils": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.1.tgz", + "integrity": "sha512-rlgpCupaW6qQqvu0hvv2FDv40QG427fjghV56XyPcP5aKtOAPzNAhQ7bHqk1YdS2vpW1W7aSV3JobedxuPlBAA==", + "requires": { + "@babel/code-frame": "7.10.4", + "address": "1.1.2", + "browserslist": "4.14.2", + "chalk": "2.4.2", + "cross-spawn": "7.0.3", + "detect-port-alt": "1.1.6", + "escape-string-regexp": "2.0.0", + "filesize": "6.1.0", + "find-up": "4.1.0", + "fork-ts-checker-webpack-plugin": "4.1.6", + "global-modules": "2.0.0", + "globby": "11.0.1", + "gzip-size": "5.1.1", + "immer": "7.0.9", + "is-root": "2.1.0", + "loader-utils": "2.0.0", + "open": "^7.0.2", + "pkg-up": "3.1.0", + "prompts": "2.4.0", + "react-error-overlay": "^6.0.8", + "recursive-readdir": "2.2.2", + "shell-quote": "1.7.2", + "strip-ansi": "6.0.0", + "text-table": "0.2.0" + }, + "dependencies": { + "browserslist": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", + "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", + "requires": { + "caniuse-lite": "^1.0.30001125", + "electron-to-chromium": "^1.3.564", + "escalade": "^3.0.2", + "node-releases": "^1.1.61" + } + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + } + } + }, + "react-dom": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", + "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.1" + } + }, + "react-error-overlay": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", + "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==" + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-refresh": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", + "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==" + }, + "react-resize-detector": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-2.3.0.tgz", + "integrity": "sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ==", + "requires": { + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "prop-types": "^15.6.0", + "resize-observer-polyfill": "^1.5.0" + } + }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-scripts": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.1.tgz", + "integrity": "sha512-NnniMSC/wjwhcJAyPJCWtxx6CWONqgvGgV9+QXj1bwoW/JI++YF1eEf3Upf/mQ9KmP57IBdjzWs1XvnPq7qMTQ==", + "requires": { + "@babel/core": "7.12.3", + "@pmmmwh/react-refresh-webpack-plugin": "0.4.2", + "@svgr/webpack": "5.4.0", + "@typescript-eslint/eslint-plugin": "^4.5.0", + "@typescript-eslint/parser": "^4.5.0", + "babel-eslint": "^10.1.0", + "babel-jest": "^26.6.0", + "babel-loader": "8.1.0", + "babel-plugin-named-asset-import": "^0.3.7", + "babel-preset-react-app": "^10.0.0", + "bfj": "^7.0.2", + "camelcase": "^6.1.0", + "case-sensitive-paths-webpack-plugin": "2.3.0", + "css-loader": "4.3.0", + "dotenv": "8.2.0", + "dotenv-expand": "5.1.0", + "eslint": "^7.11.0", + "eslint-config-react-app": "^6.0.0", + "eslint-plugin-flowtype": "^5.2.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jest": "^24.1.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-react": "^7.21.5", + "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-testing-library": "^3.9.2", + "eslint-webpack-plugin": "^2.1.0", + "file-loader": "6.1.1", + "fs-extra": "^9.0.1", + "fsevents": "^2.1.3", + "html-webpack-plugin": "4.5.0", + "identity-obj-proxy": "3.0.0", + "jest": "26.6.0", + "jest-circus": "26.6.0", + "jest-resolve": "26.6.0", + "jest-watch-typeahead": "0.6.1", + "mini-css-extract-plugin": "0.11.3", + "optimize-css-assets-webpack-plugin": "5.0.4", + "pnp-webpack-plugin": "1.6.4", + "postcss-flexbugs-fixes": "4.2.1", + "postcss-loader": "3.0.0", + "postcss-normalize": "8.0.1", + "postcss-preset-env": "6.7.0", + "postcss-safe-parser": "5.0.2", + "prompts": "2.4.0", + "react-app-polyfill": "^2.0.0", + "react-dev-utils": "^11.0.1", + "react-refresh": "^0.8.3", + "resolve": "1.18.1", + "resolve-url-loader": "^3.1.2", + "sass-loader": "8.0.2", + "semver": "7.3.2", + "style-loader": "1.3.0", + "terser-webpack-plugin": "4.2.3", + "ts-pnp": "1.2.0", + "url-loader": "4.1.1", + "webpack": "4.44.2", + "webpack-dev-server": "3.11.0", + "webpack-manifest-plugin": "2.2.0", + "workbox-webpack-plugin": "5.1.4" + } + }, + "react-smooth": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-1.0.5.tgz", + "integrity": "sha512-eW057HT0lFgCKh8ilr0y2JaH2YbNcuEdFpxyg7Gf/qDKk9hqGMyXryZJ8iMGJEuKH0+wxS0ccSsBBB3W8yCn8w==", + "requires": { + "lodash": "~4.17.4", + "prop-types": "^15.6.0", + "raf": "^3.4.0", + "react-transition-group": "^2.5.0" + }, + "dependencies": { + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, + "react-transition-group": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", + "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "dependencies": { + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "recharts": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-1.8.5.tgz", + "integrity": "sha512-tM9mprJbXVEBxjM7zHsIy6Cc41oO/pVYqyAsOHLxlJrbNBuLs0PHB3iys2M+RqCF0//k8nJtZF6X6swSkWY3tg==", + "requires": { + "classnames": "^2.2.5", + "core-js": "^2.6.10", + "d3-interpolate": "^1.3.0", + "d3-scale": "^2.1.0", + "d3-shape": "^1.2.0", + "lodash": "^4.17.5", + "prop-types": "^15.6.0", + "react-resize-detector": "^2.3.0", + "react-smooth": "^1.0.5", + "recharts-scale": "^0.4.2", + "reduce-css-calc": "^1.3.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + } + } + }, + "recharts-scale": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.3.tgz", + "integrity": "sha512-t8p5sccG9Blm7c1JQK/ak9O8o95WGhNXD7TXg/BW5bYbVlr6eCeRBNpgyigD4p6pSSMehC5nSvBUPj6F68rbFA==", + "requires": { + "decimal.js-light": "^2.4.1" + } + }, + "recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "requires": { + "minimatch": "3.0.4" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + } + } + }, + "reduce-function-call": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", + "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==" + }, + "regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "renderkid": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz", + "integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==", + "requires": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "lodash": "^4.17.20", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "resolve": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", + "requires": { + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "resolve-url-loader": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz", + "integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==", + "requires": { + "adjust-sourcemap-loader": "3.0.0", + "camelcase": "5.3.1", + "compose-function": "3.0.3", + "convert-source-map": "1.7.0", + "es6-iterator": "2.0.3", + "loader-utils": "1.2.3", + "postcss": "7.0.21", + "rework": "1.0.1", + "rework-visit": "1.0.0", + "source-map": "0.6.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "postcss": { + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz", + "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rework": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz", + "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=", + "requires": { + "convert-source-map": "^0.3.3", + "css": "^2.0.0" + }, + "dependencies": { + "convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=" + } + } + }, + "rework-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz", + "integrity": "sha1-mUWygD8hni96ygCtuLyfZA+ELJo=" + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rollup": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "requires": { + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" + } + }, + "rollup-plugin-babel": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", + "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-terser": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", + "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", + "requires": { + "@babel/code-frame": "^7.5.5", + "jest-worker": "^24.9.0", + "rollup-pluginutils": "^2.8.2", + "serialize-javascript": "^4.0.0", + "terser": "^4.6.2" + }, + "dependencies": { + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "requires": { + "estree-walker": "^0.6.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + } + } + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" + }, + "run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==" + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "requires": { + "aproba": "^1.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "sanitize.css": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz", + "integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg==" + }, + "sass-loader": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", + "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", + "requires": { + "clone-deep": "^4.0.1", + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.6.1", + "semver": "^6.3.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "requires": { + "xmlchars": "^2.2.0" + } + }, + "scheduler": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz", + "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" + }, + "selfsigned": { + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "optional": true + }, + "side-channel": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", + "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "requires": { + "es-abstract": "^1.18.0-next.0", + "object-inspect": "^1.8.0" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sockjs": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", + "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.4.0", + "websocket-driver": "0.6.5" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, + "sockjs-client": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", + "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==" + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "requires": { + "minipass": "^3.1.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + } + } + }, + "stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + }, + "string-length": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", + "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string.prototype.matchall": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz", + "integrity": "sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3" + } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" + }, + "strip-comments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz", + "integrity": "sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw==", + "requires": { + "babel-extract-comments": "^1.0.0", + "babel-plugin-transform-object-rest-spread": "^6.26.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "style-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", + "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.7.0" + } + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "dependencies": { + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + } + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" + }, + "tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, + "temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" + }, + "tempy": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", + "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", + "requires": { + "temp-dir": "^1.0.0", + "type-fest": "^0.3.1", + "unique-string": "^1.0.0" + }, + "dependencies": { + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" + } + } + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "terser-webpack-plugin": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", + "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", + "requires": { + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.5.0", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "source-map": "^0.6.1", + "terser": "^5.3.4", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "terser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz", + "integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==", + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "requires": { + "setimmediate": "^1.0.4" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "requires": { + "punycode": "^2.1.1" + } + }, + "tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, + "ts-pnp": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", + "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" + }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz", + "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + } + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", + "optional": true + }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==" + }, + "v8-to-istanbul": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", + "integrity": "sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vfile-location": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.2.0.tgz", + "integrity": "sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==" + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "requires": { + "makeerror": "1.0.x" + } + }, + "watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + } + }, + "watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "optional": true, + "requires": { + "chokidar": "^2.1.8" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "web-vitals": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-0.2.4.tgz", + "integrity": "sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==" + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" + }, + "webpack": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.3.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } + } + }, + "webpack-dev-server": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", + "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.3.1", + "http-proxy-middleware": "0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.8", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.26", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "0.3.20", + "sockjs-client": "1.4.0", + "spdy": "^4.0.2", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.2", + "webpack-log": "^2.0.0", + "ws": "^6.2.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, + "webpack-manifest-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz", + "integrity": "sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==", + "requires": { + "fs-extra": "^7.0.0", + "lodash": ">=3.5 <5", + "object.entries": "^1.1.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + } + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "requires": { + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-fetch": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz", + "integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==" + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "whatwg-url": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", + "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "workbox-background-sync": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-5.1.4.tgz", + "integrity": "sha512-AH6x5pYq4vwQvfRDWH+vfOePfPIYQ00nCEB7dJRU1e0n9+9HMRyvI63FlDvtFT2AvXVRsXvUt7DNMEToyJLpSA==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "workbox-broadcast-update": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-5.1.4.tgz", + "integrity": "sha512-HTyTWkqXvHRuqY73XrwvXPud/FN6x3ROzkfFPsRjtw/kGZuZkPzfeH531qdUGfhtwjmtO/ZzXcWErqVzJNdXaA==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "workbox-build": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-5.1.4.tgz", + "integrity": "sha512-xUcZn6SYU8usjOlfLb9Y2/f86Gdo+fy1fXgH8tJHjxgpo53VVsqRX0lUDw8/JuyzNmXuo8vXX14pXX2oIm9Bow==", + "requires": { + "@babel/core": "^7.8.4", + "@babel/preset-env": "^7.8.4", + "@babel/runtime": "^7.8.4", + "@hapi/joi": "^15.1.0", + "@rollup/plugin-node-resolve": "^7.1.1", + "@rollup/plugin-replace": "^2.3.1", + "@surma/rollup-plugin-off-main-thread": "^1.1.1", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^8.1.0", + "glob": "^7.1.6", + "lodash.template": "^4.5.0", + "pretty-bytes": "^5.3.0", + "rollup": "^1.31.1", + "rollup-plugin-babel": "^4.3.3", + "rollup-plugin-terser": "^5.3.1", + "source-map": "^0.7.3", + "source-map-url": "^0.4.0", + "stringify-object": "^3.3.0", + "strip-comments": "^1.0.2", + "tempy": "^0.3.0", + "upath": "^1.2.0", + "workbox-background-sync": "^5.1.4", + "workbox-broadcast-update": "^5.1.4", + "workbox-cacheable-response": "^5.1.4", + "workbox-core": "^5.1.4", + "workbox-expiration": "^5.1.4", + "workbox-google-analytics": "^5.1.4", + "workbox-navigation-preload": "^5.1.4", + "workbox-precaching": "^5.1.4", + "workbox-range-requests": "^5.1.4", + "workbox-routing": "^5.1.4", + "workbox-strategies": "^5.1.4", + "workbox-streams": "^5.1.4", + "workbox-sw": "^5.1.4", + "workbox-window": "^5.1.4" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + } + } + }, + "workbox-cacheable-response": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-5.1.4.tgz", + "integrity": "sha512-0bfvMZs0Of1S5cdswfQK0BXt6ulU5kVD4lwer2CeI+03czHprXR3V4Y8lPTooamn7eHP8Iywi5QjyAMjw0qauA==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "workbox-core": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz", + "integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg==" + }, + "workbox-expiration": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-5.1.4.tgz", + "integrity": "sha512-oDO/5iC65h2Eq7jctAv858W2+CeRW5e0jZBMNRXpzp0ZPvuT6GblUiHnAsC5W5lANs1QS9atVOm4ifrBiYY7AQ==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "workbox-google-analytics": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz", + "integrity": "sha512-0IFhKoEVrreHpKgcOoddV+oIaVXBFKXUzJVBI+nb0bxmcwYuZMdteBTp8AEDJacENtc9xbR0wa9RDCnYsCDLjA==", + "requires": { + "workbox-background-sync": "^5.1.4", + "workbox-core": "^5.1.4", + "workbox-routing": "^5.1.4", + "workbox-strategies": "^5.1.4" + } + }, + "workbox-navigation-preload": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-5.1.4.tgz", + "integrity": "sha512-Wf03osvK0wTflAfKXba//QmWC5BIaIZARU03JIhAEO2wSB2BDROWI8Q/zmianf54kdV7e1eLaIEZhth4K4MyfQ==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "workbox-precaching": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-5.1.4.tgz", + "integrity": "sha512-gCIFrBXmVQLFwvAzuGLCmkUYGVhBb7D1k/IL7pUJUO5xacjLcFUaLnnsoVepBGAiKw34HU1y/YuqvTKim9qAZA==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "workbox-range-requests": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-5.1.4.tgz", + "integrity": "sha512-1HSujLjgTeoxHrMR2muDW2dKdxqCGMc1KbeyGcmjZZAizJTFwu7CWLDmLv6O1ceWYrhfuLFJO+umYMddk2XMhw==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "workbox-routing": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-5.1.4.tgz", + "integrity": "sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "workbox-strategies": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-5.1.4.tgz", + "integrity": "sha512-VVS57LpaJTdjW3RgZvPwX0NlhNmscR7OQ9bP+N/34cYMDzXLyA6kqWffP6QKXSkca1OFo/v6v7hW7zrrguo6EA==", + "requires": { + "workbox-core": "^5.1.4", + "workbox-routing": "^5.1.4" + } + }, + "workbox-streams": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-5.1.4.tgz", + "integrity": "sha512-xU8yuF1hI/XcVhJUAfbQLa1guQUhdLMPQJkdT0kn6HP5CwiPOGiXnSFq80rAG4b1kJUChQQIGPrq439FQUNVrw==", + "requires": { + "workbox-core": "^5.1.4", + "workbox-routing": "^5.1.4" + } + }, + "workbox-sw": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-5.1.4.tgz", + "integrity": "sha512-9xKnKw95aXwSNc8kk8gki4HU0g0W6KXu+xks7wFuC7h0sembFnTrKtckqZxbSod41TDaGh+gWUA5IRXrL0ECRA==" + }, + "workbox-webpack-plugin": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-5.1.4.tgz", + "integrity": "sha512-PZafF4HpugZndqISi3rZ4ZK4A4DxO8rAqt2FwRptgsDx7NF8TVKP86/huHquUsRjMGQllsNdn4FNl8CD/UvKmQ==", + "requires": { + "@babel/runtime": "^7.5.5", + "fast-json-stable-stringify": "^2.0.0", + "source-map-url": "^0.4.0", + "upath": "^1.1.2", + "webpack-sources": "^1.3.0", + "workbox-build": "^5.1.4" + } + }, + "workbox-window": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-5.1.4.tgz", + "integrity": "sha512-vXQtgTeMCUq/4pBWMfQX8Ee7N2wVC4Q7XYFqLnfbXJ2hqew/cU1uMTD2KqGEgEpE4/30luxIxgE+LkIa8glBYw==", + "requires": { + "workbox-core": "^5.1.4" + } + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "requires": { + "errno": "~0.1.7" + } + }, + "worker-rpc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", + "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", + "requires": { + "microevent.ts": "~0.1.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", + "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==" + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/package.json b/Advanced/secretsanta-cordapp/clients/src/main/webapp/package.json new file mode 100644 index 00000000..e71ed6ee --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/package.json @@ -0,0 +1,46 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "dependencies": { + "@material-ui/core": "^4.11.1", + "@material-ui/icons": "^4.9.1", + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.3.0", + "axios": "^0.21.0", + "material-ui-image": "^3.3.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", + "react-scripts": "4.0.1", + "recharts": "^1.8.5", + "typescript": "^4.1.2", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/favicon.ico b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/favicon.ico new file mode 100644 index 00000000..324eb473 Binary files /dev/null and b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/favicon.ico differ diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/img/reindeer-flying.png b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/img/reindeer-flying.png new file mode 100644 index 00000000..06aca0b1 Binary files /dev/null and b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/img/reindeer-flying.png differ diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/index.html b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/index.html new file mode 100644 index 00000000..aa069f27 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +

+ + + diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/manifest.json b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/manifest.json new file mode 100644 index 00000000..b40987e2 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Secret Corda", + "name": "Corda Secret Santa from R3", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/robots.txt b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/App.css b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/App.css new file mode 100644 index 00000000..74b5e053 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/App.test.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/App.test.js new file mode 100644 index 00000000..1f03afee --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/App.test.js @@ -0,0 +1,8 @@ +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/App/App.css b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/App/App.css new file mode 100644 index 00000000..74b5e053 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/App/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/App/App.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/App/App.js new file mode 100644 index 00000000..fd403901 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/App/App.js @@ -0,0 +1,33 @@ +import React from 'react'; + +import { + BrowserRouter as Router, + Switch, + Route +} from "react-router-dom"; + +import './App.css'; + +import CreateSantaGame from '../CreateSantaGame/CreateSantaGame.js'; +import CheckSantaGame from '../CheckSantaGame/CheckSantaGame'; +import SantaGameCreated from '../SantaGameCreated/SantaGameCreated'; +import SantaCheckSent from '../SantaCheckSent/SantaCheckSent'; + +function App(props) { + + return ( + +
+ + + + + + + +
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CONSTANTS.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CONSTANTS.js new file mode 100644 index 00000000..78de5209 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CONSTANTS.js @@ -0,0 +1,9 @@ +const PROTOCOL = 'http://' +const PORT = '10056' // note that this is the port of the santaServer! (from clients build.gradle: '--server.port=10056') +const HOSTNAME = 'localhost' +const SEND_EMAIL = false // set this to true if you've configured sendgrid to send emails on the backend + + +const BACKEND_URL = PROTOCOL + HOSTNAME + ':' + PORT; + +export { BACKEND_URL, SEND_EMAIL }; diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CheckSantaGame/CheckSantaGame.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CheckSantaGame/CheckSantaGame.js new file mode 100644 index 00000000..30adcd80 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CheckSantaGame/CheckSantaGame.js @@ -0,0 +1,206 @@ +import React, { useState, useEffect } from 'react'; +import Avatar from '@material-ui/core/Avatar'; +import Button from '@material-ui/core/Button'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import TextField from '@material-ui/core/TextField'; +import Link from '@material-ui/core/Link'; +import Paper from '@material-ui/core/Paper'; +import Box from '@material-ui/core/Box'; +import Grid from '@material-ui/core/Grid'; +import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; +import { BACKEND_URL, SEND_EMAIL } from '../CONSTANTS.js'; +import { useHistory, withRouter } from 'react-router-dom'; +import axios from 'axios'; + +function Copyright() { + return ( + + {'Copyright © '} + + R3 + {' '} + {new Date().getFullYear()} + {'.'} + + ); +} + +const useStyles = makeStyles((theme) => ({ + root: { + height: '100vh', + }, + image: { + backgroundImage: 'url(https://source.unsplash.com/random)', + backgroundRepeat: 'no-repeat', + backgroundColor: + theme.palette.type === 'light' ? theme.palette.grey[50] : theme.palette.grey[900], + backgroundSize: 'cover', + backgroundPosition: 'center', + }, + paper: { + margin: theme.spacing(8, 4), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + avatar: { + margin: theme.spacing(1), + backgroundColor: theme.palette.secondary.main, + }, + form: { + width: '100%', // Fix IE 11 issue. + marginTop: theme.spacing(1), + }, + submit: { + margin: theme.spacing(3, 0, 2), + }, +})); + + +function CheckSantaGame(props) { + + const classes = useStyles(); + const history = useHistory(); + + const [response, setResponse] = useState(null); + + // state variables and related functions + const [gameId, setGameId] = useState("12345678"); + const [name, setName] = useState("david"); + + const gameIdChange = e => setGameId(e.target.value); + const nameChange = e => setName(e.target.value); + + const buttonHandler = (e) => { + e.preventDefault(); + + console.log(name); + console.log(gameId); + + const data = { + name: name.trim(), + gameId: gameId.trim(), + sendEmail: SEND_EMAIL, + } + + axios.post( + BACKEND_URL + '/games/check', + data, + { + headers: { 'Content-Type': 'application/json' } + } + ).then(res => { + console.log("RESPONSE: ", res.data); + + const target = res.data.target; + + if (target !== null) { + const secret_msg = "🧝‍♂️ - Psst! It's " + target + "!"; + console.log(secret_msg) + + if (!SEND_EMAIL) { + alert(secret_msg); + } + setResponse(res); + } + + }); + } + + useEffect(() => { + if (response !== null) { + let path = "/checked"; + history.push(path); + } + }, [response]); // eslint-disable-line react-hooks/exhaustive-deps + + return ( + + + + +
+ + + + + Check Santa Details + +
+ + + + + + + +
+ + + + {/* + Forgot password? + */} + + +
+ + {"Don't have a game? Make one 🎁 "} + + +
+
+ + + + +
+
+
+ ); +} + +export default withRouter(CheckSantaGame); diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CreateSantaGame/CreateSantaGame.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CreateSantaGame/CreateSantaGame.js new file mode 100644 index 00000000..c9f736df --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/CreateSantaGame/CreateSantaGame.js @@ -0,0 +1,281 @@ +import React, { useState, useEffect } from 'react'; +import Button from '@material-ui/core/Button'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import TextField from '@material-ui/core/TextField'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Checkbox from '@material-ui/core/Checkbox'; +import Link from '@material-ui/core/Link'; +import Grid from '@material-ui/core/Grid'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; +import Container from '@material-ui/core/Container'; +import { useHistory, withRouter } from 'react-router-dom'; +import axios from 'axios'; + +import secret_corda from '../img/secret_corda.png'; +import { BACKEND_URL, SEND_EMAIL } from '../CONSTANTS.js'; + +// function validateEmail(email) { +// const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; +// return re.test(String(email).toLowerCase()); +// } + +function Copyright() { + return ( + + {'Copyright © '} + + R3 + {' '} + {new Date().getFullYear()} + {'.'} + + ); +} + +const useStyles = makeStyles((theme) => ({ + paper: { + marginTop: theme.spacing(8), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + avatar: { + margin: theme.spacing(1), + backgroundColor: theme.palette.secondary.main, + }, + form: { + width: '100%', // Fix IE 11 issue. + marginTop: theme.spacing(3), + }, + submit: { + margin: theme.spacing(3, 0, 2), + }, +})); + +function CreateSantaGame(props) { + + // state variables and related functions + const [name1, setName1] = useState("david"); + const [name2, setName2] = useState("peter"); + const [name3, setName3] = useState("mary"); + + const name1Change = e => setName1(e.target.value); + const name2Change = e => setName2(e.target.value); + const name3Change = e => setName3(e.target.value); + + const [email1, setEmail1] = useState("david@corda.net"); + const [email2, setEmail2] = useState("peter@corda.net"); + const [email3, setEmail3] = useState("mary@corda.net"); + + const email1Change = e => setEmail1(e.target.value); + const email2Change = e => setEmail2(e.target.value); + const email3Change = e => setEmail3(e.target.value); + + let playerNames = [name1, name2, name3]; + let playerEmails = [email1, email2, email3]; + + const classes = useStyles(); + const history = useHistory(); + + const [response, setResponse] = useState(null); + + function buttonHandler(e) { + e.preventDefault(); + + console.log(playerNames); + console.log(playerEmails); + + const data = { + playerNames: playerNames, + playerEmails: playerEmails, + sendEmail: SEND_EMAIL + } + + axios.post( + BACKEND_URL + '/games', + data, + { headers: { 'Content-Type': 'application/json' } } + ).then(res => { + const gameId = res.data.gameId; + + if (gameId !== null) { + const secret_msg = "❄️ The elves 🧝‍♂️ have set aside some space " + gameId + " in santa's workshop! ❄️"; + console.log(secret_msg); + + if (!SEND_EMAIL) { + alert(secret_msg); + } + + setResponse(res); + } + + }); + + }; + + // note for anyone curious how this useEffect binds to the response + // https://stackoverflow.com/questions/63603966/react-api-call-with-axios-how-to-bind-an-onclick-event-with-an-api-call + + useEffect(() => { + if (response !== null) { + let path = "/created"; + history.push(path); + } + }, [response]); // eslint-disable-line react-hooks/exhaustive-deps + + return ( + + +
+ + corda logo with santa hat + + + Corda Secret Santa! + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + } + label="We want to receive 🦌 reindeer, 🎁 marketing promotions and 🍭 candy via email." + /> + +
+
+ +
+
+ + + + + +
+ + {"Looking up a game? Check a Santa 🎅 ID."} + +
+
+
+ +
+ + + +
+ ); +} + +export default withRouter(CreateSantaGame); \ No newline at end of file diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/SantaCheckSent/SantaCheckSent.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/SantaCheckSent/SantaCheckSent.js new file mode 100644 index 00000000..c0d46b64 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/SantaCheckSent/SantaCheckSent.js @@ -0,0 +1,88 @@ +import React from 'react'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import Link from '@material-ui/core/Link'; +import Grid from '@material-ui/core/Grid'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; +import Container from '@material-ui/core/Container'; +import secret_corda from '../img/secret_corda.png'; + +function Copyright() { + return ( + + {'Copyright © '} + + R3 + {' '} + {new Date().getFullYear()} + {'.'} + + ); +} + +const useStyles = makeStyles((theme) => ({ + paper: { + marginTop: theme.spacing(8), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + avatar: { + margin: theme.spacing(1), + backgroundColor: theme.palette.secondary.main, + }, + form: { + width: '100%', // Fix IE 11 issue. + marginTop: theme.spacing(3), + }, + submit: { + margin: theme.spacing(3, 0, 2), + }, +})); + +export default function SantaCheckSent(props) { + const classes = useStyles(); + + return ( + + +
+ + + + + Help is on the sleigh! + + +
+ + + Everyone forgets sometimes. Not to worry! The elves have sent a reminder to your email (check your spam chimney 📨!)! + + Make sure you don't tell anyone who the elves assigned you! 🤫 + + Good luck! 🎄 + + + + + + + + + + Don't have a game? Make one 🎁 + + + + +
+ + + + +
+ ); + +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/SantaGameCreated/SantaGameCreated.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/SantaGameCreated/SantaGameCreated.js new file mode 100644 index 00000000..5dd1f3e5 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/SantaGameCreated/SantaGameCreated.js @@ -0,0 +1,94 @@ +import React from 'react'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import Link from '@material-ui/core/Link'; +import Grid from '@material-ui/core/Grid'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; +import Container from '@material-ui/core/Container'; + +import secret_corda from '../img/secret_corda.png'; +import santa_flying from '../img/reindeer-flying.png'; + + +function Copyright() { + return ( + + {'Copyright © '} + + R3 + {' '} + {new Date().getFullYear()} + {'.'} + + ); +} + +const useStyles = makeStyles((theme) => ({ + paper: { + marginTop: theme.spacing(8), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + avatar: { + margin: theme.spacing(1), + backgroundColor: theme.palette.secondary.main, + }, + form: { + width: '100%', // Fix IE 11 issue. + marginTop: theme.spacing(3), + }, + submit: { + margin: theme.spacing(3, 0, 2), + }, +})); + +export default function SantaGameCreated(props) { + const classes = useStyles(); + + return ( + + +
+ + + + + The Game is on! + + +
+ + + The elves have sent the assignments to each of the participants so your friends know where to send their gifts 🎁! (Check your spam chimney!) + + Make sure you don't tell anyone who the elves assigned you! 🤫 For the best results you should wait to open your gifts altogether and guess who the elves picked to get each gift. + +
+
+ + Merry Christmas from all of us at R3. 🎄 +
+ + + + + + + + + {"Looking up a game? Check a Santa 🎅 ID."} + + + + +
+ + + + +
+ ); + +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/img/reindeer-flying.png b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/img/reindeer-flying.png new file mode 100644 index 00000000..06aca0b1 Binary files /dev/null and b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/img/reindeer-flying.png differ diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/img/secret_corda.png b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/img/secret_corda.png new file mode 100644 index 00000000..b5f1c38c Binary files /dev/null and b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/Components/img/secret_corda.png differ diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/index.css b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/index.css new file mode 100644 index 00000000..ec2585e8 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/index.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/index.js new file mode 100644 index 00000000..06d8f1e4 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/index.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './Components/App/App'; + +ReactDOM.render( + , + document.getElementById('root') +); \ No newline at end of file diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/react-app-env.d.ts b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/react-app-env.d.ts new file mode 100644 index 00000000..6431bc5f --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/reportWebVitals.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/reportWebVitals.js new file mode 100644 index 00000000..5253d3ad --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/serviceWorker.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/serviceWorker.js new file mode 100644 index 00000000..b04b771a --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/serviceWorker.js @@ -0,0 +1,141 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, + }) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready + .then(registration => { + registration.unregister(); + }) + .catch(error => { + console.error(error.message); + }); + } +} diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/setupTests.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/setupTests.js new file mode 100644 index 00000000..8f2609b7 --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/theme.js b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/theme.js new file mode 100644 index 00000000..e187a30c --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/src/theme.js @@ -0,0 +1,40 @@ +import { red } from '@material-ui/core/colors'; +import { createMuiTheme } from '@material-ui/core/styles'; + +// A custom theme for this app +const theme = createMuiTheme({ + palette: { + type: 'light', + primary: { + main: '#61dafb', + light: '#61dafb', + dark: '#21a1c4', + }, + secondary: { + main: '#b5ecfb', + light: '#61dafb', + dark: '#21a1c4', + }, + error: { + main: red.A400, + }, + background: { + default: '#282c34', + }, + }, + overrides: { + MuiPaper: { + root: { + padding: '20px 10px', + margin: '10px', + backgroundColor: '#fff', // 5d737e + }, + }, + MuiButton: { + root: { + margin: '5px', + }, + }, + }, +}); +export default theme; diff --git a/Advanced/secretsanta-cordapp/clients/src/main/webapp/tsconfig.json b/Advanced/secretsanta-cordapp/clients/src/main/webapp/tsconfig.json new file mode 100644 index 00000000..e18c413e --- /dev/null +++ b/Advanced/secretsanta-cordapp/clients/src/main/webapp/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src" + ] +} diff --git a/Basic/rpc-nodeinfo/config/dev/log4j2.xml b/Advanced/secretsanta-cordapp/config/dev/log4j2.xml similarity index 100% rename from Basic/rpc-nodeinfo/config/dev/log4j2.xml rename to Advanced/secretsanta-cordapp/config/dev/log4j2.xml diff --git a/Basic/ping-pong/client-java/src/test/resources/log4j2-test.xml b/Advanced/secretsanta-cordapp/config/test/log4j2.xml similarity index 100% rename from Basic/ping-pong/client-java/src/test/resources/log4j2-test.xml rename to Advanced/secretsanta-cordapp/config/test/log4j2.xml diff --git a/Advanced/secretsanta-cordapp/contracts/build.gradle b/Advanced/secretsanta-cordapp/contracts/build.gradle new file mode 100644 index 00000000..48c20f54 --- /dev/null +++ b/Advanced/secretsanta-cordapp/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Secret Santa Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} diff --git a/Advanced/secretsanta-cordapp/contracts/src/main/java/net/corda/samples/secretsanta/contracts/SantaSessionContract.java b/Advanced/secretsanta-cordapp/contracts/src/main/java/net/corda/samples/secretsanta/contracts/SantaSessionContract.java new file mode 100644 index 00000000..0c521daa --- /dev/null +++ b/Advanced/secretsanta-cordapp/contracts/src/main/java/net/corda/samples/secretsanta/contracts/SantaSessionContract.java @@ -0,0 +1,70 @@ +package net.corda.samples.secretsanta.contracts; + +import net.corda.samples.secretsanta.states.SantaSessionState; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.CommandWithParties; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.ContractState; +import net.corda.core.transactions.LedgerTransaction; + +import java.util.List; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class SantaSessionContract implements Contract { + + public static final String ID = "net.corda.samples.secretsanta.contracts.SantaSessionContract"; + + @Override + public void verify(LedgerTransaction tx) throws IllegalArgumentException { + /* + * We can use the requireSingleCommand function to extract command data from transaction. + * However, it is possible to have multiple commands in a signle transaction. + */ + final CommandWithParties command = requireSingleCommand(tx.getCommands(), SantaSessionContract.Commands.class); + + List inputs = tx.getInputStates(); + List outputs = tx.getOutputStates(); + + if (command.getValue() instanceof SantaSessionContract.Commands.Issue) { + + // Using Corda DSL function requireThat to replicate conditions-checks + requireThat(require -> { + // no inputs to the transaction + require.using("No inputs should be consumed when creating a new SantaSession.", inputs.isEmpty()); + + require.using("Transaction must have no input.", inputs.size() == 0); + require.using("Transaction must have exactly one output.", outputs.size() == 1); + + require.using("Output must be a SantaSessionState.", outputs.get(0) instanceof SantaSessionState); + + // Retrieve the output state of the transaction + SantaSessionState output = (SantaSessionState) outputs.get(0); + + // must be three or more players + require.using("Must be three or more players", output.getPlayerNames().size() > 2); + + require.using("Contact info for each player", output.getPlayerEmails().size() == output.getPlayerNames().size()); + + // each player has only one person assigned + //require.using("Each Player should have one person assigned", output.getAssignments().size() == output.getPlayerNames().size()); + + return null; + }); + + } else { + throw new IllegalArgumentException("Command not found!"); + } + + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + // In our secret santa app we only issue new Secret Santa game sessions shared with all players accounts + class Issue implements Commands {} + } +} diff --git a/Advanced/secretsanta-cordapp/contracts/src/main/java/net/corda/samples/secretsanta/states/SantaSessionState.java b/Advanced/secretsanta-cordapp/contracts/src/main/java/net/corda/samples/secretsanta/states/SantaSessionState.java new file mode 100644 index 00000000..79a233c2 --- /dev/null +++ b/Advanced/secretsanta-cordapp/contracts/src/main/java/net/corda/samples/secretsanta/states/SantaSessionState.java @@ -0,0 +1,194 @@ +package net.corda.samples.secretsanta.states; + +import net.corda.samples.secretsanta.contracts.SantaSessionContract; +import com.sun.istack.NotNull; +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.serialization.ConstructorForDeserialization; + +import java.util.*; + +// ********* +// * State * +// ********* +@BelongsToContract(SantaSessionContract.class) +public class SantaSessionState implements ContractState, LinearState { + + // private variables + private UniqueIdentifier linearId; + + private List playerNames; + private List playerEmails; + private LinkedHashMap assignments; + + private Party issuer; // issuer is the creator of the santa session + private Party owner; // owner is the receiver of the santa session + + + // Note Corda always needs a constructor for deserialization in java! + @ConstructorForDeserialization + public SantaSessionState(UniqueIdentifier linearId, List playerNames, List playerEmails, LinkedHashMap assignments, Party issuer, Party owner) { + this.linearId = linearId; + this.playerNames = playerNames; + this.playerEmails = playerEmails; + this.assignments = assignments; + this.issuer = issuer; + this.owner = owner; + } + + public SantaSessionState(List playerNames, List playerEmails, Party issuer, Party owner) { + this.playerNames = playerNames; + this.playerEmails = playerEmails; + this.issuer = issuer; + this.owner = owner; + this.linearId = new UniqueIdentifier(); + + if (playerNames.size() != playerEmails.size()) { + throw new IllegalArgumentException("Inconsistent number of names and emails"); + } + + if (playerNames.size() < 3) { + throw new IllegalArgumentException("Too few players: " + playerNames.size() + " " + playerNames.toString()); + } + + LinkedHashMap _assignments = new LinkedHashMap<>(); + + ArrayList shuffledPersons = new ArrayList<>(playerNames); + Collections.shuffle(shuffledPersons); + + for (int i=0; i < playerNames.size() - 1; i++) { + _assignments.put(shuffledPersons.get(i), shuffledPersons.get(i+1)); + } + + _assignments.put(shuffledPersons.get(shuffledPersons.size()-1), shuffledPersons.get(0)); + + this.setAssignments(_assignments); + } + + // confirm quality of assignments + public void setAssignments(LinkedHashMap assignments) { + + if (assignments == null) { + throw new IllegalArgumentException("Given null assignments"); + } + + if (playerNames.size() != assignments.size()) { + throw new IllegalArgumentException("Invalid number of pairings"); + } + + // iterate through assignments for validity + for (String santa_candidate: playerNames) { + + // ensure all these players exist + if(!playerNames.contains(santa_candidate)) { + throw new IllegalArgumentException("A santa candidate does not exist in the list of players"); + } + + for (String target_candidate: playerNames) { + + // skip duplicates in iteration + if (santa_candidate.equals(target_candidate)) { continue; } + + // ensure no one is assigned themselves + if(santa_candidate.equals(assignments.get(santa_candidate))){ + throw new IllegalArgumentException("player assigned themselves"); + } + } + } + + // if assignments look good, we'll set them in the member variable + this.assignments = assignments; + } + + @Override + public UniqueIdentifier getLinearId() { + return linearId; + } + + public void setLinearId(UniqueIdentifier linearId) { + this.linearId = linearId; + } + + public List getPlayerNames() { + return playerNames; + } + + public void setPlayerNames(List playerNames) { + this.playerNames = playerNames; + } + + public List getPlayerEmails() { + return playerEmails; + } + + public void setPlayerEmails(List playerEmails) { + this.playerEmails = playerEmails; + } + + public LinkedHashMap getAssignments() { + return assignments; + } + + public Party getIssuer() { + return issuer; + } + + public void setIssuer(Party issuer) { + this.issuer = issuer; + } + + public Party getOwner() { + return owner; + } + + public void setOwner(Party owner) { + this.owner = owner; + } + + /* * + * This method will indicate who are the participants and required signers when + * this state is used in a transaction. + * */ + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(issuer, owner); + } + + + // convenience method to get a random element from a set + public Object chooseRandomElement (Set s) { + int item = new Random().nextInt(s.size()); + int i = 0; + + for (Object obj : s) { + if (i == item) + return obj; + i++; + } + return null; + } + + /* * + * get key by value from HashMap + * from StackOverflow: https://stackoverflow.com/questions/8112975/get-key-from-a-hashmap-using-the-value + */ + public Object getKeyByFirstValue(LinkedHashMap map, Object v) { + + for (Map.Entry e: map.entrySet()) { + Object key = e.getKey(); + Object value = e.getValue(); + + if (v.equals(value)){ + return key; + } + } + + return null; + } + +} diff --git a/Advanced/secretsanta-cordapp/contracts/src/test/java/net/corda/samples/secretsanta/contracts/SantaSessionContractTests.java b/Advanced/secretsanta-cordapp/contracts/src/test/java/net/corda/samples/secretsanta/contracts/SantaSessionContractTests.java new file mode 100644 index 00000000..725f6b08 --- /dev/null +++ b/Advanced/secretsanta-cordapp/contracts/src/test/java/net/corda/samples/secretsanta/contracts/SantaSessionContractTests.java @@ -0,0 +1,191 @@ +package net.corda.samples.secretsanta.contracts; + +import net.corda.samples.secretsanta.states.SantaSessionState; +import junit.framework.TestCase; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.TypeOnlyCommandData; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.contracts.DummyState; +import net.corda.testing.core.DummyCommandData; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Before; +import org.junit.Test; +import static net.corda.testing.node.NodeTestUtils.transaction; + +import java.util.ArrayList; +import java.util.Arrays; + +public class SantaSessionContractTests { + + private final TestIdentity santa = new TestIdentity(new CordaX500Name("Santa", "", "GB")); + private final TestIdentity elf = new TestIdentity(new CordaX500Name("Santa", "", "GB")); + + // A pre-defined dummy command. + public interface Commands extends CommandData { + class DummyCommand extends TypeOnlyCommandData implements Commands{} + } + + private MockServices ledgerServices = new MockServices( + Arrays.asList("net.corda.samples.secretsanta.contracts") + ); + + private ArrayList playerNames = new ArrayList<>(Arrays.asList("david", "alice", "bob", "charlie", "olivia", "peter")); + private ArrayList playerEmails = new ArrayList<>(Arrays.asList("david@corda.net", "alice@corda.net", "bob@corda.net", "charlie@corda.net", "olivia@corda.net", "peter@corda.net")); + + private SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa.getParty(), elf.getParty()); + + private ArrayList noPlayerNames = new ArrayList<>(); + private ArrayList onePlayerNames = new ArrayList<>(Arrays.asList("david")); + private ArrayList threePlayerNames = new ArrayList<>(Arrays.asList("david", "alice", "bob", "peter")); + + private ArrayList noPlayerEmails = new ArrayList<>(); + private ArrayList onePlayerEmails = new ArrayList<>(Arrays.asList("david@corda.net")); + private ArrayList threePlayerEmails = new ArrayList<>(Arrays.asList("david@corda.net", "alice@corda.net", "bob@corda.net", "peter@corda.net")); + + + @Before + public void setup() { } + +// @After +// public void tearDown() { } + + @Test + public void SantaSessionContractImplementsContract() { + assert(new SantaSessionContract() instanceof Contract); + } + + @Test + public void constructorTest() { + SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa.getParty(), elf.getParty()); + TestCase.assertEquals(santa.getParty(), st.getIssuer()); + TestCase.assertEquals(playerNames, st.getPlayerNames()); + TestCase.assertEquals(playerEmails, st.getPlayerEmails()); + } + + @Test + public void SantaSessionContractRequiresZeroInputsInTheTransaction() { + SantaSessionState t1 = new SantaSessionState(playerNames, playerEmails, santa.getParty(), elf.getParty()); + SantaSessionState t2 = new SantaSessionState(playerNames, playerEmails, santa.getParty(), elf.getParty()); + + transaction(ledgerServices, tx -> { + // Has an input, will fail. + tx.input(SantaSessionContract.ID, t1); + tx.output(SantaSessionContract.ID, t2); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + // Has no input, will verify. + tx.output(SantaSessionContract.ID, st); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.verifies(); + return null; + }); + } + + @Test + public void SantaSessionContractRequiresOneOutputInTheTransaction() { + transaction(ledgerServices, tx -> { + // Has two outputs, will fail. + tx.output(SantaSessionContract.ID, st); + tx.output(SantaSessionContract.ID, st); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + // Has one output, will verify. + tx.output(SantaSessionContract.ID, st); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.verifies(); + return null; + }); + } + + @Test + public void SantaSessionContractRequiresOneCommandInTheTransaction() { + transaction(ledgerServices, tx -> { + tx.output(SantaSessionContract.ID, st); + // Has two commands, will fail. + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + tx.output(SantaSessionContract.ID, st); + // Has one command, will verify. + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.verifies(); + return null; + }); + } + + @Test + public void SantaSessionContractRequiresTheTransactionsOutputToBeASantaSessionState() { + transaction(ledgerServices, tx -> { + // Has wrong output type, will fail. + tx.output(SantaSessionContract.ID, new DummyState()); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + // Has correct output type, will verify. + tx.output(SantaSessionContract.ID, st); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.verifies(); + return null; + }); + } + + @Test + public void SantaSessionContractRequiresTheTransactionsCommandToBeAnIssueCommand() { + transaction(ledgerServices, tx -> { + // Has wrong command type, will fail. + tx.output(SantaSessionContract.ID, st); + tx.command(Arrays.asList(santa.getPublicKey()), DummyCommandData.INSTANCE); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + // Has correct command type, will verify. + tx.output(SantaSessionContract.ID, st); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.verifies(); + return null; + }); + } + + @Test + public void SantaSessionContractRequiresTheTransactionsOutputToHaveMoreThanThreePlayers() { + + SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa.getParty(), elf.getParty()); + SantaSessionState threePlayerSantaState = new SantaSessionState(threePlayerNames, threePlayerEmails, santa.getParty(), elf.getParty()); + + transaction(ledgerServices, tx -> { + // Has three players SantaSessionState, will verify. + tx.output(SantaSessionContract.ID, threePlayerSantaState); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.verifies(); + return null; + }); + + transaction(ledgerServices, tx -> { + // Has over three players SantaSessionState, will verify. + tx.output(SantaSessionContract.ID, st); + tx.command(Arrays.asList(santa.getPublicKey(), elf.getPublicKey()), new SantaSessionContract.Commands.Issue()); + tx.verifies(); + return null; + }); + } + +} diff --git a/Advanced/secretsanta-cordapp/contracts/src/test/java/net/corda/samples/secretsanta/contracts/SantaSessionStateTests.java b/Advanced/secretsanta-cordapp/contracts/src/test/java/net/corda/samples/secretsanta/contracts/SantaSessionStateTests.java new file mode 100644 index 00000000..b8fa6a3e --- /dev/null +++ b/Advanced/secretsanta-cordapp/contracts/src/test/java/net/corda/samples/secretsanta/contracts/SantaSessionStateTests.java @@ -0,0 +1,168 @@ +package net.corda.samples.secretsanta.contracts; + +import net.corda.samples.secretsanta.states.SantaSessionState; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.LinearState; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.testing.core.TestIdentity; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SantaSessionStateTests { + + private List playerNames = new ArrayList<>(); + private List playerEmails = new ArrayList<>(); + + private final Party santa = new TestIdentity(new CordaX500Name("Santa", "", "GB")).getParty(); + private final Party elf = new TestIdentity(new CordaX500Name("Elf", "", "GB")).getParty(); + + @Before + public void setup() { + playerNames.add("david"); + playerNames.add("alice"); + playerNames.add("bob"); + playerNames.add("charlie"); + playerNames.add("olivia"); + playerNames.add("peter"); + + playerEmails.add("david@corda.net"); + playerEmails.add("alice@corda.net"); + playerEmails.add("bob@corda.net"); + playerEmails.add("charlie@corda.net"); + playerEmails.add("olivia@corda.net"); + playerEmails.add("peter@corda.net"); + } + + @After + public void tearDown() { + // pass + } + + // unmatching names + @Test(expected = IllegalArgumentException.class) + public void unmatchingNameTest() { + ArrayList badNames = new ArrayList<>(Arrays.asList("alice", "bob", "charlie", "olivia", "peter")); + ArrayList goodEmails = new ArrayList<>(Arrays.asList("david@corda.net", "alice@corda.net", "bob@corda.net", "charlie@corda.net", "olivia@corda.net", "peter@corda.net")); + SantaSessionState st = new SantaSessionState(badNames, goodEmails, santa, elf); + + } + + // few name test + @Test(expected = IllegalArgumentException.class) + public void fewNameTest() { + // here there are too few names + ArrayList badNames = new ArrayList<>(Arrays.asList("peter")); + ArrayList goodEmails = new ArrayList<>(Arrays.asList("david@corda.net", "alice@corda.net", "bob@corda.net", "charlie@corda.net", "olivia@corda.net", "peter@corda.net")); + SantaSessionState st = new SantaSessionState(badNames, goodEmails, santa, elf); + + } + + // unmatching emails + @Test(expected = IllegalArgumentException.class) + public void unmatchingEmailTest() { + // note there's no matching email for david, david@corda.net + ArrayList goodNames = new ArrayList<>(Arrays.asList("david", "alice", "bob", "charlie", "olivia", "peter")); + ArrayList badEmails = new ArrayList<>(Arrays.asList("alice@corda.net", "bob@corda.net", "charlie@corda.net", "olivia@corda.net", "peter@corda.net")); + SantaSessionState st = new SantaSessionState(goodNames, badEmails, santa, elf); + } + + // too few emails + @Test(expected = IllegalArgumentException.class) + public void fewEmailTest() { + ArrayList goodNames = new ArrayList<>(Arrays.asList("david", "alice", "bob", "charlie", "olivia", "peter")); + ArrayList badEmails = new ArrayList<>(Arrays.asList("peter@corda.net")); + SantaSessionState st = new SantaSessionState(goodNames, badEmails, santa, elf); + } + + + @Test(expected = IllegalArgumentException.class) + public void tooFewPairingsTest() { + SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa, elf); + + LinkedHashMap assignments = new LinkedHashMap<>(); + assignments.put("david", "alice"); + assignments.put("alice", "bob"); + assignments.put("bob", "charlie"); + assignments.put("charlie", "olivia"); + assignments.put("olivia", "david"); + // note peter would be the "odd man out" and not have a valid assignment + // assignments.put("peter", "peter"); + st.setAssignments(assignments); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidPairingsTest() { + SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa, elf); + + LinkedHashMap assignments = new LinkedHashMap(); + assignments.put("david", "alice"); + assignments.put("alice", "bob"); + assignments.put("bob", "charlie"); + assignments.put("charlie", "olivia"); + assignments.put("olivia", "david"); + // note peter would be the "odd man out" and not have a valid assignment + assignments.put("peter", "peter"); + + st.setAssignments(assignments); + } + + @Test + public void stateGetters() { + SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa, elf); + + assertEquals(santa, st.getIssuer()); + assertEquals(playerNames, st.getPlayerNames()); + assertEquals(playerEmails, st.getPlayerEmails()); + + assertTrue(st.getPlayerNames().contains("olivia")); + assertTrue(st.getPlayerNames().contains("peter")); + + assertTrue(st.getPlayerEmails().contains("olivia@corda.net")); + assertTrue(st.getPlayerEmails().contains("peter@corda.net")); + + assertNotEquals(st.getAssignments().get("david"), st.getAssignments().get("peter")); + } + + @Test + public void stateImplementsContractState() { + SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa, elf); + assertTrue(st instanceof ContractState); + assertTrue(st instanceof LinearState); + } + + @Test + public void stateHasOneParticipant() { + SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa, elf); + assertEquals(2, st.getParticipants().size()); + assertTrue(st.getParticipants().contains(santa)); + assertTrue(st.getParticipants().contains(elf)); + } + + @Test + public void stateProducesValidAssignments() { + SantaSessionState st = new SantaSessionState(playerNames, playerEmails, santa, elf); + + HashMap assignments = st.getAssignments(); + // correct number of assignments + assertEquals(playerNames.size(), assignments.size()); + // iterate through assignments for validity + for (String santa_candidate: playerNames) { + // ensure all these players actually exist + assertTrue(playerNames.contains(santa_candidate)); + for (String target_candidate: playerNames) { + // skip duplicates in iteration + if (santa_candidate.equals(target_candidate)) { continue; } + // ensure no one is assigned themselves + assertNotEquals(santa_candidate, assignments.get(santa_candidate)); + } + } + } +} diff --git a/Advanced/secretsanta-cordapp/gradle.properties b/Advanced/secretsanta-cordapp/gradle.properties new file mode 100644 index 00000000..60d754a6 --- /dev/null +++ b/Advanced/secretsanta-cordapp/gradle.properties @@ -0,0 +1,3 @@ +name=Secret Santa +group=com.secretsanta +version=0.1 diff --git a/Basic/yo-cordapp/gradle/wrapper/gradle-wrapper.jar b/Advanced/secretsanta-cordapp/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from Basic/yo-cordapp/gradle/wrapper/gradle-wrapper.jar rename to Advanced/secretsanta-cordapp/gradle/wrapper/gradle-wrapper.jar diff --git a/Basic/yo-cordapp/gradle/wrapper/gradle-wrapper.properties b/Advanced/secretsanta-cordapp/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from Basic/yo-cordapp/gradle/wrapper/gradle-wrapper.properties rename to Advanced/secretsanta-cordapp/gradle/wrapper/gradle-wrapper.properties diff --git a/Basic/yo-cordapp/gradlew b/Advanced/secretsanta-cordapp/gradlew similarity index 100% rename from Basic/yo-cordapp/gradlew rename to Advanced/secretsanta-cordapp/gradlew diff --git a/Basic/spring-webserver/gradlew.bat b/Advanced/secretsanta-cordapp/gradlew.bat similarity index 100% rename from Basic/spring-webserver/gradlew.bat rename to Advanced/secretsanta-cordapp/gradlew.bat diff --git a/Advanced/secretsanta-cordapp/postman/Secret Santa .postman_collection.json b/Advanced/secretsanta-cordapp/postman/Secret Santa .postman_collection.json new file mode 100644 index 00000000..843e50cd --- /dev/null +++ b/Advanced/secretsanta-cordapp/postman/Secret Santa .postman_collection.json @@ -0,0 +1,111 @@ +{ + "info": { + "_postman_id": "cd6a9d5a-e954-4b77-9075-5fed8c736fdc", + "name": "Secret Santa ", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Create Game", + "event": [ + { + "listen": "test", + "script": { + "id": "5bdc1f50-2716-409d-a65a-7d04b573a8cb", + "exec": [ + "var jsonData = JSON.parse(responseBody);", + "postman.setEnvironmentVariable(\"gameId\", jsonData.gameId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "warning": "This is a duplicate header and will be overridden by the Content-Type header generated by Postman.", + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"playerNames\": [\"david\", \"peter\", \"mary\"],\n \"playerEmails\": [\"david@example.com\",\"peter@example.com\", \"mary@example.com\"],\n \"sendEmail\" : {{sendEmail}}\n}" + }, + "url": { + "raw": "{{santa_server}}/games", + "host": [ + "{{santa_server}}" + ], + "path": [ + "games" + ], + "query": [ + { + "key": "playerEmails", + "value": "", + "disabled": true + }, + { + "key": "playerNames", + "value": "", + "disabled": true + } + ] + }, + "description": "Creates a game in the SS server \n\n```\nflow start com.template.flows.CreateSantaSessionFlow playerNames: [\"david\", \"peter\", \"mary\"], playerEmails: [\"david@corda.net\", \"peter@corda.net\", \"mary@corda.net\"], owner: elf \n```" + }, + "response": [] + }, + { + "name": "Check Game", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"david\",\n \"gameId\": \"{{gameId}}\",\n \"sendEmail\" : {{sendEmail}}\n}" + }, + "url": { + "raw": "{{santa_server}}/games/check", + "host": [ + "{{santa_server}}" + ], + "path": [ + "games", + "check" + ] + }, + "description": "checks an existing game: \n```\nflow start com.template.flows.CheckAssignedSantaFlow santaSessionId: {{gameId}}\n```" + }, + "response": [] + }, + { + "name": "Check Name", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{santa_server}}/node", + "host": [ + "{{santa_server}}" + ], + "path": [ + "node" + ] + }, + "description": "returns name of santa server" + }, + "response": [] + } + ], + "protocolProfileBehavior": {} +} \ No newline at end of file diff --git a/Advanced/secretsanta-cordapp/repositories.gradle b/Advanced/secretsanta-cordapp/repositories.gradle new file mode 100644 index 00000000..7adba3a3 --- /dev/null +++ b/Advanced/secretsanta-cordapp/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Advanced/secretsanta-cordapp/runWebServer.sh b/Advanced/secretsanta-cordapp/runWebServer.sh new file mode 100644 index 00000000..66c073e5 --- /dev/null +++ b/Advanced/secretsanta-cordapp/runWebServer.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./gradlew runSantaServer & diff --git a/Advanced/secretsanta-cordapp/settings.gradle b/Advanced/secretsanta-cordapp/settings.gradle new file mode 100644 index 00000000..2514aca2 --- /dev/null +++ b/Advanced/secretsanta-cordapp/settings.gradle @@ -0,0 +1,3 @@ +include 'workflows' +include 'contracts' +include 'clients' \ No newline at end of file diff --git a/Advanced/secretsanta-cordapp/workflows/build.gradle b/Advanced/secretsanta-cordapp/workflows/build.gradle new file mode 100644 index 00000000..54f935cf --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/build.gradle @@ -0,0 +1,68 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Secret Santa Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +dependencies { + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // SendGrid + implementation 'com.sendgrid:sendgrid-java:4.7.0' + compile 'com.sendgrid:sendgrid-java:4.7.0' + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} diff --git a/Advanced/secretsanta-cordapp/workflows/src/integrationTest/java/net/corda/samples/secretsanta/DriverBasedTest.java b/Advanced/secretsanta-cordapp/workflows/src/integrationTest/java/net/corda/samples/secretsanta/DriverBasedTest.java new file mode 100644 index 00000000..b49478ff --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/src/integrationTest/java/net/corda/samples/secretsanta/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.secretsanta; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} diff --git a/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CheckAssignedSantaFlow.java b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CheckAssignedSantaFlow.java new file mode 100644 index 00000000..55a1f217 --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CheckAssignedSantaFlow.java @@ -0,0 +1,61 @@ +package net.corda.samples.secretsanta.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.samples.secretsanta.states.SantaSessionState; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +/** + * This flow will create the account on the node on which you run this flow. This is done using inbuilt flow called CreateAccount. + * CreateAccount creates an AccountInfo object which has name, host and id as its fields. This is mapped to Account table in the db. + * For any other party to transact with this account, this AccountInfo will have to be shared with that Party. + * Hence the Ipl Ticket Dealers create the ticket buyers accounts on their end and share this accountInfo with the Bank node and BCCI node. + */ +@StartableByRPC +@InitiatingFlow +public class CheckAssignedSantaFlow extends FlowLogic { + + private UniqueIdentifier santaSessionId; + private SantaSessionState santaSessionState = null; + + public CheckAssignedSantaFlow(UniqueIdentifier santaSessionId) { + this.santaSessionId = santaSessionId; + } + + @Override + @Suspendable + public SantaSessionState call() throws FlowException { + + // Retrieve the Game State from the vault using LinearStateQueryCriteria + List listOfLinearIds = Arrays.asList(santaSessionId.getId()); + QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, listOfLinearIds); + Vault.Page results = getServiceHub().getVaultService().queryBy(SantaSessionState.class, queryCriteria); + + if (results.getStates().size() < 1) { + throw new IllegalArgumentException("No corresponding GameID Found."); + } + + santaSessionState = (SantaSessionState) ((StateAndRef) results.getStates().get(0)).getState().getData(); + + return santaSessionState; + } + + public UniqueIdentifier getSantaSessionId() { + return santaSessionId; + } + + public SantaSessionState getSantaSessionState() { + return santaSessionState; + } +} + diff --git a/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CheckAssignedSantaFlowResponder.java b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CheckAssignedSantaFlowResponder.java new file mode 100644 index 00000000..9816f694 --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CheckAssignedSantaFlowResponder.java @@ -0,0 +1,30 @@ +package net.corda.samples.secretsanta.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; + + +@InitiatedBy(CheckAssignedSantaFlow.class) +public class CheckAssignedSantaFlowResponder extends FlowLogic { + + private final FlowSession otherSide; + + public CheckAssignedSantaFlowResponder(FlowSession otherSide) { + this.otherSide = otherSide; + } + + @Override + @Suspendable + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(otherSide) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + // Implement responder flow transaction checks here + } + }); + subFlow(new ReceiveFinalityFlow(otherSide, signedTransaction.getId())); + return null; + } +} diff --git a/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlow.java b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlow.java new file mode 100644 index 00000000..7b9635d5 --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlow.java @@ -0,0 +1,94 @@ +package net.corda.samples.secretsanta.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.sun.istack.Nullable; +import net.corda.samples.secretsanta.contracts.SantaSessionContract; +import net.corda.samples.secretsanta.states.SantaSessionState; +import net.corda.core.contracts.CommandData; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.core.identity.Party; +import net.corda.core.utilities.ProgressTracker; + +import static java.util.Collections.singletonList; + +import java.util.*; +import net.corda.core.identity.CordaX500Name; + + +/** + * santaPlayers is a list of Pairs representing the names and emails of the players in this session + */ +@StartableByRPC +@InitiatingFlow +public class CreateSantaSessionFlow extends FlowLogic { + + private List playerNames; + private List playerEmails; + + private Party owner; + + // elves 'own' a secret santa session + public CreateSantaSessionFlow(List playerNames, List playerEmails, Party owner) { + this.playerNames = playerNames; + this.playerEmails = playerEmails; + this.owner = owner; + } + + private static final ProgressTracker.Step CREATING = new ProgressTracker.Step("Shoveling coal in the server . . ."); + private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step("Getting the message to Santa . . ."); + private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step("Gathering the reindeer . . ."); + private static final ProgressTracker.Step FINALISING = new ProgressTracker.Step("Sending christmas cheer!"); + private static final ProgressTracker.Step FINALDISPLAY = new ProgressTracker.Step("Secret Santa has been successfully generated.") { + @Nullable + @Override + public ProgressTracker childProgressTracker() { + return FinalityFlow.tracker(); + } + }; + + ProgressTracker progressTracker = new ProgressTracker( + CREATING, + SIGNING, + VERIFYING, + FINALISING, + FINALDISPLAY + ); + + @Override + public ProgressTracker getProgressTracker() { + return progressTracker; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + // run an issuance transaction for a new secret santa game + progressTracker.setCurrentStep(CREATING); + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + Party issuer = getOurIdentity(); + + SantaSessionState newSantaState = new SantaSessionState(playerNames, playerEmails, issuer, owner); + + TransactionBuilder transactionBuilder = new TransactionBuilder(notary); + CommandData commandData = new SantaSessionContract.Commands.Issue(); + transactionBuilder.addCommand(commandData, issuer.getOwningKey(), owner.getOwningKey()); + transactionBuilder.addOutputState(newSantaState, SantaSessionContract.ID); + + progressTracker.setCurrentStep(VERIFYING); + transactionBuilder.verify(getServiceHub()); + FlowSession session = initiateFlow(owner); + + progressTracker.setCurrentStep(SIGNING); + SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(transactionBuilder); + + progressTracker.setCurrentStep(FINALISING); + SignedTransaction fullySignedTransaction = subFlow(new CollectSignaturesFlow(signedTransaction, singletonList(session))); + + progressTracker.setCurrentStep(FINALDISPLAY); + return subFlow(new FinalityFlow(fullySignedTransaction, singletonList(session))); + } + +} diff --git a/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlowResponder.java b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlowResponder.java new file mode 100644 index 00000000..09f23827 --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/CreateSantaSessionFlowResponder.java @@ -0,0 +1,30 @@ +package net.corda.samples.secretsanta.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; + + +@InitiatedBy(CreateSantaSessionFlow.class) +public class CreateSantaSessionFlowResponder extends FlowLogic { + + private final FlowSession otherSide; + + public CreateSantaSessionFlowResponder(FlowSession otherSide) { + this.otherSide = otherSide; + } + + @Override + @Suspendable + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(otherSide) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + // Implement responder flow transaction checks here + } + }); + subFlow(new ReceiveFinalityFlow(otherSide, signedTransaction.getId())); + return null; + } +} diff --git a/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/TemplateSerializationWhitelist.java b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/TemplateSerializationWhitelist.java new file mode 100644 index 00000000..ca95adf5 --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/src/main/java/net/corda/samples/secretsanta/flows/TemplateSerializationWhitelist.java @@ -0,0 +1,23 @@ +package net.corda.samples.secretsanta.flows; + +import net.corda.core.serialization.SerializationWhitelist; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +import com.sendgrid.helpers.mail.objects.Content; +import com.sendgrid.helpers.mail.objects.Email; +import com.sendgrid.helpers.mail.Mail; +import com.sendgrid.*; + + +// Serialization whitelist. +public class TemplateSerializationWhitelist implements SerializationWhitelist { + @NotNull + @Override + public List> getWhitelist() { + return Arrays.asList(Content.class, Email.class, Mail.class, SendGrid.class); + } + +} diff --git a/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CheckAssignedSantaFlowTests.java b/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CheckAssignedSantaFlowTests.java new file mode 100644 index 00000000..8102b6e6 --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CheckAssignedSantaFlowTests.java @@ -0,0 +1,191 @@ +package net.corda.samples.secretsanta; + +import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.secretsanta.flows.CheckAssignedSantaFlow; +import net.corda.samples.secretsanta.flows.CreateSantaSessionFlow; +import net.corda.samples.secretsanta.states.SantaSessionState; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.node.NetworkParameters; +import net.corda.core.transactions.SignedTransaction; +import net.corda.testing.node.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class CheckAssignedSantaFlowTests { + private MockNetwork network; + private StartedMockNode santa; + private StartedMockNode elf; + + private NetworkParameters testNetworkParameters = new NetworkParameters(4, Arrays.asList(), 10485760, (10485760 * 5), Instant.now(),1, new LinkedHashMap<>()); + + private ArrayList playerNames = new ArrayList<>(Arrays.asList("david", "alice", "bob", "charlie", "olivia", "peter")); + private ArrayList playerEmails = new ArrayList<>(Arrays.asList("david@corda.net", "alice@corda.net", "bob@corda.net", "charlie@corda.net", "olivia@corda.net", "peter@corda.net")); + + @Before + public void setup() { + + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.secretsanta.contracts"), + TestCordapp.findCordapp("net.corda.samples.secretsanta.flows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + + santa = network.createPartyNode(null); + elf = network.createPartyNode(null); + + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test(expected = IllegalArgumentException.class) + public void checkingBadSessionID() throws Exception { + CheckAssignedSantaFlow f1 = new CheckAssignedSantaFlow(UniqueIdentifier.Companion.fromString("badUID")); + CordaFuture future1 = santa.startFlow(f1); + network.runNetwork(); + + SantaSessionState f1Output = future1.get(); + } + + @Test(expected = java.util.concurrent.ExecutionException.class) + public void checkingMissingSessionID() throws Exception { + CheckAssignedSantaFlow f1 = new CheckAssignedSantaFlow(UniqueIdentifier.Companion.fromString("8237fd23-3dab-4a9b-b2ea-671006a660b5")); + CordaFuture future1 = santa.startFlow(f1); + network.runNetwork(); + + SantaSessionState f1Output = future1.get(); + } + + // ensure we can create and query a santa session + @Test + public void flowProducesCorrectState() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + + SantaSessionState f1Output = future.get().getTx().outputsOfType(SantaSessionState.class).get(0); + CheckAssignedSantaFlow f2 = new CheckAssignedSantaFlow(f1Output.getLinearId()); + CordaFuture future2 = santa.startFlow(f2); + network.runNetwork(); + + SantaSessionState f2Output = future2.get(); + + assertEquals(playerNames, f1Output.getPlayerNames()); + assertEquals(playerNames, f2Output.getPlayerNames()); + + // ensure these states are really the same + assertEquals(f1Output.getPlayerNames(), f2Output.getPlayerNames()); + assertEquals(f1Output.getPlayerEmails(), f2Output.getPlayerEmails()); + assertEquals(f1Output.getAssignments(), f2Output.getAssignments()); + + assert(f1Output.getPlayerNames().contains("david")); + assert(f1Output.getPlayerNames().contains("olivia")); + assert(!f1Output.getPlayerNames().contains("derek")); + + assert(f2Output.getPlayerNames().contains("david")); + assert(f2Output.getPlayerNames().contains("olivia")); + assert(!f2Output.getPlayerNames().contains("derek")); + + assertEquals(f1Output.getAssignments().get("david"), f2Output.getAssignments().get("david")); + assertEquals(f1Output.getAssignments().get("peter"), f2Output.getAssignments().get("peter")); + assertNotEquals(f1Output.getAssignments().get("peter"), f2Output.getAssignments().get("david")); + + assertEquals(f1Output.getLinearId(), f2Output.getLinearId()); + } + + + @Test + public void bothNodesRetrieveTheSameState() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + + SantaSessionState f1Output = future.get().getTx().outputsOfType(SantaSessionState.class).get(0); + CheckAssignedSantaFlow f2 = new CheckAssignedSantaFlow(f1Output.getLinearId()); + CheckAssignedSantaFlow f3 = new CheckAssignedSantaFlow(f1Output.getLinearId()); + CordaFuture future2 = santa.startFlow(f2); + CordaFuture future3 = elf.startFlow(f3); + network.runNetwork(); + + SantaSessionState f2Output = future2.get(); + SantaSessionState f3Output = future3.get(); + + assertEquals(playerNames, f1Output.getPlayerNames()); + assertEquals(playerNames, f2Output.getPlayerNames()); + + // ensure these states are really the same + assertEquals(f2Output.getPlayerNames(), f3Output.getPlayerNames()); + assertEquals(f2Output.getPlayerEmails(), f3Output.getPlayerEmails()); + assertEquals(f2Output.getAssignments(), f3Output.getAssignments()); + + assert(f2Output.getPlayerNames().contains("david")); + assert(f2Output.getPlayerNames().contains("olivia")); + assert(!f2Output.getPlayerNames().contains("derek")); + + assert(f3Output.getPlayerNames().contains("david")); + assert(f3Output.getPlayerNames().contains("olivia")); + assert(!f3Output.getPlayerNames().contains("derek")); + + assertEquals(f3Output.getAssignments().get("david"), f2Output.getAssignments().get("david")); + assertEquals(f3Output.getAssignments().get("peter"), f2Output.getAssignments().get("peter")); + assertNotEquals(f3Output.getAssignments().get("peter"), f2Output.getAssignments().get("david")); + + assertEquals(f3Output.getLinearId(), f2Output.getLinearId()); + } + + // ensure we can create and query a santa session + @Test + public void canRetrieveWithConvertedStringId() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + + SantaSessionState f1Output = future.get().getTx().outputsOfType(SantaSessionState.class).get(0); + + String strSessionId = f1Output.getLinearId().toString(); + System.out.println("CONVERTED STRING ID: " + strSessionId); + UniqueIdentifier convertedId = UniqueIdentifier.Companion.fromString(strSessionId); + + CheckAssignedSantaFlow f2 = new CheckAssignedSantaFlow(convertedId); + CordaFuture future2 = santa.startFlow(f2); + network.runNetwork(); + + SantaSessionState f2Output = future2.get(); + + assertEquals(playerNames, f1Output.getPlayerNames()); + assertEquals(playerNames, f2Output.getPlayerNames()); + + // ensure these states are really the same + assertEquals(f1Output.getPlayerNames(), f2Output.getPlayerNames()); + assertEquals(f1Output.getPlayerEmails(), f2Output.getPlayerEmails()); + assertEquals(f1Output.getAssignments(), f2Output.getAssignments()); + + assert(f1Output.getPlayerNames().contains("david")); + assert(f1Output.getPlayerNames().contains("olivia")); + assert(!f1Output.getPlayerNames().contains("derek")); + + assert(f2Output.getPlayerNames().contains("david")); + assert(f2Output.getPlayerNames().contains("olivia")); + assert(!f2Output.getPlayerNames().contains("derek")); + + assertEquals(f1Output.getAssignments().get("david"), f2Output.getAssignments().get("david")); + assertEquals(f1Output.getAssignments().get("peter"), f2Output.getAssignments().get("peter")); + assertNotEquals(f1Output.getAssignments().get("peter"), f2Output.getAssignments().get("david")); + + assertEquals(f1Output.getLinearId(), f2Output.getLinearId()); + } + +} diff --git a/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CreateSantaSessionFlowTests.java b/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CreateSantaSessionFlowTests.java new file mode 100644 index 00000000..b617bca2 --- /dev/null +++ b/Advanced/secretsanta-cordapp/workflows/src/test/java/net/corda/samples/secretsanta/CreateSantaSessionFlowTests.java @@ -0,0 +1,165 @@ +package net.corda.samples.secretsanta; + +import com.google.common.collect.ImmutableList; +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.secretsanta.contracts.SantaSessionContract; +import net.corda.samples.secretsanta.flows.CreateSantaSessionFlow; +import net.corda.samples.secretsanta.states.SantaSessionState; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.contracts.Command; +import net.corda.core.contracts.TransactionState; +import net.corda.core.node.NetworkParameters; +import net.corda.core.transactions.SignedTransaction; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class CreateSantaSessionFlowTests { + private MockNetwork network; + + private StartedMockNode santa; + private StartedMockNode elf; + + private NetworkParameters testNetworkParameters = new NetworkParameters(4, Arrays.asList(), 10485760, (10485760 * 5), Instant.now(),1, new LinkedHashMap<>()); + + private ArrayList playerNames = new ArrayList<>(Arrays.asList("david", "alice", "bob", "charlie", "olivia", "peter")); + private ArrayList playerEmails = new ArrayList<>(Arrays.asList("david@corda.net", "alice@corda.net", "bob@corda.net", "charlie@corda.net", "olivia@corda.net", "peter@corda.net")); + + @Before + public void setup() { + + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.secretsanta.contracts"), + TestCordapp.findCordapp("net.corda.samples.secretsanta.flows"))).withNetworkParameters(testNetworkParameters) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + + santa = network.createPartyNode(null); + elf = network.createPartyNode(null); + + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void flowUsesCorrectNotary() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + SignedTransaction signedTransaction = future.get(); + assertEquals(1, signedTransaction.getTx().getOutputStates().size()); + + // ensure correct notary is used + assertEquals(network.getNotaryNodes().get(0).getInfo().getLegalIdentities().get(0), signedTransaction.getNotary()); + } + + @Test + public void canCreateSession() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + SignedTransaction signedTransaction = future.get(); + assertEquals(1, signedTransaction.getTx().getOutputStates().size()); + + SantaSessionState output = signedTransaction.getTx().outputsOfType(SantaSessionState.class).get(0); + // get some random data from the output to verify + assertEquals(playerNames, output.getPlayerNames()); + } + + @Test + public void transactionConstructedHasCorrectOutput() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + SignedTransaction signedTransaction = future.get(); + + assertEquals(1, signedTransaction.getTx().getOutputStates().size()); + TransactionState tOutput = signedTransaction.getTx().getOutputs().get(0); + + // ensure correct notary is used is used + assertEquals(network.getNotaryNodes().get(0).getInfo().getLegalIdentities().get(0), tOutput.getNotary()); + + SantaSessionState output = signedTransaction.getTx().outputsOfType(SantaSessionState.class).get(0); + + // checking player names, emails, and assignments. + assertEquals(playerNames, output.getPlayerNames()); + assertEquals(playerEmails, output.getPlayerEmails()); + assertEquals(playerEmails.size(), output.getAssignments().size()); + } + + + @Test + public void transactionConstructedHasOneOutputUsingTheCorrectContract() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + + SignedTransaction signedTransaction = future.get(); + + assertEquals(1, signedTransaction.getTx().getOutputStates().size()); + TransactionState output = signedTransaction.getTx().getOutputs().get(0); + + assertEquals("net.corda.samples.secretsanta.contracts.SantaSessionContract", output.getContract()); + } + + + @Test + public void transactionConstructedByFlowHasOneIssueCommand() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + + SignedTransaction signedTransaction = future.get(); + + assertEquals(1, signedTransaction.getTx().getCommands().size()); + Command command = signedTransaction.getTx().getCommands().get(0); + + assert (command.getValue() instanceof SantaSessionContract.Commands.Issue); + } + + @Test + public void transactionConstructedByFlowHasOneCommandWithTheIssuerAndTheOwnerAsASigners() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + + SignedTransaction signedTransaction = future.get(); + + assertEquals(1, signedTransaction.getTx().getCommands().size()); + Command command = signedTransaction.getTx().getCommands().get(0); + + assertEquals(2, command.getSigners().size()); + assertTrue(command.getSigners().contains(santa.getInfo().getLegalIdentities().get(0).getOwningKey())); + assertTrue(command.getSigners().contains(elf.getInfo().getLegalIdentities().get(0).getOwningKey())); + } + + @Test + public void transactionConstructedByFlowHasNoInputsAttachmentsOrTimeWindows() throws Exception { + CreateSantaSessionFlow f1 = new CreateSantaSessionFlow(playerNames, playerEmails, elf.getInfo().getLegalIdentities().get(0)); + CordaFuture future = santa.startFlow(f1); + network.runNetwork(); + + SignedTransaction signedTransaction = future.get(); + assertEquals(0, signedTransaction.getTx().getInputs().size()); + assertEquals(1, signedTransaction.getTx().getOutputs().size()); + + // The single attachment is the contract attachment. + assertEquals(1, signedTransaction.getTx().getAttachments().size()); + assertNull(signedTransaction.getTx().getTimeWindow()); + } + +} diff --git a/Basic/yo-cordapp/LICENCE b/Advanced/snakesandladders-cordapp/LICENCE similarity index 100% rename from Basic/yo-cordapp/LICENCE rename to Advanced/snakesandladders-cordapp/LICENCE diff --git a/Advanced/snakesandladders-cordapp/README.md b/Advanced/snakesandladders-cordapp/README.md new file mode 100644 index 00000000..2e5f5b9a --- /dev/null +++ b/Advanced/snakesandladders-cordapp/README.md @@ -0,0 +1,53 @@ +# Snakes And Ladders + +This sample implements a simple Snakes And Ladder Game on Corda. + +It's a simple game which has a board with numbers from 1 to 100. Each player starts at 1. +Players take turn to roll a dice and move as many places as they rolled. If a player lands on a number +with a ladder they climb up using the ladder or if they land in a number with a snake they move down on the board. +The player who reach 100 first wins. + +The CorDapp runs a network having 4 nodes, +1. Classic Games +2. Mega Games +3. Oracle +4. Notary + +Each player can create an account to participate. Players can either be on the same node or different nodes. +Oracle node is used to obtain the player dice rolls. + +# Setting up +Go into the project directory and build the project +``` +./gradlew clean deployNodes +``` +Run the project +``` +./build/nodes/runnodes +``` + +Now, you should have four Corda terminals opened automatically. + +Run the below command to start clients: + +``` ./gradlew runClassicGamesClient``` + +``` ./gradlew runMegaGamesClient``` + +The clients can be accessed at http://localhost:50007 and http://localhost:50008 for Classic Games and Mega Games respectively. + +# Running the Sample CorDapp + +1. Goto http://localhost:50007/ +2. Click the "Create Game Account" to create account for players. Create two accounts say "Player1" and "Player2". + ![Create Player](./snaps/Create_Player.png) +3. To State the Game enter the player's name in Player 1 and Player 2 field and click on "Start Game" button. +4. The game is now started. + ![Create Player](./snaps/game.png) +5. Player two can join from a different browser window by using the "Join Game as Player 2" button on the landing screen. + The Game Id can be found in the Game screen. +6. Each player need to roll the dice till one of them reaches 100. The one who reaches 100 first becomes the winner. + + + + diff --git a/Advanced/snakesandladders-cordapp/TRADEMARK b/Advanced/snakesandladders-cordapp/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/TRADEMARK @@ -0,0 +1,4 @@ +Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates. All rights reserved. + +For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy at +https://www.r3.com/trademark-policy/. \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/build.gradle b/Advanced/snakesandladders-cordapp/build.gradle new file mode 100644 index 00000000..f30e7f56 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/build.gradle @@ -0,0 +1,173 @@ +buildscript { + Properties constants = new Properties() + //file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + file("$projectDir/../constants.properties").withInputStream { constants.load(it) } + + ext { + + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + corda_gradle_plugins_version = constants.getProperty("gradlePluginsVersion") + kotlin_version = constants.getProperty("kotlinVersion") + junit_version = constants.getProperty("junitVersion") + quasar_version = constants.getProperty("quasarVersion") + log4j_version = constants.getProperty("log4jVersion") + slf4j_version = constants.getProperty("slf4jVersion") + corda_platform_version = constants.getProperty("platformVersion").toInteger() + //accounts + accounts_release_version = '1.0' + accounts_release_group = 'com.r3.corda.lib.accounts' + confidential_id_release_group = "com.r3.corda.lib.ci" + confidential_id_release_version = "1.0" + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/corda-releases' } + } + + dependencies { + classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" + classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" + classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" + } +} + +allprojects { + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://jitpack.io' } + //SDK lib + maven { url 'https://download.corda.net/maven/corda-lib' } + //Gradle Plugins + maven { url 'https://repo.gradle.org/gradle/libs-releases' } + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. + } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + + + + +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":workflows") + cordapp project(":contracts") + cordapp project(":oracle-flows") + cordapp project(":oracle-service") + + cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" + cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaDriver "net.corda:corda-shell:4.10" + + + //accounts + cordapp "$accounts_release_group:accounts-contracts:$accounts_release_version" + cordapp "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" + cordapp "$accounts_release_group:accounts-workflows:$accounts_release_version" +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp("$confidential_id_release_group:ci-workflows:$confidential_id_release_version") + cordapp("$accounts_release_group:accounts-contracts:$accounts_release_version") + cordapp("$accounts_release_group:accounts-workflows:$accounts_release_version") + + cordapp project(":contracts") + cordapp project(":workflows") + cordapp project(":oracle-flows") + runSchemaMigration = true + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + cordapps.clear(); + } + node { + name "O=ClassicGames,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=MegaGames,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + + } + node { + name "O=Oracle,L=Mumbai,C=IN" + p2pPort 10011 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + cordapps.clear(); + cordapp project(":oracle-service") + cordapp project(":oracle-flows") + } +} + +task installQuasar(type: Copy) { + destinationDir rootProject.file("lib") + from(configurations.quasar) { + rename 'quasar-core(.*).jar', 'quasar.jar' + } +} + diff --git a/Advanced/snakesandladders-cordapp/client/build.gradle b/Advanced/snakesandladders-cordapp/client/build.gradle new file mode 100644 index 00000000..82103fec --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'org.springframework.boot' + +configurations { + all { + exclude group: 'ch.qos.logback', module: 'logback-classic' + } +} + +dependencies { + // Corda dependencies. + compile "$corda_release_group:corda-rpc:$corda_release_version" + + // CorDapp dependencies. + compile project(":contracts") + compile project(":workflows") + compile "net.corda:corda-core:$corda_release_version" + compile "net.corda:corda-jackson:$corda_release_version" + compile "net.corda:corda-rpc:$corda_release_version" + compile "net.corda:corda:$corda_release_version" + testCompile "net.corda:corda-node-driver:$corda_release_version" + + compile("org.springframework.boot:spring-boot-starter-websocket:$spring_boot_version") { + exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" + } + + compile "org.springframework.boot:spring-boot-devtools:$spring_boot_version" + + compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + compile "org.apache.logging.log4j:log4j-web:${log4j_version}" + compile "org.slf4j:jul-to-slf4j:$slf4j_version" +} + +springBoot { + mainClassName = "net.corda.samples.snl.client.Starter" +} + +task runClassicGamesClient(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.snl.client.Starter' + args '--server.port=50007', '--config.rpc.host=localhost', '--config.rpc.port=10006', '--config.rpc.username=user1', '--config.rpc.password=test' +} + +task runMegaGamesClient(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.snl.client.Starter' + args '--server.port=50008', '--config.rpc.host=localhost', '--config.rpc.port=10009', '--config.rpc.username=user1', '--config.rpc.password=test' +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/APIResponse.java b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/APIResponse.java new file mode 100644 index 00000000..e871855c --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/APIResponse.java @@ -0,0 +1,41 @@ +package net.corda.samples.snl.client; + +/** + * A wrapper to send response from the rest calls. + * @param + */ +public class APIResponse { + private String message; + private T data; + private boolean status; + + public APIResponse(String message, T data, boolean status) { + this.message = message; + this.data = data; + this.status = status; + } + + public String getMessage() { + return message; + } + + public T getData() { + return data; + } + + public boolean isStatus() { + return status; + } + + public static APIResponse success(){ + return new APIResponse<>("SUCCESS", null, true); + } + + public static APIResponse success(T data){ + return new APIResponse<>("SUCCESS", data, true); + } + + public static APIResponse error(String message){ + return new APIResponse<>(message, null, false); + } +} diff --git a/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/AppConfig.java b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/AppConfig.java new file mode 100644 index 00000000..d48f00c2 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/AppConfig.java @@ -0,0 +1,46 @@ +package net.corda.samples.snl.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.corda.client.jackson.JacksonSupport; +import net.corda.client.rpc.CordaRPCClient; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class AppConfig implements WebMvcConfigurer { + + public interface CONSTANTS { + String CORDA_USER_NAME = "config.rpc.username"; + String CORDA_USER_PASSWORD = "config.rpc.password"; + String CORDA_NODE_HOST = "config.rpc.host"; + String CORDA_RPC_PORT = "config.rpc.port"; + } + + @Value("${" + CONSTANTS.CORDA_NODE_HOST + "}") String host; + @Value("${" + CONSTANTS.CORDA_USER_NAME + "}") String username; + @Value("${" + CONSTANTS.CORDA_USER_PASSWORD + "}") String password; + @Value("${" + CONSTANTS.CORDA_RPC_PORT + "}") int rpcPort; + + @Bean(destroyMethod = "") // Avoids node shutdown on rpc disconnect + public CordaRPCOps rpcProxy(){ + NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); + CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); + return rpcClient.start(username, password).getProxy(); + } + + /** + * Corda Jackson Support, to convert corda objects to json + */ + @Bean + public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){ + ObjectMapper mapper = JacksonSupport.createDefaultMapper(rpcProxy()); + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(mapper); + return converter; + } +} diff --git a/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Controller.java b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Controller.java new file mode 100644 index 00000000..8f84aee9 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Controller.java @@ -0,0 +1,69 @@ +package net.corda.samples.snl.client; + +import net.corda.core.messaging.CordaRPCOps; +import net.corda.samples.snl.flows.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/snl/") +public class Controller { + + @Autowired + private CordaRPCOps rpcProxy; + + @PostMapping("createGame") + public APIResponse createGame(@RequestBody Forms.CreateGameForm createGameForm) { + try{ + String gameId = rpcProxy.startFlowDynamic(StartGameFlow.class, createGameForm.getPlayer1(), + createGameForm.getPlayer2()).getReturnValue().get(); + return APIResponse.success(gameId); + }catch(Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @PostMapping("playerMove") + public APIResponse playMove(@RequestBody Forms.PlayerMoveForm playerMoveForm) { + try{ + rpcProxy.startFlowDynamic(PlayerMoveFlow.Initiator.class, playerMoveForm.getPlayer(), + playerMoveForm.getGameId(), + Integer.valueOf(playerMoveForm.getRolledNumber())).getReturnValue().get(); + return APIResponse.success(); + }catch(Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @GetMapping("account/create/{name}") + public APIResponse createAccount(@PathVariable String name){ + try{ + rpcProxy.startFlowDynamic(CreateAndShareAccountFlow.class, name).getReturnValue().get(); + return APIResponse.success(); + }catch(Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + + @GetMapping("getGame/{gameId}") + public APIResponse getGame(@PathVariable String gameId){ + try{ + GameInfo gameInfo = rpcProxy.startFlowDynamic(QueyGameInfo.class, gameId).getReturnValue().get(); + return APIResponse.success(gameInfo); + }catch(Exception e){ + return APIResponse.error(e.getMessage()); + } + } + + @GetMapping("rollDice") + public APIResponse rollDice(){ + try{ + Integer numberRolled = rpcProxy.startFlowDynamic(QueryOracleForDiceRollFlow.class, "Test").getReturnValue().get(); + return APIResponse.success(numberRolled); + }catch(Exception e){ + return APIResponse.error(e.getMessage()); + } + } + +} diff --git a/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Forms.java b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Forms.java new file mode 100644 index 00000000..ebf05068 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Forms.java @@ -0,0 +1,48 @@ +package net.corda.samples.snl.client; + +public class Forms { + + public static class CreateGameForm { + private String player1; + private String player2; + + public String getPlayer1() { + return player1; + } + + public void setPlayer1(String player1) { + this.player1 = player1; + } + + public String getPlayer2() { + return player2; + } + + public void setPlayer2(String player2) { + this.player2 = player2; + } + } + + public static class PlayerMoveForm { + private String player; + private String gameId; + private int rolledNumber; + + public String getPlayer() { + return player; + } + + public void setPlayer(String player) { + this.player = player; + } + + public String getGameId() { + return gameId; + } + + public int getRolledNumber() { + return rolledNumber; + } + } + +} diff --git a/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Starter.java b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Starter.java new file mode 100644 index 00000000..5e871358 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/java/net/corda/samples/snl/client/Starter.java @@ -0,0 +1,18 @@ +package net.corda.samples.snl.client; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Starter { + + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Starter.class); + app.setBannerMode(Banner.Mode.OFF); + app.setWebApplicationType(WebApplicationType.SERVLET); + app.run(args); + } + +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/application.yaml b/Advanced/snakesandladders-cordapp/client/src/main/resources/application.yaml new file mode 100644 index 00000000..4fa71351 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/resources/application.yaml @@ -0,0 +1,10 @@ +partyA: + host: localhost:10006 + +partyB: + host: localhost:10009 + +partyC: + host: localhost:10012 + +server.port: 8085 \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/public/css/bootstrap-datetimepicker.min.css b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/css/bootstrap-datetimepicker.min.css new file mode 100644 index 00000000..2b1e210c --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/css/bootstrap-datetimepicker.min.css @@ -0,0 +1,5 @@ +.sr-only,.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after,.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after,.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after,.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after,.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after,.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after,.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after,.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after,.bootstrap-datetimepicker-widget .btn[data-action="today"]::after,.bootstrap-datetimepicker-widget .picker-switch::after,.bootstrap-datetimepicker-widget table th.prev::after,.bootstrap-datetimepicker-widget table th.next::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}/*! + * Datetimepicker for Bootstrap 3 + * ! version : 4.7.14 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */.bootstrap-datetimepicker-widget{list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{margin:2px 0;padding:4px;width:19em}@media (min-width: 540px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width: 720px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width: 960px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:before,.bootstrap-datetimepicker-widget.dropdown-menu:after{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;top:-6px;left:8px}.bootstrap-datetimepicker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:bold;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after{content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after{content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after{content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after{content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after{content:"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after{content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after{content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after{content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action="today"]::after{content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:.25rem}.bootstrap-datetimepicker-widget table th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:none;color:#636c72;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next::after{content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#eceeef}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#636c72}.bootstrap-datetimepicker-widget table td.day{height:20px;line-height:20px;width:20px;padding:5px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#eceeef;cursor:pointer}.bootstrap-datetimepicker-widget table td.old,.bootstrap-datetimepicker-widget table td.new{color:#636c72}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-datetimepicker-widget table td.today:before{content:'';display:inline-block;border:solid transparent;border-width:0 0 7px 7px;border-bottom-color:#0275d8;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#0275d8;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:none;color:#636c72;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:.25rem}.bootstrap-datetimepicker-widget table td span:hover{background:#eceeef}.bootstrap-datetimepicker-widget table td span.active{background-color:#0275d8;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td span.old{color:#636c72}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:none;color:#636c72;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.input-group.date .input-group-addon{cursor:pointer} diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/public/css/style.css b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/css/style.css new file mode 100644 index 00000000..b3e43932 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/css/style.css @@ -0,0 +1,325 @@ +* { + margin: 0; + padding: 0; + vertical-align: baseline; +} + +html { + font-family: system-ui, sans-serif; +} + +body { + display: grid; + grid-template-columns: 1fr; + height: 100vh; + width: 100%; +} + +.home-btns{ + position: relative; + top: 40%; + padding: 20px; +} + +.power-by{ + height: 24px; + margin-left: 5px; + margin-top: -5px; +} + +.title{ + position: absolute; + font-size: 68px; + color: red; + font-family: cursive; + margin-top: -25%; +} + +.logo{ + font-size: 24px; + font-family: cursive; + color: #DF0A1B; +} + +.header{ + height: 60px; + background: #EEEEEE; + border-bottom: 1px solid #CCCCCC; + padding: 5px 50px; +} + +.box-wrapper{ + padding: 10px; + background: #EEEEEE; + margin-bottom: 15px; + border: 1px solid #DDDDDD; + position: relative; + height: 140px; +} + +h3{ + font-size: 20px; + color: #444; + color: #DF0B1B; +} + +.input-group{ + margin-bottom: 15px; +} + +.console p { + margin-bottom: 0px; +} +.console { + font-size: 14px; +} + +.nodata{ + background: #eeeeee; + height: 100px; + display: flex; + justify-content: center; + align-items: center; + font-size: 16px; +} + +.spinner { + position: absolute; + z-index: 9999; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; +} + +.board-wrapper{ + position: relative; + width: 800px; + height: 800px; +} + +.player { + position: absolute; + width: 40px; + height: 40px; + background: red; + border-radius: 30px; + border: 1px solid #999; + box-shadow: 2px 3px 3px #111; + top: 750px; + left: 10px; +} + +/** Dice Styles **/ + +.dice-wrapper{ +} + +.dice { + align-items: center; + display: grid; + grid-gap: 2rem; + grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); + grid-template-rows: auto; + justify-items: center; + padding: 2rem; + perspective: 600px; +} +.die-list { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 1fr; + height: 6rem; + list-style-type: none; + transform-style: preserve-3d; + width: 6rem; +} +.even-roll { + transition: transform 1.5s ease-out; +} +.odd-roll { + transition: transform 1.25s ease-out; +} +.die-item { + background-color: #fefefe; + box-shadow: inset -0.35rem 0.35rem 0.75rem rgba(0, 0, 0, 0.3), + inset 0.5rem -0.25rem 0.5rem rgba(0, 0, 0, 0.15); + display: grid; + grid-column: 1; + grid-row: 1; + grid-template-areas: + "one two three" + "four five six" + "seven eight nine"; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(3, 1fr); + height: 100%; + padding: 2.1rem; + width: 100%; +} +.dot { + align-self: center; + background-color: #676767; + border-radius: 50%; + box-shadow: inset -0.15rem 0.15rem 0.25rem rgba(0, 0, 0, 0.5); + display: block; + height: 1.25rem; + justify-self: center; + width: 1.25rem; +} +.even-roll[data-roll="1"] { + transform: rotateX(360deg) rotateY(720deg) rotateZ(360deg); +} +.even-roll[data-roll="2"] { + transform: rotateX(450deg) rotateY(720deg) rotateZ(360deg); +} +.even-roll[data-roll="3"] { + transform: rotateX(360deg) rotateY(630deg) rotateZ(360deg); +} +.even-roll[data-roll="4"] { + transform: rotateX(360deg) rotateY(810deg) rotateZ(360deg); +} +.even-roll[data-roll="5"] { + transform: rotateX(270deg) rotateY(720deg) rotateZ(360deg); +} +.even-roll[data-roll="6"] { + transform: rotateX(360deg) rotateY(900deg) rotateZ(360deg); +} +.odd-roll[data-roll="1"] { + transform: rotateX(-360deg) rotateY(-720deg) rotateZ(-360deg); +} +.odd-roll[data-roll="2"] { + transform: rotateX(-270deg) rotateY(-720deg) rotateZ(-360deg); +} +.odd-roll[data-roll="3"] { + transform: rotateX(-360deg) rotateY(-810deg) rotateZ(-360deg); +} +.odd-roll[data-roll="4"] { + transform: rotateX(-360deg) rotateY(-630deg) rotateZ(-360deg); +} +.odd-roll[data-roll="5"] { + transform: rotateX(-450deg) rotateY(-720deg) rotateZ(-360deg); +} +.odd-roll[data-roll="6"] { + transform: rotateX(-360deg) rotateY(-900deg) rotateZ(-360deg); +} +[data-side="1"] { + transform: rotate3d(0, 0, 0, 90deg) translateZ(4rem); +} +[data-side="2"] { + transform: rotate3d(-1, 0, 0, 90deg) translateZ(4rem); +} +[data-side="3"] { + transform: rotate3d(0, 1, 0, 90deg) translateZ(4rem); +} +[data-side="4"] { + transform: rotate3d(0, -1, 0, 90deg) translateZ(4rem); +} +[data-side="5"] { + transform: rotate3d(1, 0, 0, 90deg) translateZ(4rem); +} +[data-side="6"] { + transform: rotate3d(1, 0, 0, 180deg) translateZ(4rem); +} +[data-side="1"] .dot:nth-of-type(1) { + grid-area: five; +} +[data-side="2"] .dot:nth-of-type(1) { + grid-area: one; +} +[data-side="2"] .dot:nth-of-type(2) { + grid-area: nine; +} +[data-side="3"] .dot:nth-of-type(1) { + grid-area: one; +} +[data-side="3"] .dot:nth-of-type(2) { + grid-area: five; +} +[data-side="3"] .dot:nth-of-type(3) { + grid-area: nine; +} +[data-side="4"] .dot:nth-of-type(1) { + grid-area: one; +} +[data-side="4"] .dot:nth-of-type(2) { + grid-area: three; +} +[data-side="4"] .dot:nth-of-type(3) { + grid-area: seven; +} +[data-side="4"] .dot:nth-of-type(4) { + grid-area: nine; +} +[data-side="5"] .dot:nth-of-type(1) { + grid-area: one; +} +[data-side="5"] .dot:nth-of-type(2) { + grid-area: three; +} +[data-side="5"] .dot:nth-of-type(3) { + grid-area: five; +} +[data-side="5"] .dot:nth-of-type(4) { + grid-area: seven; +} +[data-side="5"] .dot:nth-of-type(5) { + grid-area: nine; +} +[data-side="6"] .dot:nth-of-type(1) { + grid-area: one; +} +[data-side="6"] .dot:nth-of-type(2) { + grid-area: three; +} +[data-side="6"] .dot:nth-of-type(3) { + grid-area: four; +} +[data-side="6"] .dot:nth-of-type(4) { + grid-area: six; +} +[data-side="6"] .dot:nth-of-type(5) { + grid-area: seven; +} +[data-side="6"] .dot:nth-of-type(6) { + grid-area: nine; +} + +@media (min-width: 900px) { + .dice { + perspective: 1300px; + } +} + +.app-modal-window .modal-dialog { + min-width: 700px; +} + +.modal-backdrop{ + opacity: 0.6 !important; +} + +.modal{ + opacity: 1; +} + +.modal-header{ + padding: 8px 15px; +} + +.modal-content{ + margin-top: 40%; +} + +.fas{ + padding: 10px; + background: #EEEEEE; + border-bottom: 1px solid #CCCCCC; + border-right: 1px solid #CCCCCC; + border-top: 1px solid #CCCCCC; + border-radius: 0 6px 6px 0; + min-width: 40px; + text-align: center; +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/corda.png b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/corda.png new file mode 100644 index 00000000..f5150218 Binary files /dev/null and b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/corda.png differ diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/game-board.jpg b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/game-board.jpg new file mode 100644 index 00000000..cdc59d97 Binary files /dev/null and b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/game-board.jpg differ diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/snl-banner.png b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/snl-banner.png new file mode 100644 index 00000000..79b38160 Binary files /dev/null and b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/snl-banner.png differ diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/spinner.svg b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/spinner.svg new file mode 100644 index 00000000..3e920f67 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/img/spinner.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/public/index.html b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/index.html new file mode 100644 index 00000000..5bd691eb --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/index.html @@ -0,0 +1,207 @@ + + + + + Snakes and Ladders Powered By Corda + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+ +
Powered by + Corda
+
+
+ + +
+ + +
+
+
+
+ +
+
+
+
Snakes And Ladders
+
+
+ +
+
+ +
+
+ + + +
+
+ +
+ + +
+
+ + +
+
+
+
+
+
Powered by + Corda +
+
+
+
+ + +
+
+
+
+ +
+
+
+
+
+
+
+ Game Id: {{appCtrl.game.linearId.id}} +
+
+
+
+
+ {{appCtrl.game.player1}} +
+
{{appCtrl.game.player1Pos}}
+
+
+
+ {{appCtrl.game.player2}} +
+
{{appCtrl.game.player2Pos}}
+
+
+
+
+ +
+
+
+
+
    +
  1. + +
  2. +
  3. + + +
  4. +
  5. + + + +
  6. +
  7. + + + + +
  8. +
  9. + + + + + +
  10. +
  11. + + + + + + +
  12. +
+
+
+
+
+
+
+

{{message}}

+
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/client/src/main/resources/public/js/angular-module.js b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/js/angular-module.js new file mode 100644 index 00000000..ede2fe1e --- /dev/null +++ b/Advanced/snakesandladders-cordapp/client/src/main/resources/public/js/angular-module.js @@ -0,0 +1,255 @@ +const app = angular.module('snlApp', ['ui.bootstrap', 'toastr']); + +let httpHeaders = { + headers : { + "Content-Type": "application/x-www-form-urlencoded" + } + }; + +app.controller('AppController', function($http, toastr, $uibModal, $interval) { + const demoApp = this; + const apiBaseURL = "/api/snl/"; + + demoApp.landingScreen = true; + demoApp.gameScreen = false; + demoApp.showSpinner = false; + demoApp.pristine = true; + demoApp.isP1 = false; + demoApp.player = ""; + demoApp.playerNum = 1; + demoApp.rolledFlag = false; + demoApp.dice = { + diceRollFlag : false, + roll: 6, + id: "die-6" + } + demoApp.game = {}; + + demoApp.console = { + welcome: "Welcome to Snakes and Ladders powered by Corda!", + myTurn: "You turn to play. Roll the Dice", + myRoll: "You rolled ", +// snake: "Oops!! You landed on a snake!", +// ladder: "Great!! You found on a ladder!", + winner: "Congratulation!! You WON.", + loser: " won. Hard Luck!! You Lose.", + opTurn: "Waiting for your opposition to complete their turn.", + opRoll: "Opposition Player rolled " +// oppSnake: "And he landed on a snake.", +// oppLadder: "And ge found on a ladder.", + }; + + demoApp.consoleMessages = [demoApp.console.welcome]; + + demoApp.startGame = () => { + demoApp.showSpinner = true; + $http.post(apiBaseURL + 'createGame', {player1: demoApp.player1, player2: demoApp.player2}) + .then((response) => { + if(response.data && response.data.status){ + toastr.success('Game Created Successfully!'); + var gameId = response.data.data; + demoApp.gameScreen = true; + demoApp.landingScreen = false; + demoApp.game = demoApp.fetchGame(gameId, 1); + demoApp.isP1 = true; + }else{ + toastr.error(response.data? response.data.message: "Something went wrong. Please try again later!"); + } + demoApp.showSpinner = false; + }); + } + + demoApp.loadGame = (gameId, player) => { + if(player ==1){ + demoApp.isP1 = true; + } + demoApp.fetchGame(gameId, player, 0); + } + + demoApp.rollDice = () => { + demoApp.showSpinner = true; + $http.get(apiBaseURL + 'rollDice') + .then((response) => { + if(response.data && response.data.status){ + demoApp.dice.diceRollFlag = !demoApp.dice.diceRollFlag; + var roll = response.data.data + demoApp.dice.roll = roll; + demoApp.dice.id = "die-" + roll; + demoApp.playMove(roll); + }else{ + toastr.error(response.data? response.data.message: "Something went wrong. Please try again later!"); + } + demoApp.showSpinner = false; + }); + } + + + demoApp.playMove = (rolledNumber) => { + demoApp.showSpinner = true; + $http.post(apiBaseURL + 'playerMove', {player: demoApp.player, gameId: demoApp.game.linearId.id, rolledNumber: rolledNumber}) + .then((response) => { + if(response.data && response.data.status){ + demoApp.rolledFlag = true; + demoApp.pristine = false; + demoApp.fetchGame(demoApp.game.linearId.id); + }else{ + toastr.error(response.data? response.data.message: "Something went wrong. Please try again later!"); + } + demoApp.showSpinner = false; + }); + } + + // 0 - loadGame + demoApp.animatePlayer = (flag) => { + var playerSelector = ""; + if(flag == 0){ + updateP1Pos(); + updateP2Pos(); + }else{ + if(demoApp.playerNum ==1){ + updateP1Pos(); + }else if(demoApp.playerNum == 2){ + updateP2Pos(); + } + } + } + + const updateP1Pos = () => { + // X position + if((demoApp.game.player1Pos % 20 == 0)) + angular.element(document.querySelector('#player1'))[0].style.left = (((demoApp.game.player1Pos-1) %10)*80) + 10 - 720+ "px"; + else if((parseInt(demoApp.game.player1Pos/10) % 2 == 0) || (demoApp.game.player1Pos % 10 == 0 )) + angular.element(document.querySelector('#player1'))[0].style.left = (((demoApp.game.player1Pos-1) %10)*80) + 10 + "px" + else + angular.element(document.querySelector('#player1'))[0].style.left = ((10 - (demoApp.game.player1Pos %10))*80) + 10 + "px" + + // Y Position + + if(parseInt(demoApp.game.player1Pos/10) % 10 == 0 && parseInt(demoApp.game.player1Pos/10) != 0 || demoApp.game.player1Pos%10 == 0) + angular.element(document.querySelector('#player1'))[0].style.top = 750 - (parseInt(demoApp.game.player1Pos/10) * 80) + 80 + "px"; + else + angular.element(document.querySelector('#player1'))[0].style.top = 750 - (parseInt(demoApp.game.player1Pos/10) * 80) + "px"; + } + + const updateP2Pos = () => { + if((demoApp.game.player2Pos % 20 == 0)) + angular.element(document.querySelector('#player2'))[0].style.left = (((demoApp.game.player2Pos-1) %10)*80) + 30 - 720+ "px"; + else if((parseInt(demoApp.game.player2Pos/10) % 2 == 0) || (demoApp.game.player2Pos % 10 == 0 )) + angular.element(document.querySelector('#player2'))[0].style.left = (((demoApp.game.player2Pos-1) %10)*80) + 30 + "px"; + else + angular.element(document.querySelector('#player2'))[0].style.left = ((10 - (demoApp.game.player2Pos %10))*80) + 30 + "px" + + if(parseInt(demoApp.game.player2Pos/10) % 10 == 0 && parseInt(demoApp.game.player2Pos/10) != 0 || demoApp.game.player2Pos%10 == 0) + angular.element(document.querySelector('#player2'))[0].style.top = 750 - (parseInt(demoApp.game.player2Pos/10) * 80) + 80 + "px"; + else + angular.element(document.querySelector('#player2'))[0].style.top = 750 - (parseInt(demoApp.game.player2Pos/10) * 80) + "px"; + } + + + demoApp.openCreateAccountModal = () => { + const accountModel = $uibModal.open({ + templateUrl: 'createAccountModal.html', + controller: 'CreateAccountModalCtrl', + controllerAs: 'createAccountModalCtrl', + windowClass: 'app-modal-window', + resolve: { + demoApp: () => demoApp, + apiBaseURL: () => apiBaseURL, + toastr: () => toastr, + } + }); + + accountModel.result.then(() => {}, () => {}); + }; + + demoApp.fetchGame = (gameId, player, flag) => { + $http.get(apiBaseURL + 'getGame/' + gameId) + .then((response) => { + if(response.data && response.data.status){ + demoApp.gameScreen = true; + demoApp.landingScreen = false; + demoApp.game = response.data.data; + if(player == 1){ + demoApp.player = demoApp.game.player1; + demoApp.playerNum = 1; + }else if(player == 2){ + demoApp.player = demoApp.game.player2; + demoApp.playerNum = 2; + } + demoApp.animatePlayer(flag); + if(demoApp.game.winner != null){ + demoApp.handleConsole(true); + }else{ + demoApp.handleConsole(); + } + + }else{ + toastr.error(response.data? response.data.message: "Something went wrong. Please try again later!"); + } + }); + } + + demoApp.handleConsole = (gameOver) => { + demoApp.consoleMessages = []; + if(gameOver){ + if(demoApp.game.winner == demoApp.player){ + demoApp.consoleMessages.push(demoApp.console.winner); + }else{ + demoApp.consoleMessages.push(demoApp.game.winner + " " + demoApp.console.loser); + } + }else{ + demoApp.consoleMessages.push(demoApp.console.welcome); + if(demoApp.rolledFlag){ + demoApp.consoleMessages.push(demoApp.console.myRoll + " " + demoApp.game.lastRoll + + ". Your new position is " + (demoApp.isP1? demoApp.game.player1Pos:demoApp.game.player2Pos)); + } + + if(demoApp.player == demoApp.game.currentPlayer){ + if(!demoApp.pristine){ + demoApp.consoleMessages.push(demoApp.console.opRoll + " " + demoApp.game.lastRoll + + ". Their new position is " + (demoApp.isP1? demoApp.game.player2Pos:demoApp.game.player1Pos)); + } + demoApp.consoleMessages.push(demoApp.console.myTurn); + } + else{ + demoApp.pristine = false; + demoApp.consoleMessages.push(demoApp.console.opTurn); + } + } + } + + $interval(function(){ + console.log("Executed"); + if(demoApp.game.linearId != undefined) + demoApp.fetchGame(demoApp.game.linearId.id, undefined, 0); + },5000) + +}); + + +app.controller('CreateAccountModalCtrl', function ($http, $uibModalInstance, $uibModal, demoApp, apiBaseURL, toastr) { + const createAccountModel = this; + + createAccountModel.form = {}; + + createAccountModel.create = () => { + if(createAccountModel.form.name == undefined || createAccountModel.form.name == '' ){ + toastr.error("Enter Gamer Account Name!"); + }else{ + demoApp.showSpinner = true; + $http.get(apiBaseURL + 'account/create/' + createAccountModel.form.name) + .then((response) => { + if(response.data && response.data.status){ + toastr.success('Gamer Account Create Successfully'); + $uibModalInstance.dismiss(); + }else{ + toastr.error(response.data? response.data.message: "Something went wrong. Please try again later!"); + } + demoApp.showSpinner = false; + }); + } + } + + createAccountModel.cancel = () => $uibModalInstance.dismiss(); + +}); diff --git a/Basic/yo-cordapp/config/dev/log4j2.xml b/Advanced/snakesandladders-cordapp/config/dev/log4j2.xml similarity index 100% rename from Basic/yo-cordapp/config/dev/log4j2.xml rename to Advanced/snakesandladders-cordapp/config/dev/log4j2.xml diff --git a/Advanced/snakesandladders-cordapp/config/test/log4j2.xml b/Advanced/snakesandladders-cordapp/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Advanced/snakesandladders-cordapp/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Advanced/snakesandladders-cordapp/contracts/build.gradle b/Advanced/snakesandladders-cordapp/contracts/build.gradle new file mode 100644 index 00000000..03b7d863 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Snakes And Ladder Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/contracts/BoardConfigContract.java b/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/contracts/BoardConfigContract.java new file mode 100644 index 00000000..8de6140a --- /dev/null +++ b/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/contracts/BoardConfigContract.java @@ -0,0 +1,31 @@ +package net.corda.samples.snl.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.snl.states.BoardConfig; +import org.jetbrains.annotations.NotNull; + +public class BoardConfigContract implements Contract { + public static String ID = "BoardConfigContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract verification logic should be implemented here + + if(tx.getOutputStates().size() != 1 || tx.getInputStates().size() != 0) + throw new IllegalArgumentException("Zero Input and One Output Expected"); + + if(!(tx.getOutput(0) instanceof BoardConfig)) + throw new IllegalArgumentException("Output of type BoardConfig expected"); + + BoardConfig boardConfig = (BoardConfig) tx.getOutput(0); + if(boardConfig.getSnakePositions() == null || boardConfig.getSnakePositions().size() ==0 + || boardConfig.getLadderPositions() == null || boardConfig.getLadderPositions().size() == 0) + throw new IllegalArgumentException("Snake and Ladder Positions should not be empty or null"); + } + + public interface Commands extends CommandData { + class Create implements GameBoardContract.Commands {} + } +} diff --git a/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/contracts/GameBoardContract.java b/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/contracts/GameBoardContract.java new file mode 100644 index 00000000..2ac0ef9b --- /dev/null +++ b/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/contracts/GameBoardContract.java @@ -0,0 +1,63 @@ +package net.corda.samples.snl.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.snl.states.BoardConfig; +import net.corda.samples.snl.states.GameBoard; +import org.jetbrains.annotations.NotNull; + +public class GameBoardContract implements Contract { + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract verification logic should be implemented here + if(tx.getCommands().size() != 1) + throw new IllegalArgumentException("One command Expected"); + + if(tx.getCommand(0).getValue() instanceof Commands.Create){ + verifyCreate(tx); + }else if(tx.getCommand(0).getValue() instanceof Commands.PlayMove){ + verifyPlay(tx); + } + + } + + private void verifyCreate(LedgerTransaction tx) throws IllegalArgumentException{ + // Contract verification logic for create game should be implemented here + if(tx.getOutputStates().size() != 1 || tx.getInputStates().size() != 0) + throw new IllegalArgumentException("Zero Input and One Output Expected"); + + if(!(tx.getOutput(0) instanceof GameBoard)) + throw new IllegalArgumentException("Output of type GameBoard expected"); + + + } + + private void verifyPlay(LedgerTransaction tx) throws IllegalArgumentException { + // Contract verification logic for play move should be implemented here + + if(tx.getReferences().size() == 0 || !(tx.getReferenceInput(0) instanceof BoardConfig)){ + throw new IllegalArgumentException("One reference Input of BoardConfig Expected"); + } + + if(tx.getOutputStates().size() != 1 || tx.getInputStates().size() != 1) + throw new IllegalArgumentException("One Input and One Output Expected"); + + } + + public interface Commands extends CommandData { + class Create implements Commands {} + class PlayMove implements Commands { + private int roll; + + public PlayMove(int roll) { + this.roll = roll; + } + + public int getRoll() { + return roll; + } + } + } +} diff --git a/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/states/BoardConfig.java b/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/states/BoardConfig.java new file mode 100644 index 00000000..1596b6c4 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/states/BoardConfig.java @@ -0,0 +1,38 @@ +package net.corda.samples.snl.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.ContractState; +import net.corda.core.identity.AbstractParty; +import net.corda.samples.snl.contracts.BoardConfigContract; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +@BelongsToContract(BoardConfigContract.class) +public class BoardConfig implements ContractState { + + private Map ladderPositions; + private Map snakePositions; + private List players; + + public BoardConfig(Map ladderPositions, Map snakePositions, List players) { + this.ladderPositions = ladderPositions; + this.snakePositions = snakePositions; + this.players = players; + } + + public Map getLadderPositions() { + return ladderPositions; + } + + public Map getSnakePositions() { + return snakePositions; + } + + @NotNull + @Override + public List getParticipants() { + return players; + } +} diff --git a/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/states/GameBoard.java b/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/states/GameBoard.java new file mode 100644 index 00000000..cc1a5fa1 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/contracts/src/main/java/net/corda/samples/snl/states/GameBoard.java @@ -0,0 +1,76 @@ +package net.corda.samples.snl.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.samples.snl.contracts.GameBoardContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +@BelongsToContract(GameBoardContract.class) +public class GameBoard implements LinearState { + + private UniqueIdentifier linearId; + private AbstractParty player1; + private AbstractParty player2; + private String currentPlayer; + private int player1Pos; + private int player2Pos; + private String winner; + private int lastRoll; + + public GameBoard(UniqueIdentifier linearId, AbstractParty player1, AbstractParty player2, + String currentPlayer, int player1Pos, int player2Pos, String winner, int lastRoll) { + this.linearId = linearId; + this.player1 = player1; + this.player2 = player2; + this.currentPlayer = currentPlayer; + this.player1Pos = player1Pos; + this.player2Pos = player2Pos; + this.winner = winner; + this.lastRoll = lastRoll; + } + + public AbstractParty getPlayer1() { + return player1; + } + + public AbstractParty getPlayer2() { + return player2; + } + + public String getCurrentPlayer() { + return currentPlayer; + } + + public int getPlayer1Pos() { + return player1Pos; + } + + public int getPlayer2Pos() { + return player2Pos; + } + + public String getWinner() { + return winner; + } + + public int getLastRoll() { + return lastRoll; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return linearId; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(player1, player2); + } +} diff --git a/Advanced/snakesandladders-cordapp/contracts/src/test/java/net/corda/samples/snl/contracts/ContractTests.java b/Advanced/snakesandladders-cordapp/contracts/src/test/java/net/corda/samples/snl/contracts/ContractTests.java new file mode 100644 index 00000000..76126208 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/contracts/src/test/java/net/corda/samples/snl/contracts/ContractTests.java @@ -0,0 +1,64 @@ +package net.corda.samples.snl.contracts; + +import net.corda.core.identity.CordaX500Name; +import net.corda.samples.snl.states.BoardConfig; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; + +import static net.corda.testing.node.NodeTestUtils.transaction; + +public class ContractTests { + private final TestIdentity p1 = new TestIdentity(new CordaX500Name("PL1", "", "IN")); + private final TestIdentity p2 = new TestIdentity(new CordaX500Name("PL2", "", "IN")); + private MockServices ledgerServices = new MockServices(new TestIdentity(new CordaX500Name("TestId", "", "IN"))); + + private BoardConfig boardConfig_f = new BoardConfig(null, null, Arrays.asList(p1.getParty(), p2.getParty())); + + private BoardConfig boardConfig_s = new BoardConfig(new LinkedHashMap(Collections.singletonMap(1, 5)), + new LinkedHashMap(Collections.singletonMap(1, 5)), Arrays.asList(p1.getParty(), p2.getParty())); + + @Test + public void testVerifyCreateBoardZeroInputs() { + transaction(ledgerServices, tx -> { + // Has an input, will fail. + tx.input(BoardConfigContract.ID, boardConfig_s); + tx.output(BoardConfigContract.ID, boardConfig_s); + tx.command(Arrays.asList(p1.getPublicKey(), p2.getPublicKey()), new BoardConfigContract.Commands.Create()); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + // Has no input, will verify. + tx.output(BoardConfigContract.ID, boardConfig_s); + tx.command(Arrays.asList(p1.getPublicKey(), p2.getPublicKey()), new BoardConfigContract.Commands.Create()); + tx.verifies(); + return null; + }); + } + + @Test + public void testVerifyCreateBoardNonEmptySnakeAndLadderPositions() { + transaction(ledgerServices, tx -> { + // Has empty/ null snake and ladder positions, should fail + tx.output(BoardConfigContract.ID, boardConfig_f); + tx.command(Arrays.asList(p1.getPublicKey(), p2.getPublicKey()), new BoardConfigContract.Commands.Create()); + tx.fails(); + return null; + }); + + transaction(ledgerServices, tx -> { + // Has no input, will verify. + tx.output(BoardConfigContract.ID, boardConfig_s); + tx.command(Arrays.asList(p1.getPublicKey(), p2.getPublicKey()), new BoardConfigContract.Commands.Create()); + tx.verifies(); + return null; + }); + } + +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/contracts/src/test/java/net/corda/samples/snl/contracts/StateTests.java b/Advanced/snakesandladders-cordapp/contracts/src/test/java/net/corda/samples/snl/contracts/StateTests.java new file mode 100644 index 00000000..e0ab71c3 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/contracts/src/test/java/net/corda/samples/snl/contracts/StateTests.java @@ -0,0 +1,18 @@ +package net.corda.samples.snl.contracts; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.samples.snl.states.GameBoard; +import net.corda.testing.node.MockServices; +import org.junit.Test; + +public class StateTests { + private final MockServices ledgerServices = new MockServices(); + + @Test + public void hasFieldOfCorrectType() throws NoSuchFieldException { + // Does the message field exist? + GameBoard.class.getDeclaredField("linearId"); + // Is the message field of the correct type? + assert(GameBoard.class.getDeclaredField("linearId").getType().equals(UniqueIdentifier.class)); + } +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/gradle.properties b/Advanced/snakesandladders-cordapp/gradle.properties new file mode 100644 index 00000000..834d4552 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=com.supplychain +version=0.1 \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/gradle/wrapper/gradle-wrapper.jar b/Advanced/snakesandladders-cordapp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Advanced/snakesandladders-cordapp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Advanced/snakesandladders-cordapp/gradle/wrapper/gradle-wrapper.properties b/Advanced/snakesandladders-cordapp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ae01072d --- /dev/null +++ b/Advanced/snakesandladders-cordapp/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Aug 25 12:50:39 BST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/Advanced/snakesandladders-cordapp/gradlew b/Advanced/snakesandladders-cordapp/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Basic/yo-cordapp/gradlew.bat b/Advanced/snakesandladders-cordapp/gradlew.bat similarity index 100% rename from Basic/yo-cordapp/gradlew.bat rename to Advanced/snakesandladders-cordapp/gradlew.bat diff --git a/Advanced/snakesandladders-cordapp/oracle-flows/build.gradle b/Advanced/snakesandladders-cordapp/oracle-flows/build.gradle new file mode 100644 index 00000000..1fcf8308 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/oracle-flows/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Oracle Flows Cordapp" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/test") + } + } +} + + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":oracle-service") + +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/DiceRollerFlow.java b/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/DiceRollerFlow.java new file mode 100644 index 00000000..f4dc3ed9 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/DiceRollerFlow.java @@ -0,0 +1,26 @@ +package net.corda.samples.snl.oracle.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; + +@InitiatingFlow +public class DiceRollerFlow extends FlowLogic { + + private String player; + private Party oracle; + + public DiceRollerFlow(String player, Party oracle) { + this.player = player; + this.oracle = oracle; + } + + @Override + @Suspendable + public Integer call() throws FlowException { + return initiateFlow(oracle).sendAndReceive(Integer.class, player).unwrap(it->it); + } +} diff --git a/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/DiceRollerFlowHandler.java b/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/DiceRollerFlowHandler.java new file mode 100644 index 00000000..caf74227 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/DiceRollerFlowHandler.java @@ -0,0 +1,28 @@ +package net.corda.samples.snl.oracle.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.FlowSession; +import net.corda.core.flows.InitiatedBy; +import net.corda.samples.snl.service.DiceRollService; + +@InitiatedBy(DiceRollerFlow.class) +public class DiceRollerFlowHandler extends FlowLogic { + + private FlowSession requestSession; + + public DiceRollerFlowHandler(FlowSession requestSession) { + this.requestSession = requestSession; + } + + @Override + @Suspendable + public Void call() throws FlowException { + String player = requestSession.receive(String.class).unwrap(it -> it); + DiceRollService service = getServiceHub().cordaService(DiceRollService.class); + int roll = service.diceRoll(player); + requestSession.send(roll); + return null; + } +} diff --git a/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/OracleSignatureFlow.java b/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/OracleSignatureFlow.java new file mode 100644 index 00000000..7a6cf94f --- /dev/null +++ b/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/OracleSignatureFlow.java @@ -0,0 +1,29 @@ +package net.corda.samples.snl.oracle.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.crypto.TransactionSignature; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.FlowSession; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.identity.Party; +import net.corda.core.transactions.FilteredTransaction; + +@InitiatingFlow +public class OracleSignatureFlow extends FlowLogic { + + private final Party oracle; + private final FilteredTransaction ftx; + + public OracleSignatureFlow(Party oracle, FilteredTransaction ftx) { + this.oracle = oracle; + this.ftx = ftx; + } + + @Suspendable + @Override + public TransactionSignature call() throws FlowException { + FlowSession session = initiateFlow(oracle); + return session.sendAndReceive(TransactionSignature.class, ftx).unwrap(it -> it); + } +} diff --git a/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/OracleSignatureFlowHandler.java b/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/OracleSignatureFlowHandler.java new file mode 100644 index 00000000..b473ae85 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/oracle-flows/src/main/java/net/corda/samples/snl/oracle/flows/OracleSignatureFlowHandler.java @@ -0,0 +1,35 @@ +package net.corda.samples.snl.oracle.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.crypto.TransactionSignature; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.FlowSession; +import net.corda.core.flows.InitiatedBy; +import net.corda.core.transactions.FilteredTransaction; +import net.corda.samples.snl.service.DiceRollService; + + +@InitiatedBy(OracleSignatureFlow.class) +public class OracleSignatureFlowHandler extends FlowLogic { + + private FlowSession requestSession; + + public OracleSignatureFlowHandler(FlowSession requestSession) { + this.requestSession = requestSession; + } + + @Override + @Suspendable + public Void call() throws FlowException { + FilteredTransaction transaction = requestSession.receive(FilteredTransaction.class).unwrap(it->it); + TransactionSignature signature = null; + try{ + signature = getServiceHub().cordaService(DiceRollService.class).sign(transaction); + }catch (Exception e){ + throw new FlowException(e); + } + requestSession.send(signature); + return null; + } +} diff --git a/Advanced/snakesandladders-cordapp/oracle-service/build.gradle b/Advanced/snakesandladders-cordapp/oracle-service/build.gradle new file mode 100644 index 00000000..ee1ff63b --- /dev/null +++ b/Advanced/snakesandladders-cordapp/oracle-service/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Oracle Service Cordapp" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/test") + } + } +} + + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/oracle-service/src/main/java/net/corda/samples/snl/service/DiceRollService.java b/Advanced/snakesandladders-cordapp/oracle-service/src/main/java/net/corda/samples/snl/service/DiceRollService.java new file mode 100644 index 00000000..f254e43a --- /dev/null +++ b/Advanced/snakesandladders-cordapp/oracle-service/src/main/java/net/corda/samples/snl/service/DiceRollService.java @@ -0,0 +1,45 @@ +package net.corda.samples.snl.service; + +import net.corda.core.crypto.TransactionSignature; +import net.corda.core.node.AppServiceHub; +import net.corda.core.node.services.CordaService; +import net.corda.core.serialization.SingletonSerializeAsToken; +import net.corda.core.transactions.FilteredTransaction; +import net.corda.core.transactions.FilteredTransactionVerificationException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + + +@CordaService +public class DiceRollService extends SingletonSerializeAsToken { + + private final AppServiceHub serviceHub; + private Map rollMap; + + public DiceRollService(AppServiceHub serviceHub) { + this.serviceHub = serviceHub; + this.rollMap = new HashMap<>(); + } + + public Integer diceRoll (String player){ + Random ran = new Random(); + int roll = ran.nextInt(6) + 1; + rollMap.put(player, roll); + return roll; + } + + public TransactionSignature sign(FilteredTransaction transaction) throws FilteredTransactionVerificationException { + + transaction.verify(); + + boolean isValid = true; // Additional verification logic here. + + if(isValid){ + return serviceHub.createSignature(transaction, serviceHub.getMyInfo().getLegalIdentities().get(0).getOwningKey()); + }else{ + throw new IllegalArgumentException(); + } + } +} diff --git a/Advanced/snakesandladders-cordapp/repositories.gradle b/Advanced/snakesandladders-cordapp/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Advanced/snakesandladders-cordapp/settings.gradle b/Advanced/snakesandladders-cordapp/settings.gradle new file mode 100644 index 00000000..7545e319 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/settings.gradle @@ -0,0 +1,5 @@ +include 'workflows' +include 'contracts' +include 'oracle-service' +include 'client' +include 'oracle-flows' \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/snaps/Create_Player.png b/Advanced/snakesandladders-cordapp/snaps/Create_Player.png new file mode 100644 index 00000000..1b42b439 Binary files /dev/null and b/Advanced/snakesandladders-cordapp/snaps/Create_Player.png differ diff --git a/Advanced/snakesandladders-cordapp/snaps/game.png b/Advanced/snakesandladders-cordapp/snaps/game.png new file mode 100644 index 00000000..5f698cae Binary files /dev/null and b/Advanced/snakesandladders-cordapp/snaps/game.png differ diff --git a/Advanced/snakesandladders-cordapp/workflows/build.gradle b/Advanced/snakesandladders-cordapp/workflows/build.gradle new file mode 100644 index 00000000..d37b9a3b --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/build.gradle @@ -0,0 +1,72 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Snakes And Ladder Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +dependencies { + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") + cordapp project(":oracle-flows") + + //Account dependencies + cordaCompile "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" + cordaCompile "$accounts_release_group:accounts-workflows:$accounts_release_version" + cordaCompile "$accounts_release_group:accounts-contracts:$accounts_release_version" + +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/workflows/src/integrationTest/java/net/corda/samples/snl/flows/DriverBasedTest.java b/Advanced/snakesandladders-cordapp/workflows/src/integrationTest/java/net/corda/samples/snl/flows/DriverBasedTest.java new file mode 100644 index 00000000..71186cae --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/integrationTest/java/net/corda/samples/snl/flows/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.snl.flows; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateAndShareAccountFlow.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateAndShareAccountFlow.java new file mode 100644 index 00000000..17c1cb96 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateAndShareAccountFlow.java @@ -0,0 +1,54 @@ +package net.corda.samples.snl.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.flows.CreateAccount; +import com.r3.corda.lib.accounts.workflows.flows.ShareAccountInfo; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; + +import java.util.List; +import java.util.stream.Collectors; +import net.corda.core.identity.CordaX500Name; + +@StartableByRPC +@InitiatingFlow +public class CreateAndShareAccountFlow extends FlowLogic { + + private final String accountName; + + public CreateAndShareAccountFlow(String accountName) { + this.accountName = accountName; + } + + @Override + @Suspendable + public String call() throws FlowException { + + //Call inbuilt CreateAccount flow to create the AccountInfo object + StateAndRef accountInfoStateAndRef = + (StateAndRef) subFlow(new CreateAccount(accountName)); + + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + Party oracle = getServiceHub().getNetworkMapCache() + .getNodeByLegalName(CordaX500Name.parse("O=Oracle,L=Mumbai,C=IN")).getLegalIdentities().get(0); + + List parties = getServiceHub().getNetworkMapCache().getAllNodes().stream() + .map(nodeInfo -> nodeInfo.getLegalIdentities().get(0)) + .collect(Collectors.toList()); + parties.remove(getOurIdentity()); + parties.remove(notary); + parties.remove(oracle); + + //Share this AccountInfo object with the parties who want to transact with this account + subFlow(new ShareAccountInfo(accountInfoStateAndRef, parties)); + return "" + accountName +"has been created and shared to " + parties+"."; + } +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateBoardConfig.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateBoardConfig.java new file mode 100644 index 00000000..8af2de27 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateBoardConfig.java @@ -0,0 +1,125 @@ +package net.corda.samples.snl.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.commands.Create; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import com.sun.istack.NotNull; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.snl.states.BoardConfig; + +import java.security.SignatureException; +import java.util.*; +import net.corda.core.identity.CordaX500Name; + +public class CreateBoardConfig { + private CreateBoardConfig() {} + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + + private String player1; + private String player2; + + public Initiator(String player1, String player2) { + this.player1 = player1; + this.player2 = player2; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); + + List> p1accountInfo = accountService.accountInfo(player1); + if(p1accountInfo.size() ==0) + throw new FlowException("Player "+ player1 + " doesn't exist!"); + List> p2accountInfo = accountService.accountInfo(player2); + if(p1accountInfo.size() ==0) + throw new FlowException("Player "+ player2 + " doesn't exist!"); + + AbstractParty player1 = subFlow(new RequestKeyForAccount(p1accountInfo.get(0).getState().getData())); + AbstractParty player2 = subFlow(new RequestKeyForAccount(p2accountInfo.get(0).getState().getData())); + + Map ladderPositions = new LinkedHashMap<>(); + ladderPositions.put(2, 45); + ladderPositions.put(4, 27); + ladderPositions.put(9, 31); + ladderPositions.put(47, 84); + ladderPositions.put(70, 87); + ladderPositions.put(71, 91); + + Map snakePositions = new LinkedHashMap<>(); + snakePositions.put(16, 8); + snakePositions.put(52, 28); + snakePositions.put(78, 25); + snakePositions.put(93, 89); + snakePositions.put(95, 75); + snakePositions.put(99, 21); + + BoardConfig boardConfig = new BoardConfig(ladderPositions, snakePositions, Arrays.asList(player1, player2)); + + TransactionBuilder transactionBuilder = new TransactionBuilder(notary) + .addOutputState(boardConfig) + .addCommand(new Create(), Arrays.asList(player1.getOwningKey(), player2.getOwningKey())); + + transactionBuilder.verify(getServiceHub()); + + SignedTransaction selfSignedTransaction = + getServiceHub().signInitialTransaction(transactionBuilder, player1.getOwningKey()); + + FlowSession player2Session = initiateFlow(p2accountInfo.get(0).getState().getData().getHost()); + SignedTransaction signedTransaction = + subFlow(new CollectSignaturesFlow(selfSignedTransaction, Collections.singletonList(player2Session), + Collections.singleton(player1.getOwningKey()))); + + if(!p2accountInfo.get(0).getState().getData().getHost().equals(getOurIdentity())) { + signedTransaction = subFlow(new FinalityFlow(signedTransaction, Collections.singletonList(player2Session))); + try { + accountService.shareStateAndSyncAccounts(signedTransaction + .toLedgerTransaction(getServiceHub()).outRef(0) ,player2Session.getCounterparty()); + } catch (SignatureException e) { + e.printStackTrace(); + } + return signedTransaction; + }else{ + return subFlow(new FinalityFlow(signedTransaction, Collections.emptyList())); + } + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + private FlowSession counterpartySession; + + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + subFlow(new SignTransactionFlow(counterpartySession) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // Custom Logic to validate transaction. + } + }); + if(!counterpartySession.getCounterparty().equals(getOurIdentity())) + return subFlow(new ReceiveFinalityFlow(counterpartySession)); + + return null; + } + } +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateGameFlow.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateGameFlow.java new file mode 100644 index 00000000..fb664384 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/CreateGameFlow.java @@ -0,0 +1,123 @@ +package net.corda.samples.snl.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.commands.Create; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import com.sun.istack.NotNull; +import net.corda.core.contracts.ReferencedStateAndRef; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.snl.states.BoardConfig; +import net.corda.samples.snl.states.GameBoard; + +import java.security.SignatureException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import net.corda.core.identity.CordaX500Name; + +public class CreateGameFlow { + + private CreateGameFlow() {} + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic{ + + private String player1; + private String player2; + + public Initiator(String player1, String player2) { + this.player1 = player1; + this.player2 = player2; + } + + @Override + @Suspendable + public String call() throws FlowException { + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); + + List> p1accountInfo = accountService.accountInfo(player1); + if(p1accountInfo.size() ==0) + throw new FlowException("Player "+ player1 + " doesn't exist!"); + List> p2accountInfo = accountService.accountInfo(player2); + if(p1accountInfo.size() ==0) + throw new FlowException("Player "+ player2 + " doesn't exist!"); + + AbstractParty player1 = subFlow(new RequestKeyForAccount(p1accountInfo.get(0).getState().getData())); + AbstractParty player2 = subFlow(new RequestKeyForAccount(p2accountInfo.get(0).getState().getData())); + + GameBoard gameBoard = new GameBoard(new UniqueIdentifier(null, UUID.randomUUID()), + player1, player2, this.player1, 1, 1, null, 0); + + List> boardConfigList = + getServiceHub().getVaultService().queryBy(BoardConfig.class).getStates(); + if(boardConfigList.size() == 0) + throw new FlowException("Please create board config first"); + + TransactionBuilder transactionBuilder = new TransactionBuilder(notary) + .addOutputState(gameBoard) + .addCommand(new Create(), Arrays.asList(player1.getOwningKey(), player2.getOwningKey())) + .addReferenceState(new ReferencedStateAndRef<>(boardConfigList.get(0))); + + transactionBuilder.verify(getServiceHub()); + + SignedTransaction selfSignedTransaction = + getServiceHub().signInitialTransaction(transactionBuilder, player1.getOwningKey()); + + FlowSession player2Session = initiateFlow(p2accountInfo.get(0).getState().getData().getHost()); + SignedTransaction signedTransaction = + subFlow(new CollectSignaturesFlow(selfSignedTransaction, Collections.singletonList(player2Session), + Collections.singletonList(player1.getOwningKey()))); + + if(!p2accountInfo.get(0).getState().getData().getHost().equals(getOurIdentity())) { + subFlow(new FinalityFlow(signedTransaction, Collections.singletonList(player2Session))); + try { + accountService.shareStateAndSyncAccounts(signedTransaction + .toLedgerTransaction(getServiceHub(), false).outRef(0) ,player2Session.getCounterparty()); + } catch (SignatureException e) { + e.printStackTrace(); + } + }else{ + subFlow(new FinalityFlow(signedTransaction, Collections.emptyList())); + } + return gameBoard.getLinearId().getId().toString(); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + private FlowSession counterpartySession; + + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + subFlow(new SignTransactionFlow(counterpartySession) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // Custom Logic to validate transaction. + } + }); + if(!counterpartySession.getCounterparty().equals(getOurIdentity())) + return subFlow(new ReceiveFinalityFlow(counterpartySession)); + + return null; + } + } +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/GameInfo.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/GameInfo.java new file mode 100644 index 00000000..fce43c0f --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/GameInfo.java @@ -0,0 +1,61 @@ +package net.corda.samples.snl.flows; + +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.serialization.CordaSerializable; + +@CordaSerializable +public class GameInfo { + private UniqueIdentifier linearId; + private String player1; + private String player2; + private String currentPlayer; + private int player1Pos; + private int player2Pos; + private String winner; + private int lastRoll; + + public GameInfo(UniqueIdentifier linearId, String player1, String player2, String currentPlayer, int player1Pos, + int player2Pos, String winner, int lastRoll) { + this.linearId = linearId; + this.player1 = player1; + this.player2 = player2; + this.currentPlayer = currentPlayer; + this.player1Pos = player1Pos; + this.player2Pos = player2Pos; + this.winner = winner; + this.lastRoll = lastRoll; + } + + public UniqueIdentifier getLinearId() { + return linearId; + } + + public String getPlayer1() { + return player1; + } + + public String getPlayer2() { + return player2; + } + + public String getCurrentPlayer() { + return currentPlayer; + } + + public int getPlayer1Pos() { + return player1Pos; + } + + public int getPlayer2Pos() { + return player2Pos; + } + + public String getWinner() { + return winner; + } + + public int getLastRoll() { + return lastRoll; + } +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/PlayerMoveFlow.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/PlayerMoveFlow.java new file mode 100644 index 00000000..4712a094 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/PlayerMoveFlow.java @@ -0,0 +1,210 @@ +package net.corda.samples.snl.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.contracts.states.AccountInfo; +import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import com.sun.istack.NotNull; +import net.corda.core.contracts.Command; +import net.corda.core.contracts.ReferencedStateAndRef; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.crypto.TransactionSignature; +import net.corda.core.flows.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.FilteredTransaction; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.snl.contracts.GameBoardContract; +import net.corda.samples.snl.states.BoardConfig; +import net.corda.samples.snl.states.GameBoard; +import net.corda.samples.snl.oracle.flows.OracleSignatureFlow; + +import java.security.SignatureException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import net.corda.core.identity.CordaX500Name; + +public class PlayerMoveFlow { + + private PlayerMoveFlow() {} + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + + private String player; + private String linearId; + private int diceRolled; + + public Initiator(String player, String linearId, int diceRolled) { + this.player = player; + this.linearId = linearId; + this.diceRolled = diceRolled; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); + + //Get current player account info + List> currentPlayerAccountInfo = accountService.accountInfo(player); + if(currentPlayerAccountInfo.size() ==0) + throw new FlowException("Player "+ player + " doesn't exist!"); + + AbstractParty currPlayer = subFlow(new RequestKeyForAccount(currentPlayerAccountInfo.get(0).getState().getData())); + + // Get game board + QueryCriteria.LinearStateQueryCriteria linearStateQueryCriteria = + new QueryCriteria.LinearStateQueryCriteria(null, + Collections.singletonList(UUID.fromString(linearId)), + null, Vault.StateStatus.UNCONSUMED, null); + List> gameBoardList = getServiceHub().getVaultService() + .queryBy(GameBoard.class, linearStateQueryCriteria).getStates(); + if(gameBoardList.size() ==0) + throw new FlowException("Game doesn't exist!"); + + GameBoard gameBoard = gameBoardList.get(0).getState().getData(); + + if(gameBoard.getWinner() !=null){ + throw new FlowException("This Game is Over"); + } + + // Check if the initiator is the current player + if(!gameBoard.getCurrentPlayer().equals(player)) + throw new FlowException("Please wait for your turn"); + + // Get the other player + AtomicReference otherPlayerAccountInfo = new AtomicReference<>(); + gameBoard.getParticipants().forEach(abstractParty -> { + AccountInfo thisAI = accountService.accountInfo(abstractParty.getOwningKey()).getState().getData(); + if(!thisAI.getName().equals(player)){ + otherPlayerAccountInfo.set(accountService.accountInfo(abstractParty.getOwningKey()).getState().getData()); + } + }); + AbstractParty otherPlayer = subFlow(new RequestKeyForAccount(otherPlayerAccountInfo.get())); + + List> boardConfigList = + getServiceHub().getVaultService().queryBy(BoardConfig.class).getStates(); + if(boardConfigList.size() == 0) + throw new FlowException("Board config missing"); + + BoardConfig boardConfig = boardConfigList.get(0).getState().getData(); + + // Calculate Player Position + int newPlayer1Pos = gameBoard.getPlayer1Pos(); + int newPlayer2Pos = gameBoard.getPlayer2Pos(); + String winner = null; + if(player.equals(accountService.accountInfo(gameBoard.getPlayer1().getOwningKey()).getState().getData().getName())){ + newPlayer1Pos = gameBoard.getPlayer1Pos() + diceRolled; + if(newPlayer1Pos > 100){ + throw new FlowException("You need to roll " + (100 - gameBoard.getPlayer1Pos()) + " to win."); + } + if(boardConfig.getLadderPositions().keySet().contains(newPlayer1Pos)) + newPlayer1Pos = boardConfig.getLadderPositions().get(newPlayer1Pos); + else if(boardConfig.getSnakePositions().keySet().contains(newPlayer1Pos)) + newPlayer1Pos = boardConfig.getSnakePositions().get(newPlayer1Pos); + + if(newPlayer1Pos == 100){ + winner = accountService.accountInfo(gameBoard.getPlayer1().getOwningKey()).getState().getData().getName(); + } + + }else{ + newPlayer2Pos = gameBoard.getPlayer2Pos() + diceRolled; + if(newPlayer2Pos > 100){ + throw new FlowException("You need to roll " + (100 - gameBoard.getPlayer2Pos()) + " to win."); + } + if(boardConfig.getLadderPositions().keySet().contains(newPlayer2Pos)) + newPlayer2Pos = boardConfig.getLadderPositions().get(newPlayer2Pos); + else if(boardConfig.getSnakePositions().keySet().contains(newPlayer2Pos)) + newPlayer2Pos = boardConfig.getSnakePositions().get(newPlayer2Pos); + + if(newPlayer2Pos == 100){ + winner = accountService.accountInfo(gameBoard.getPlayer2().getOwningKey()).getState().getData().getName(); + } + } + + GameBoard outputGameBoard = new GameBoard(gameBoard.getLinearId(), + gameBoard.getPlayer1(), gameBoard.getPlayer2(), otherPlayerAccountInfo.get().getName(), + newPlayer1Pos, newPlayer2Pos, winner, diceRolled); + + TransactionBuilder transactionBuilder = new TransactionBuilder(notary) + .addInputState(gameBoardList.get(0)) + .addOutputState(outputGameBoard) + .addCommand(new GameBoardContract.Commands.PlayMove(diceRolled), Arrays.asList(currPlayer.getOwningKey(), + otherPlayer.getOwningKey())) + .addReferenceState(new ReferencedStateAndRef<>(boardConfigList.get(0))); + + transactionBuilder.verify(getServiceHub()); + + SignedTransaction selfSignedTransaction = + getServiceHub().signInitialTransaction(transactionBuilder, currPlayer.getOwningKey()); + + Party oracle = getServiceHub().getNetworkMapCache() + .getNodeByLegalName(CordaX500Name.parse("O=Oracle,L=Mumbai,C=IN")).getLegalIdentities().get(0); + + FilteredTransaction ftx = selfSignedTransaction.buildFilteredTransaction(o -> { + return o instanceof Command && ((Command) o).getSigners().contains(oracle.getOwningKey()) + && ((Command) o).getValue() instanceof GameBoardContract.Commands.PlayMove; + }); + + TransactionSignature oracleSignature = subFlow(new OracleSignatureFlow(oracle, ftx)); + SignedTransaction selfAndOracleSignedTransaction = selfSignedTransaction.withAdditionalSignature(oracleSignature); + + + FlowSession otherPlayerSession = initiateFlow(otherPlayerAccountInfo.get().getHost()); + SignedTransaction signedTransaction = + subFlow(new CollectSignaturesFlow(selfAndOracleSignedTransaction, Collections.singletonList(otherPlayerSession), + Collections.singletonList(currPlayer.getOwningKey()))); + + if(!otherPlayerAccountInfo.get().getHost().equals(getOurIdentity())) { + signedTransaction = subFlow(new FinalityFlow(signedTransaction, Collections.singletonList(otherPlayerSession))); + try { + accountService.shareStateAndSyncAccounts(signedTransaction + .toLedgerTransaction(getServiceHub()).outRef(0) ,otherPlayerSession.getCounterparty()); + } catch (SignatureException e) { + e.printStackTrace(); + } + } + else{ + signedTransaction = subFlow(new FinalityFlow(signedTransaction, Collections.emptyList())); + } + return signedTransaction; + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + private FlowSession counterpartySession; + + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + subFlow(new SignTransactionFlow(counterpartySession) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // Custom Logic to validate transaction. + } + }); + if(!counterpartySession.getCounterparty().equals(getOurIdentity())) + return subFlow(new ReceiveFinalityFlow(counterpartySession)); + + return null; + } + } +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/QueryOracleForDiceRollFlow.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/QueryOracleForDiceRollFlow.java new file mode 100644 index 00000000..bdccd604 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/QueryOracleForDiceRollFlow.java @@ -0,0 +1,28 @@ +package net.corda.samples.snl.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.samples.snl.oracle.flows.DiceRollerFlow; + +@StartableByRPC +public class QueryOracleForDiceRollFlow extends FlowLogic { + + private String player; + + public QueryOracleForDiceRollFlow(String player) { + this.player = player; + } + + @Override + @Suspendable + public Integer call() throws FlowException { + Party oracle = getServiceHub().getNetworkMapCache() + .getNodeByLegalName(CordaX500Name.parse("O=Oracle,L=Mumbai,C=IN")).getLegalIdentities().get(0); + + return subFlow(new DiceRollerFlow(player, oracle)); + } +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/QueyGameInfo.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/QueyGameInfo.java new file mode 100644 index 00000000..92bbb575 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/QueyGameInfo.java @@ -0,0 +1,59 @@ +package net.corda.samples.snl.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.accounts.workflows.services.AccountService; +import com.r3.corda.lib.accounts.workflows.services.KeyManagementBackedAccountService; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.InitiatingFlow; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.samples.snl.states.GameBoard; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@InitiatingFlow +@StartableByRPC +public class QueyGameInfo extends FlowLogic { + + private String gameId; + + public QueyGameInfo(String gameId) { + this.gameId = gameId; + } + + @Override + @Suspendable + public GameInfo call() throws FlowException { + + QueryCriteria.LinearStateQueryCriteria linearStateQueryCriteria = + new QueryCriteria.LinearStateQueryCriteria(null, + Collections.singletonList(UUID.fromString(gameId)), + null, Vault.StateStatus.UNCONSUMED, null); + List> gameBoardList = getServiceHub().getVaultService() + .queryBy(GameBoard.class, linearStateQueryCriteria).getStates(); + if(gameBoardList.size() ==0) + throw new FlowException("Game doesn't exist!"); + + GameBoard gameBoard = gameBoardList.get(0).getState().getData(); + AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class); + String player1 = accountService.accountInfo(gameBoard.getPlayer1().getOwningKey()).getState().getData().getName(); + String player2 = accountService.accountInfo(gameBoard.getPlayer2().getOwningKey()).getState().getData().getName(); + + GameInfo gameInfo = new GameInfo( + gameBoard.getLinearId(), + player1, + player2, + gameBoard.getCurrentPlayer(), + gameBoard.getPlayer1Pos(), + gameBoard.getPlayer2Pos(), + gameBoard.getWinner(), + gameBoard.getLastRoll() + ); + return gameInfo; + } +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/StartGameFlow.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/StartGameFlow.java new file mode 100644 index 00000000..a8ec6c62 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/flows/StartGameFlow.java @@ -0,0 +1,26 @@ +package net.corda.samples.snl.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.transactions.SignedTransaction; + +@StartableByRPC +public class StartGameFlow extends FlowLogic { + + private String player1; + private String player2; + + public StartGameFlow(String player1, String player2) { + this.player1 = player1; + this.player2 = player2; + } + + @Override + @Suspendable + public String call() throws FlowException { + subFlow(new CreateBoardConfig.Initiator(player1, player2)); + return subFlow(new CreateGameFlow.Initiator(player1, player2)); + } +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/service/GameSyncService.java b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/service/GameSyncService.java new file mode 100644 index 00000000..b87a8c13 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/main/java/net/corda/samples/snl/service/GameSyncService.java @@ -0,0 +1,17 @@ +package net.corda.samples.snl.service; + +import net.corda.core.node.AppServiceHub; +import net.corda.core.node.services.CordaService; +import net.corda.core.serialization.SingletonSerializeAsToken; + +@CordaService +public class GameSyncService extends SingletonSerializeAsToken { + + private AppServiceHub serviceHub; + + public GameSyncService(AppServiceHub serviceHub) { + this.serviceHub = serviceHub; + } + + +} diff --git a/Advanced/snakesandladders-cordapp/workflows/src/test/java/net/corda/samples/snl/flows/FlowTests.java b/Advanced/snakesandladders-cordapp/workflows/src/test/java/net/corda/samples/snl/flows/FlowTests.java new file mode 100644 index 00000000..7f141371 --- /dev/null +++ b/Advanced/snakesandladders-cordapp/workflows/src/test/java/net/corda/samples/snl/flows/FlowTests.java @@ -0,0 +1,92 @@ +package net.corda.samples.snl.flows; + +import com.google.common.collect.ImmutableList; +import com.r3.corda.lib.accounts.workflows.flows.CreateAccount; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.NetworkParameters; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.snl.states.BoardConfig; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +import java.time.Instant; +import java.util.Collections; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork( + new MockNetworkParameters( + ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.snl.flows"), + TestCordapp.findCordapp("net.corda.samples.snl.contracts"), + TestCordapp.findCordapp("com.r3.corda.lib.accounts.contracts"), + TestCordapp.findCordapp("com.r3.corda.lib.accounts.workflows"), + TestCordapp.findCordapp("com.r3.corda.lib.ci")) + ).withNetworkParameters(new NetworkParameters(4, Collections.emptyList(), + 10485760, 10485760 * 50, Instant.now(), 1, + Collections.emptyMap()) + ) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + ); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void testCreateBoardConfigFlow() throws Exception { + CreateAccount createAccount1 = new CreateAccount("Ashutosh"); + a.startFlow(createAccount1); + network.runNetwork(); + + CreateAccount createAccount2 = new CreateAccount("Peter"); + a.startFlow(createAccount2); + network.runNetwork(); + + CreateBoardConfig.Initiator createBoardConfig = new CreateBoardConfig.Initiator("Ashutosh", "Peter"); + CordaFuture signedTransactionCordaFuture = a.startFlow(createBoardConfig); + network.runNetwork(); + + SignedTransaction signedTransaction = signedTransactionCordaFuture.get(); + BoardConfig boardConfig = (BoardConfig) signedTransaction.getTx().getOutput(0); + assertNotNull(boardConfig); + } + + + @Test + public void testCreateGameFlow() throws Exception{ + CreateAccount createAccount1 = new CreateAccount("Ashutosh"); + a.startFlow(createAccount1); + network.runNetwork(); + + CreateAccount createAccount2 = new CreateAccount("Peter"); + a.startFlow(createAccount2); + network.runNetwork(); + + CreateBoardConfig.Initiator createBoardConfig = new CreateBoardConfig.Initiator("Ashutosh", "Peter"); + CordaFuture signedTransactionCordaFuture = a.startFlow(createBoardConfig); + network.runNetwork(); + + CreateGameFlow.Initiator createGameFlow = new CreateGameFlow.Initiator("Ashutosh", "Peter"); + CordaFuture signedTransactionCordaFuture1 = a.startFlow(createGameFlow); + network.runNetwork(); + + String gameId = signedTransactionCordaFuture1.get(); + assertNotNull(gameId); + } +} diff --git a/Advanced/syndicated-lending/.ci/Jenkinsfile b/Advanced/syndicated-lending/.ci/Jenkinsfile new file mode 100644 index 00000000..7421cafe --- /dev/null +++ b/Advanced/syndicated-lending/.ci/Jenkinsfile @@ -0,0 +1,37 @@ +#!groovy +/** + * Jenkins pipeline to build the java CorDapp template + */ + +/** + * Kill already started job. + * Assume new commit takes precedence and results from previousunfinished builds are not required. + * This feature doesn't play well with disableConcurrentBuilds() option + */ +@Library('corda-shared-build-pipeline-steps') +import static com.r3.build.BuildControl.killAllExistingBuildsForJob +killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) + +pipeline { + agent { + label 'eight-cores' + } + options { + ansiColor('xterm') + timestamps() + timeout(3*60) // 3 hours + buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) + } + stages { + stage('Build') { + steps { + sh './gradlew --no-daemon -s clean build test deployNodes' + } + } + } + post { + cleanup { + deleteDir() + } + } +} diff --git a/Advanced/syndicated-lending/.gitignore b/Advanced/syndicated-lending/.gitignore new file mode 100644 index 00000000..0d75547d --- /dev/null +++ b/Advanced/syndicated-lending/.gitignore @@ -0,0 +1,78 @@ +# Eclipse, ctags, Mac metadata, log files +.classpath +.project +tags +.DS_Store +*.log +*.log.gz +*.orig + +.gradle + +# General build files +**/build/* +!docs/build/* + +lib/dokka.jar + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +#.idea + +# if you remove the above rule, at least ignore the following: + +# Specific files to avoid churn +.idea/*.xml +.idea/copyright +.idea/jsLibraryMappings.xml + +# User-specific stuff: +.idea/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ +/workflows/out/ +/contracts/out/ +clients/out/ +*/bin/* + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + +# docs related +docs/virtualenv/ + +# if you use the installQuasar task +lib diff --git a/Basic/rpc-nodeinfo/.settings/org.eclipse.jdt.core.prefs b/Advanced/syndicated-lending/.settings/org.eclipse.jdt.core.prefs similarity index 100% rename from Basic/rpc-nodeinfo/.settings/org.eclipse.jdt.core.prefs rename to Advanced/syndicated-lending/.settings/org.eclipse.jdt.core.prefs diff --git a/Advanced/syndicated-lending/FlowDiagram.pptx b/Advanced/syndicated-lending/FlowDiagram.pptx new file mode 100644 index 00000000..c011cc9e Binary files /dev/null and b/Advanced/syndicated-lending/FlowDiagram.pptx differ diff --git a/Advanced/syndicated-lending/LICENCE b/Advanced/syndicated-lending/LICENCE new file mode 100644 index 00000000..3ff572d1 --- /dev/null +++ b/Advanced/syndicated-lending/LICENCE @@ -0,0 +1,13 @@ + Copyright 2016, R3 Limited. + + 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. \ No newline at end of file diff --git a/Advanced/syndicated-lending/README.md b/Advanced/syndicated-lending/README.md new file mode 100644 index 00000000..77b8b6c9 --- /dev/null +++ b/Advanced/syndicated-lending/README.md @@ -0,0 +1,102 @@ +

+ Corda +

+ +# Syndicated Lending Sample Cordapp + +This is a sample Cordapp which demonstrate a high level Syndicated Lending scenario on a Corda network. + +Syndicated lending comprises of multiple banks coming together to service the loan requirement of a borrower. +Generally a lead bank is appointed who coordinated the process to forming a syndicate with other participating banks +and agreeing on loan terms. + +# Pre-Requisites + +See https://docs.corda.net/getting-set-up.html. + +# Usage + +## Running the CorDapp + +Open a terminal and go to the project root directory and type: (to deploy the nodes using bootstrapper) +``` +./gradlew clean deployNodes +``` +Then type: (to run the nodes) +``` +./build/nodes/runnodes +``` + +## Interacting with the CorDapp + +PeterCo (as a borrower) starts the `SubmitProjectProposalFlow` in order to submit the project details to a group of +lenders and request for funds. + +Go to PeterCo's terminal and run the below command + +``` +start SubmitProjectProposalFlow lenders: [BankOfAshu, BankOfSneha], projectDescription: "Overseas Expansion", projectCost: 10000000, loanAmount: 8000000 +``` + +Validate the project details are created and shared with the lenders successfully by running the vaultQuery command in each +lender's terminal and borrower's terminal. + +``` +run vaultQuery contractStateType: net.corda.samples.lending.states.ProjectState +``` + +Once the lenders have verified the project details and done their due deligence, they could submit bids for loan. + +Goto BankOfAshu's terminal and run the below command. The project-id can be found using the vaultQuery command shown earlier. + +``` +start SubmitLoanBidFlow borrower: PeterCo, loanAmount: 8000000, tenure: 5, rateofInterest: 4.0, transactionFees: 20000, projectIdentifier: +``` + +Validate the loanBid is submitted successfully by running the vaultQuery command below: + +``` +run vaultQuery contractStateType: net.corda.samples.lending.states.LoanBidState +``` + +Now the borrower can inspect the loan terms and approve the loan bid, to start the syndication process. + +Go to PeterCo's terminal and run the below command. The loanbid-identifier can be found using the vaultQuery command used earlier. + +``` +start ApproveLoanBidFlow bidIdentifier: +``` + +One the loan bid has been approved by the borrower, the lender can start the process of creating the syndicate by +acting as the lead bank and approach participating bank for funds. + +Goto BankOfAshu's terminal and run the below command. + +``` +start CreateSyndicateFlow participantBanks: [BankOfSneha, BankOfTom], projectIdentifier: , loanDetailIdentifier: +``` + +Verify the syndicate is created using the below command: + +``` +run vaultQuery contractStateType: net.corda.samples.lending.states.SyndicateState +``` + +On receiving the syndicate creation request, participating banks could verify the project and loan terms and submit +bids for the amount of fund they wish to lend by using the below flow in BankOfSneha or BankOfTom node. + +``` +start SyndicateBidFlow$Initiator syndicateIdentifier: , bidAmount: +``` + +Verify the syndicate bid is successfully created using the below command: + +``` +run vaultQuery contractStateType: net.corda.samples.lending.states.SyndicateBidState +``` + +The lead bank on receiving bids from participating banks could approve the bid using the below flow command. + +``` +start ApproveSyndicateBidFlow bidIdentifier: +``` \ No newline at end of file diff --git a/Advanced/syndicated-lending/TRADEMARK b/Advanced/syndicated-lending/TRADEMARK new file mode 100644 index 00000000..d2e056b5 --- /dev/null +++ b/Advanced/syndicated-lending/TRADEMARK @@ -0,0 +1,4 @@ +Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates. All rights reserved. + +For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy at +https://www.r3.com/trademark-policy/. \ No newline at end of file diff --git a/Advanced/syndicated-lending/build.gradle b/Advanced/syndicated-lending/build.gradle new file mode 100644 index 00000000..68ca4762 --- /dev/null +++ b/Advanced/syndicated-lending/build.gradle @@ -0,0 +1,156 @@ +buildscript {//properties that you need to build the project + Properties constants = new Properties() + file("$projectDir/./constants.properties").withInputStream { constants.load(it) } + + ext { + corda_release_group = constants.getProperty("cordaReleaseGroup") + corda_core_release_group = constants.getProperty("cordaCoreReleaseGroup") + corda_release_version = constants.getProperty("cordaVersion") + corda_core_release_version = constants.getProperty("cordaCoreVersion") + corda_gradle_plugins_version = constants.getProperty("gradlePluginsVersion") + kotlin_version = constants.getProperty("kotlinVersion") + junit_version = constants.getProperty("junitVersion") + quasar_version = constants.getProperty("quasarVersion") + log4j_version = constants.getProperty("log4jVersion") + slf4j_version = constants.getProperty("slf4jVersion") + corda_platform_version = constants.getProperty("platformVersion").toInteger() + //springboot + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + } + + repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } + } + + dependencies { + classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" + classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" + classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" + } +} + +allprojects {//Properties that you need to compile your project (The application) + apply from: "${rootProject.projectDir}/repositories.gradle" + apply plugin: 'java' + + repositories { + mavenLocal() + + mavenCentral() + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://download.corda.net/maven/corda-releases' } + maven { url 'https://jitpack.io' } + } + + tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" // Required by Corda's serialisation framework. + } + + jar { + // This makes the JAR's SHA-256 hash repeatable. + preserveFileTimestamps = false + reproducibleFileOrder = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} + +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} +//Module dependencis +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":workflows") + cordapp project(":contracts") + + cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}" + cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + cordaDriver "net.corda:corda-shell:4.10" + +} + + +//Task to deploy the nodes in order to bootstrap a network +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + /* This property will load the CorDapps to each of the node by default, including the Notary. You can find them + * in the cordapps folder of the node at build/nodes/Notary/cordapps. However, the notary doesn't really understand + * the notion of cordapps. In production, Notary does not need cordapps as well. This is just a short cut to load + * the Corda network bootstrapper. + */ + nodeDefaults { + projectCordapp { + deploy = false + } + cordapp project(':contracts') + cordapp project(':workflows') + runSchemaMigration = true //This configuration is for any CorDapps with custom schema, We will leave this as true to avoid + //problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change + //it to false for quicker project compiling time. + } + node { + name "O=Notary,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=PeterCo,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=BankOfAshu,L=New York,C=US" + p2pPort 10008 + rpcSettings { + address("localhost:10009") + adminAddress("localhost:10049") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=BankOfSneha,L=New York,C=US" + p2pPort 10011 + rpcSettings { + address("localhost:10012") + adminAddress("localhost:10052") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } + node { + name "O=BankOfTom,L=New York,C=US" + p2pPort 10014 + rpcSettings { + address("localhost:10016") + adminAddress("localhost:10056") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} diff --git a/Features/observablestates-tradereporting/workflows/src/main/resources/log4j2.xml b/Advanced/syndicated-lending/config/dev/log4j2.xml similarity index 100% rename from Features/observablestates-tradereporting/workflows/src/main/resources/log4j2.xml rename to Advanced/syndicated-lending/config/dev/log4j2.xml diff --git a/Advanced/syndicated-lending/config/test/log4j2.xml b/Advanced/syndicated-lending/config/test/log4j2.xml new file mode 100644 index 00000000..cd9926ca --- /dev/null +++ b/Advanced/syndicated-lending/config/test/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n + > + + + + + + + + + + + + diff --git a/Advanced/syndicated-lending/constants.properties b/Advanced/syndicated-lending/constants.properties new file mode 100644 index 00000000..adedb0f3 --- /dev/null +++ b/Advanced/syndicated-lending/constants.properties @@ -0,0 +1,12 @@ +cordaReleaseGroup=net.corda +cordaCoreReleaseGroup=net.corda +cordaVersion=4.8.5 +cordaCoreVersion=4.8.5 +gradlePluginsVersion=5.0.12 +kotlinVersion=1.2.71 +junitVersion=4.12 +quasarVersion=0.7.10 +log4jVersion =2.16.0 +platformVersion=10 +slf4jVersion=1.7.25 +nettyVersion=4.1.22.Final diff --git a/Advanced/syndicated-lending/contracts/build.gradle b/Advanced/syndicated-lending/contracts/build.gradle new file mode 100644 index 00000000..9f2664fb --- /dev/null +++ b/Advanced/syndicated-lending/contracts/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + contract { + name "Lending Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main{ + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + } + test{ + java{ + srcDir 'src/test/java' + java.outputDir = file('bin/test') + } + } +} + +dependencies { + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/LoanBidContract.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/LoanBidContract.java new file mode 100644 index 00000000..047caa6c --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/LoanBidContract.java @@ -0,0 +1,42 @@ +package net.corda.samples.lending.contracts; + + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class LoanBidContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.lending.contracts.LoanBidContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Validation Logic Goes Here + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.Submit) { + requireThat(require -> { + /* At here, the loan bid proposal from the competing banks is verified. + These contract rules make sure that each loan bid for project is valid. */ + return null; + }); + }else if (commandData instanceof Commands.Approve) { + requireThat(require -> { + /* At here, the loan bid is verified for approval process. These contract rules make + sure that all the conditions are met for the borrower to approve the sole loan bid for its + project. */ + return null; + }); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class Submit implements ProjectContract.Commands {} + class Approve implements ProjectContract.Commands {} + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/ProjectContract.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/ProjectContract.java new file mode 100644 index 00000000..3189f63e --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/ProjectContract.java @@ -0,0 +1,40 @@ +package net.corda.samples.lending.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +// ************ +// * Contract * +// ************ +public class ProjectContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.lending.contracts.ProjectContract"; + + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Validation Logic Goes Here + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.ProposeProject) { + requireThat(require -> { + /*At here, you can structure the rules for creating a project proposal + * this verify method makes sure that all proposed projects from the borrower company + * are sound, so that banks are not going to waste any time on unqualified project proposals*/ + return null; + }); + } + } + + // Used to indicate the transaction's intent. + public interface Commands extends CommandData { + class ProposeProject implements Commands {} + } +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateBidContract.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateBidContract.java new file mode 100644 index 00000000..bd3738c2 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateBidContract.java @@ -0,0 +1,39 @@ +package net.corda.samples.lending.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.transactions.LedgerTransaction; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class SyndicateBidContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.lending.contracts.SyndicateBidContract"; + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Validation Logic Goes Here + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.Submit) { + requireThat(require -> { + /* At here, the syndication bid proposal from the syndication participating banks is verified. + These contract rules make sure that each bid for syndicated loan is valid. */ + return null; + }); + }else if (commandData instanceof Commands.Approve) { + requireThat(require -> { + /* At here, the syndicated bid is verified for approval process. These contract rules make + sure that all the conditions are met for the lead bank to approve the each syndicated bid. */ + return null; + }); + } + } + + public interface Commands extends CommandData { + class Submit implements Commands {} + class Approve implements Commands {} + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateContract.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateContract.java new file mode 100644 index 00000000..d2c15f9c --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/contracts/SyndicateContract.java @@ -0,0 +1,38 @@ +package net.corda.samples.lending.contracts; + +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.SyndicateState; +import org.jetbrains.annotations.NotNull; + +import static net.corda.core.contracts.ContractsDSL.requireThat; + +public class SyndicateContract implements Contract { + + // This is used to identify our contract when building a transaction. + public static final String ID = "net.corda.samples.lending.contracts.SyndicateContract"; + + + // A transaction is valid if the verify() function of the contract of all the transaction's input and output states + // does not throw an exception. + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + // Contract Validation Logic Goes Here + final CommandData commandData = tx.getCommands().get(0).getValue(); + + if (commandData instanceof Commands.Create) { + requireThat(require -> { + /*Here writes the rules for the lead bank's creating the syndication.*/ + + return null; + }); + } + } + + public interface Commands extends CommandData { + class Create implements Commands {} + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/LoanBidState.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/LoanBidState.java new file mode 100644 index 00000000..7be802bc --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/LoanBidState.java @@ -0,0 +1,82 @@ +package net.corda.samples.lending.states; + +import net.corda.core.contracts.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.lending.contracts.LoanBidContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +@BelongsToContract(LoanBidContract.class) +public class LoanBidState implements LinearState { + + private StaticPointer projectDetails; + private UniqueIdentifier uniqueIdentifier; + private Party lender; + private Party borrower; + private int loanAmount; + private int tenure; + private double rateofInterest; + private int transactionFees; + private String status; + + public LoanBidState(StaticPointer projectDetails, UniqueIdentifier uniqueIdentifier, + Party lender, Party borrower, int loanAmount, int tenure, + double rateofInterest, int transactionFees, String status) { + this.projectDetails = projectDetails; + this.uniqueIdentifier = uniqueIdentifier; + this.lender = lender; + this.borrower = borrower; + this.loanAmount = loanAmount; + this.tenure = tenure; + this.rateofInterest = rateofInterest; + this.transactionFees = transactionFees; + this.status = status; + } + + public StaticPointer getProjectDetails() { + return projectDetails; + } + + public Party getLender() { + return lender; + } + + public Party getBorrower() { + return borrower; + } + + public int getLoanAmount() { + return loanAmount; + } + + public int getTenure() { + return tenure; + } + + public double getRateofInterest() { + return rateofInterest; + } + + public int getTransactionFees() { + return transactionFees; + } + + public String getStatus() { + return status; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(lender, borrower); + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return uniqueIdentifier; + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/ProjectState.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/ProjectState.java new file mode 100644 index 00000000..a4240e05 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/ProjectState.java @@ -0,0 +1,77 @@ +package net.corda.samples.lending.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.lending.contracts.ProjectContract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +// ********* +// * ProposalState * +// ********* +@BelongsToContract(ProjectContract.class) +public class ProjectState implements LinearState { + + //private variables + private UniqueIdentifier uniqueIdentifier; + private String projectDescription; + private Party borrower; + private int projectCost; + private int loanAmount; + private List lenders; + + /* Constructor of your Corda state */ + + public ProjectState(UniqueIdentifier uniqueIdentifier, String projectDescription, Party borrower, + int projectCost, int loanAmount, List lenders) { + this.uniqueIdentifier = uniqueIdentifier; + this.projectDescription = projectDescription; + this.borrower = borrower; + this.projectCost = projectCost; + this.loanAmount = loanAmount; + this.lenders = lenders; + } + + // getters + + public String getProjectDescription() { + return projectDescription; + } + + public Party getBorrower() { + return borrower; + } + + public int getProjectCost() { + return projectCost; + } + + public int getLoanAmount() { + return loanAmount; + } + + public List getBidders() { + return lenders; + } + + /* This method will indicate who are the participants when + * this state is used in a transaction. */ + @Override + public List getParticipants() { + List particiants = new ArrayList<>(); + particiants.add(borrower); + particiants.addAll(lenders); + return particiants; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return uniqueIdentifier; + } +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateBidState.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateBidState.java new file mode 100644 index 00000000..204289ba --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateBidState.java @@ -0,0 +1,66 @@ +package net.corda.samples.lending.states; + +import net.corda.core.contracts.BelongsToContract; +import net.corda.core.contracts.LinearPointer; +import net.corda.core.contracts.LinearState; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.lending.contracts.SyndicateBidContract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +@BelongsToContract(SyndicateBidContract.class) +public class SyndicateBidState implements LinearState { + + private UniqueIdentifier uniqueIdentifier; + private LinearPointer syndicateState; + private int bidAmount; + private Party leadBank; + private Party participatBank; + private String status; + + public SyndicateBidState(UniqueIdentifier uniqueIdentifier, LinearPointer syndicateState, int bidAmount, + Party leadBank, Party participatBank, String status) { + this.uniqueIdentifier = uniqueIdentifier; + this.syndicateState = syndicateState; + this.bidAmount = bidAmount; + this.leadBank = leadBank; + this.participatBank = participatBank; + this.status = status; + } + + public LinearPointer getSyndicateState() { + return syndicateState; + } + + public int getBidAmount() { + return bidAmount; + } + + public Party getLeadBank() { + return leadBank; + } + + public Party getParticipatBank() { + return participatBank; + } + + public String getStatus() { + return status; + } + + @NotNull + @Override + public List getParticipants() { + return Arrays.asList(participatBank, leadBank); + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return uniqueIdentifier; + } +} diff --git a/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateState.java b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateState.java new file mode 100644 index 00000000..0787b300 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/main/java/net/corda/samples/lending/states/SyndicateState.java @@ -0,0 +1,62 @@ +package net.corda.samples.lending.states; + +import net.corda.core.contracts.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.samples.lending.contracts.SyndicateContract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@BelongsToContract(SyndicateContract.class) +public class SyndicateState implements LinearState { + + private UniqueIdentifier uniqueIdentifier; + private Party leadBank; + private List participantBanks; + private LinearPointer projectDetails; + private LinearPointer loanDetails; + + public SyndicateState(UniqueIdentifier uniqueIdentifier, Party leadBank, List participantBanks, + LinearPointer projectDetails, + LinearPointer loanDetails) { + this.uniqueIdentifier = uniqueIdentifier; + this.leadBank = leadBank; + this.participantBanks = participantBanks; + this.projectDetails = projectDetails; + this.loanDetails = loanDetails; + } + + public Party getLeadBank() { + return leadBank; + } + + public List getParticipantBanks() { + return participantBanks; + } + + public LinearPointer getProjectDetails() { + return projectDetails; + } + + public LinearPointer getLoanDetails() { + return loanDetails; + } + + + @NotNull + @Override + public List getParticipants() { + List particiants = new ArrayList<>(); + particiants.add(leadBank); + particiants.addAll(participantBanks); + return particiants; + } + + @NotNull + @Override + public UniqueIdentifier getLinearId() { + return uniqueIdentifier; + } +} diff --git a/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/ContractTests.java b/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/ContractTests.java new file mode 100644 index 00000000..04ad1485 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/ContractTests.java @@ -0,0 +1,18 @@ +package net.corda.samples.lending.contracts; + +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; + +import java.util.Arrays; + +import static net.corda.testing.node.NodeTestUtils.ledger; + + +public class ContractTests { + private final MockServices ledgerServices = new MockServices(Arrays.asList("net.corda.samples.lending")); + TestIdentity alice = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + TestIdentity bob = new TestIdentity(new CordaX500Name("Alice", "TestLand", "US")); + + +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/StateTests.java b/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/StateTests.java new file mode 100644 index 00000000..e886d011 --- /dev/null +++ b/Advanced/syndicated-lending/contracts/src/test/java/net/corda/samples/lending/contracts/StateTests.java @@ -0,0 +1,8 @@ +package net.corda.samples.lending.contracts; + +import net.corda.samples.lending.states.ProjectState; +import org.junit.Test; + +public class StateTests { + +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/gradle.properties b/Advanced/syndicated-lending/gradle.properties new file mode 100644 index 00000000..60bb519a --- /dev/null +++ b/Advanced/syndicated-lending/gradle.properties @@ -0,0 +1,3 @@ +name=Test +group=net.corda.samples.lending +version=0.1 \ No newline at end of file diff --git a/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.jar b/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..99340b4a Binary files /dev/null and b/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.properties b/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0188225a --- /dev/null +++ b/Advanced/syndicated-lending/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#Fri Aug 25 12:50:39 BST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip + diff --git a/Advanced/syndicated-lending/gradlew b/Advanced/syndicated-lending/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/Advanced/syndicated-lending/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Advanced/syndicated-lending/gradlew.bat b/Advanced/syndicated-lending/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Advanced/syndicated-lending/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Advanced/syndicated-lending/repositories.gradle b/Advanced/syndicated-lending/repositories.gradle new file mode 100644 index 00000000..8be7b630 --- /dev/null +++ b/Advanced/syndicated-lending/repositories.gradle @@ -0,0 +1,8 @@ +repositories { + mavenLocal() + mavenCentral() + + maven { url 'https://jitpack.io' } + maven { url 'https://download.corda.net/maven/corda-dependencies' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } +} diff --git a/Advanced/syndicated-lending/settings.gradle b/Advanced/syndicated-lending/settings.gradle new file mode 100644 index 00000000..b4446eaf --- /dev/null +++ b/Advanced/syndicated-lending/settings.gradle @@ -0,0 +1,2 @@ +include 'workflows' +include 'contracts' \ No newline at end of file diff --git a/Advanced/syndicated-lending/workflows/build.gradle b/Advanced/syndicated-lending/workflows/build.gradle new file mode 100644 index 00000000..8a229424 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version + minimumPlatformVersion corda_platform_version + workflow { + name "Lending Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/dev") + } + } + test { + java { + srcDir 'src/test/java' + java.outputDir = file('bin/main') + } + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/java') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +dependencies { + testCompile "junit:junit:$junit_version" + + // Corda dependencies. + cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaRuntime "$corda_release_group:corda:$corda_release_version" + testCompile "$corda_release_group:corda-node-driver:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":contracts") +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/workflows/src/integrationTest/java/net/corda/samples/lending/DriverBasedTest.java b/Advanced/syndicated-lending/workflows/src/integrationTest/java/net/corda/samples/lending/DriverBasedTest.java new file mode 100644 index 00000000..85ec790f --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/integrationTest/java/net/corda/samples/lending/DriverBasedTest.java @@ -0,0 +1,48 @@ +package net.corda.samples.lending; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.driver.DriverParameters; +import net.corda.testing.driver.NodeHandle; +import net.corda.testing.driver.NodeParameters; +import org.junit.Test; + +import java.util.List; + +import static net.corda.testing.driver.Driver.driver; +import static org.junit.Assert.assertEquals; + +public class DriverBasedTest { + private final TestIdentity bankA = new TestIdentity(new CordaX500Name("BankA", "", "GB")); + private final TestIdentity bankB = new TestIdentity(new CordaX500Name("BankB", "", "US")); + + @Test + public void nodeTest() { + driver(new DriverParameters().withIsDebug(true).withStartNodesInProcess(true), dsl -> { + // Start a pair of nodes and wait for them both to be ready. + List> handleFutures = ImmutableList.of( + dsl.startNode(new NodeParameters().withProvidedName(bankA.getName())), + dsl.startNode(new NodeParameters().withProvidedName(bankB.getName())) + ); + + try { + NodeHandle partyAHandle = handleFutures.get(0).get(); + NodeHandle partyBHandle = handleFutures.get(1).get(); + + // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the + // nodes have started and can communicate. + + // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault + // and other important metrics to ensure that your CorDapp is working as intended. + assertEquals(partyAHandle.getRpc().wellKnownPartyFromX500Name(bankB.getName()).getName(), bankB.getName()); + assertEquals(partyBHandle.getRpc().wellKnownPartyFromX500Name(bankA.getName()).getName(), bankA.getName()); + } catch (Exception e) { + throw new RuntimeException("Caught exception during test: ", e); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveLoanBidFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveLoanBidFlow.java new file mode 100644 index 00000000..776a85c6 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveLoanBidFlow.java @@ -0,0 +1,112 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StaticPointer; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.LoanBidContract; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; + +import java.util.Arrays; +import java.util.List; + +public class ApproveLoanBidFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + + private UniqueIdentifier bidIdentifier; + + //public constructor + public Initiator(UniqueIdentifier bidIdentifier) { + this.bidIdentifier = bidIdentifier; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + List> loanBidStateAndRefs = getServiceHub().getVaultService() + .queryBy(LoanBidState.class).getStates(); + + StateAndRef inputStateAndRef = loanBidStateAndRefs.stream().filter(loanBidStateAndRef -> { + LoanBidState loanBidState = loanBidStateAndRef.getState().getData(); + return loanBidState.getLinearId().equals(bidIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Loan Bid Not Found")); + + LoanBidState inputState = inputStateAndRef.getState().getData(); + + final LoanBidState output = new LoanBidState( + new StaticPointer<>(inputStateAndRef.getRef(), ProjectState.class), + inputState.getLinearId(), inputState.getLender(), inputState.getBorrower(), + inputState.getLoanAmount(), inputState.getTenure(), inputState.getRateofInterest(), + inputState.getTransactionFees(), "APPROVED" + ); + + Party notary = inputStateAndRef.getState().getNotary(); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the inputs and outputs, as well as a command to the transaction builder. + builder.addInputState(inputStateAndRef); + builder.addOutputState(output); + builder.addCommand(new LoanBidContract.Commands.Approve(), Arrays.asList(inputState.getBorrower().getOwningKey(),inputState.getLender().getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession cpSession = initiateFlow(inputState.getLender()); + + //step 6: collect signatures + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, Arrays.asList(cpSession))); + + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, Arrays.asList(cpSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveSyndicateBidFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveSyndicateBidFlow.java new file mode 100644 index 00000000..b1702d49 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/ApproveSyndicateBidFlow.java @@ -0,0 +1,113 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.SyndicateBidContract; +import net.corda.samples.lending.states.SyndicateBidState; + +import java.util.Arrays; +import java.util.List; + +public class ApproveSyndicateBidFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + private UniqueIdentifier bidIdentifier; + + //public constructor + public Initiator(UniqueIdentifier bidIdentifier) { + this.bidIdentifier = bidIdentifier; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Fetch Project + List> syndicateBidStateAndRefs = getServiceHub().getVaultService() + .queryBy(SyndicateBidState.class).getStates(); + + StateAndRef syndicateBidStateAndRef = syndicateBidStateAndRefs.stream().filter(synStateAndRef -> { + SyndicateBidState syndicateBidState = synStateAndRef.getState().getData(); + return syndicateBidState.getLinearId().equals(bidIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Syndicate Bid Not Found")); + + SyndicateBidState inputState = syndicateBidStateAndRef.getState().getData(); + + SyndicateBidState outputState = new SyndicateBidState( + inputState.getLinearId(), + inputState.getSyndicateState(), + inputState.getBidAmount(), + inputState.getLeadBank(), + inputState.getParticipatBank(), + "APPROVED" + ); + + Party notary = syndicateBidStateAndRef.getState().getNotary(); + + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the input and output state, as well as a command to the transaction builder. + builder.addInputState(syndicateBidStateAndRef); + builder.addOutputState(outputState); + builder.addCommand(new SyndicateBidContract.Commands.Submit(), + Arrays.asList(getOurIdentity().getOwningKey(),inputState.getParticipatBank().getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession cpSession = initiateFlow(inputState.getParticipatBank()); + + //step 6: collect signatures + SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, Arrays.asList(cpSession))); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(stx, Arrays.asList(cpSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) { + @Suspendable + @Override + protected void checkTransaction(SignedTransaction stx) throws FlowException { + /* + * SignTransactionFlow will automatically verify the transaction and its signatures before signing it. + * However, just because a transaction is contractually valid doesn’t mean we necessarily want to sign. + * What if we don’t want to deal with the counterparty in question, or the value is too high, + * or we’re not happy with the transaction’s structure? checkTransaction + * allows us to define these additional checks. If any of these conditions are not met, + * we will not sign the transaction - even if the transaction and its signatures are contractually valid. + * ---------- + * For this hello-world cordapp, we will not implement any aditional checks. + * */ + } + }); + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/CreateSyndicateFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/CreateSyndicateFlow.java new file mode 100644 index 00000000..bdf1c264 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/CreateSyndicateFlow.java @@ -0,0 +1,116 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.LinearPointer; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StaticPointer; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.StatesToRecord; +import net.corda.core.node.services.Vault; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.SyndicateContract; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; +import net.corda.samples.lending.states.SyndicateState; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class CreateSyndicateFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + + private Party leadBank; + private List participantBanks; + private UniqueIdentifier projectIdentifier; + private UniqueIdentifier loanDetailIdentifier; + + //public constructor + public Initiator(List participantBanks, UniqueIdentifier projectIdentifier, UniqueIdentifier loanDetailIdentifier) { + this.participantBanks = participantBanks; + this.projectIdentifier = projectIdentifier; + this.loanDetailIdentifier = loanDetailIdentifier; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + + this.leadBank = getOurIdentity(); + + // Fetch Project + List> projectStateAndRefs = getServiceHub().getVaultService() + .queryBy(ProjectState.class).getStates(); + + StateAndRef projectDetailStateAndRef = projectStateAndRefs.stream().filter(projectStateAndRef -> { + ProjectState projectState = projectStateAndRef.getState().getData(); + return projectState.getLinearId().equals(projectIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Project Not Found")); + + // Fetch Loan + List> loanBidStateAndRefs = getServiceHub().getVaultService() + .queryBy(LoanBidState.class).getStates(); + + StateAndRef loanStateAndRef = loanBidStateAndRefs.stream().filter(loanBidStateAndRef -> { + LoanBidState loanBidState = loanBidStateAndRef.getState().getData(); + return loanBidState.getLinearId().equals(loanDetailIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Loan Details Not Found")); + + SyndicateState syndicateState = new SyndicateState( + new UniqueIdentifier(), leadBank, participantBanks, + new LinearPointer<>(projectIdentifier, ProjectState.class), + new LinearPointer<>(loanDetailIdentifier, LoanBidState.class) + //Collections.emptyList() + ); + + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the project as an output state, as well as a command to the transaction builder. + builder.addOutputState(syndicateState); + builder.addCommand(new SyndicateContract.Commands.Create(), Arrays.asList(syndicateState.getLeadBank().getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + List cpSesions = participantBanks.stream().map(this::initiateFlow).collect(Collectors.toList()); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, cpSesions)); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession, null, StatesToRecord.ALL_VISIBLE)); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitLoanBidFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitLoanBidFlow.java new file mode 100644 index 00000000..fe2f9980 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitLoanBidFlow.java @@ -0,0 +1,108 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.StatePointer; +import net.corda.core.contracts.StaticPointer; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.LoanBidContract; +import net.corda.samples.lending.contracts.ProjectContract; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SubmitLoanBidFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic{ + + private Party borrower; + private Party lender; + private int loanAmount; + private int tenure; + private double rateofInterest; + private int transactionFees; + private UniqueIdentifier projectIdentifier; + + //public constructor + public Initiator(Party borrower, int loanAmount, int tenure, double rateofInterest, int transactionFees, + UniqueIdentifier projectIdentifier) { + this.borrower = borrower; + this.loanAmount = loanAmount; + this.tenure = tenure; + this.rateofInterest = rateofInterest; + this.transactionFees = transactionFees; + this.projectIdentifier = projectIdentifier; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + this.lender = getOurIdentity(); + + + List> projectStateAndRefs = getServiceHub().getVaultService() + .queryBy(ProjectState.class).getStates(); + + StateAndRef inputStateAndRef = projectStateAndRefs.stream().filter(projectStateAndRef -> { + ProjectState projectState = projectStateAndRef.getState().getData(); + return projectState.getLinearId().equals(projectIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Project Not Found")); + + Party notary = inputStateAndRef.getState().getNotary(); + + final LoanBidState output = new LoanBidState( + new StaticPointer<>(inputStateAndRef.getRef(), ProjectState.class), + new UniqueIdentifier(), + lender, borrower, + loanAmount, tenure, rateofInterest, transactionFees, + "SUBMITTED" + ); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the project as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addCommand(new LoanBidContract.Commands.Submit(), Arrays.asList(lender.getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession cpSession = initiateFlow(borrower); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, Arrays.asList(cpSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitProjectProposalFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitProjectProposalFlow.java new file mode 100644 index 00000000..036f7f18 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitProjectProposalFlow.java @@ -0,0 +1,90 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.ProjectContract; +import net.corda.samples.lending.states.ProjectState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class SubmitProjectProposalFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic{ + + private Party borrower; + private List lenders; + private String projectDescription; + private int projectCost; + private int loanAmount; + + //public constructor + public Initiator(List lenders, String projectDescription, int projectCost, int loanAmount) { + this.lenders = lenders; + this.projectDescription = projectDescription; + this.projectCost = projectCost; + this.loanAmount = loanAmount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + this.borrower = getOurIdentity(); + + // Step 1. Get a reference to the notary service on our network and our key pair. + /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + + + //Compose the State that carries the Hello World message + final ProjectState output = new ProjectState(new UniqueIdentifier(), projectDescription, borrower, + projectCost, loanAmount, lenders); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the project as an output state, as well as a command to the transaction builder. + builder.addOutputState(output); + builder.addCommand(new ProjectContract.Commands.ProposeProject(), Collections.singletonList(borrower.getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + List cpSesions = lenders.stream().map(this::initiateFlow).collect(Collectors.toList()); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, cpSesions)); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic{ + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitSyndicateBidFlow.java b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitSyndicateBidFlow.java new file mode 100644 index 00000000..b1740ec6 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/main/java/net/corda/samples/lending/flows/SubmitSyndicateBidFlow.java @@ -0,0 +1,97 @@ +package net.corda.samples.lending.flows; + +import co.paralleluniverse.fibers.Suspendable; +import net.corda.core.contracts.LinearPointer; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.flows.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.samples.lending.contracts.SyndicateBidContract; +import net.corda.samples.lending.states.SyndicateBidState; +import net.corda.samples.lending.states.SyndicateState; + +import java.util.Arrays; +import java.util.List; + +public class SubmitSyndicateBidFlow { + + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + private UniqueIdentifier syndicateIdentifier; + private int bidAmount; + + //public constructor + public Initiator(UniqueIdentifier syndicateIdentifier, int bidAmount) { + this.syndicateIdentifier = syndicateIdentifier; + this.bidAmount = bidAmount; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + // Fetch Project + List> syndicateStateAndRefs = getServiceHub().getVaultService() + .queryBy(SyndicateState.class).getStates(); + + StateAndRef syndicateStateAndRef = syndicateStateAndRefs.stream().filter(synStateAndRef -> { + SyndicateState syndicateState = synStateAndRef.getState().getData(); + return syndicateState.getLinearId().equals(syndicateIdentifier); + }).findAny().orElseThrow(() -> new IllegalArgumentException("Syndicate Not Found")); + + SyndicateState syndicateState = syndicateStateAndRef.getState().getData(); + + SyndicateBidState syndicateBidState = new SyndicateBidState( + new UniqueIdentifier(), + new LinearPointer<>(syndicateIdentifier, SyndicateState.class), + bidAmount, + syndicateState.getLeadBank(), + getOurIdentity(), + "SUBMITTED" + ); + + Party notary = syndicateStateAndRef.getState().getNotary(); + + // Step 3. Create a new TransactionBuilder object. + final TransactionBuilder builder = new TransactionBuilder(notary); + + // Step 4. Add the project as an output state, as well as a command to the transaction builder. + builder.addOutputState(syndicateBidState); + builder.addCommand(new SyndicateBidContract.Commands.Submit(), Arrays.asList(getOurIdentity().getOwningKey())); + + // Step 5. Verify and sign it with our KeyPair. + builder.verify(getServiceHub()); + final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder); + + FlowSession cpSession = initiateFlow(syndicateState.getLeadBank()); + + // Step 7. Assuming no exceptions, we can now finalise the transaction + return subFlow(new FinalityFlow(ptx, Arrays.asList(cpSession))); + } + } + + @InitiatedBy(Initiator.class) + public static class Responder extends FlowLogic { + //private variable + private FlowSession counterpartySession; + + //Constructor + public Responder(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + + //Stored the transaction into data base. + subFlow(new ReceiveFinalityFlow(counterpartySession)); + return null; + } + } + +} diff --git a/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/AllInOneTester.java b/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/AllInOneTester.java new file mode 100644 index 00000000..bfe225ec --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/AllInOneTester.java @@ -0,0 +1,113 @@ +package net.corda.samples.lending; + +import com.google.common.collect.ImmutableList; +import net.corda.core.contracts.UniqueIdentifier; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.node.NetworkParameters; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.lending.flows.*; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; +import net.corda.samples.lending.states.SyndicateBidState; +import net.corda.samples.lending.states.SyndicateState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.Future; + +import static org.junit.Assert.assertEquals; + +public class AllInOneTester { + + private MockNetwork network; + private StartedMockNode bankA; + private StartedMockNode bankB; + private StartedMockNode bankC; + private StartedMockNode borrower; + private NetworkParameters testNetworkParameters = + new NetworkParameters(4, Arrays.asList(), 10485760, (10485760 * 5), Instant.now(),1, new LinkedHashMap<>()); + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.lending.contracts"), + TestCordapp.findCordapp("net.corda.samples.lending.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB")))) + .withNetworkParameters(testNetworkParameters)); + borrower = network.createPartyNode(null); + bankA = network.createPartyNode(null); + bankB = network.createPartyNode(null); + bankC = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + @Test + public void AllBusinessLogicFlowTests() { + //Create the project + List lenders = new ArrayList(); + lenders.add(bankA.getInfo().getLegalIdentities().get(0)); + lenders.add(bankB.getInfo().getLegalIdentities().get(0)); + SubmitProjectProposalFlow.Initiator createProjectFlow = new SubmitProjectProposalFlow.Initiator(lenders, "oversea expansion", 100, 10); + Future future = borrower.startFlow(createProjectFlow); + network.runNetwork(); + + //successful query means the state is stored at node b's vault. Flow went through. + QueryCriteria inputCriteria = new QueryCriteria.VaultQueryCriteria().withStatus(Vault.StateStatus.UNCONSUMED); + ProjectState project = bankA.getServices().getVaultService().queryBy(ProjectState.class,inputCriteria).getStates().get(0).getState().getData(); + + //place loan bid + UniqueIdentifier projectID = project.getLinearId(); + SubmitLoanBidFlow.Initiator submitLoanBidFlow = new SubmitLoanBidFlow.Initiator(borrower.getInfo().getLegalIdentities().get(0), 7, 5, 4.0, 1, projectID); + Future future2 = bankA.startFlow(submitLoanBidFlow); + network.runNetwork(); + LoanBidState loanBid = borrower.getServices().getVaultService().queryBy(LoanBidState.class,inputCriteria).getStates().get(0).getState().getData(); + + //Borrower approves the loan bid. pick the winning bank + UniqueIdentifier loanBidId = loanBid.getLinearId(); + ApproveLoanBidFlow.Initiator approveLoanBidFlow = new ApproveLoanBidFlow.Initiator(loanBidId); + Future future3 = borrower.startFlow(approveLoanBidFlow); + network.runNetwork(); + LoanBidState loanBidApproved = bankA.getServices().getVaultService().queryBy(LoanBidState.class,inputCriteria).getStates().get(0).getState().getData(); + assertEquals("APPROVED", loanBidApproved.getStatus()); + + //lead bank create Syndication + UniqueIdentifier approveLoanBidID = loanBidApproved.getLinearId(); + List participatingBanks = new ArrayList(); + participatingBanks.add(bankB.getInfo().getLegalIdentities().get(0)); + participatingBanks.add(bankC.getInfo().getLegalIdentities().get(0)); + CreateSyndicateFlow.Initiator createSyndication = new CreateSyndicateFlow.Initiator(participatingBanks, projectID, approveLoanBidID); + Future future4 = bankA.startFlow(createSyndication); + network.runNetwork(); + SyndicateState syndication = bankA.getServices().getVaultService().queryBy(SyndicateState.class,inputCriteria).getStates().get(0).getState().getData(); + + //participating bank submit syndication bid + UniqueIdentifier syndicationID = syndication.getLinearId(); + SubmitSyndicateBidFlow.Initiator submitSyndicationBid = new SubmitSyndicateBidFlow.Initiator(syndicationID,2); + Future future5 = bankB.startFlow(submitSyndicationBid); + network.runNetwork(); + SyndicateBidState syndicatedBid = bankA.getServices().getVaultService().queryBy(SyndicateBidState.class,inputCriteria).getStates().get(0).getState().getData(); + + //Lead bank approves syndication bid + UniqueIdentifier syndicationBidId = syndicatedBid.getLinearId(); + ApproveSyndicateBidFlow.Initiator approveSyndicationBid = new ApproveSyndicateBidFlow.Initiator(syndicationBidId); + Future future6 = bankA.startFlow(approveSyndicationBid); + network.runNetwork(); + SyndicateBidState syndicatedBidApproved = bankA.getServices().getVaultService().queryBy(SyndicateBidState.class,inputCriteria).getStates().get(0).getState().getData(); + assertEquals("APPROVED", syndicatedBidApproved.getStatus()); + + } +} diff --git a/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/FlowTests.java b/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/FlowTests.java new file mode 100644 index 00000000..2f2ab817 --- /dev/null +++ b/Advanced/syndicated-lending/workflows/src/test/java/net/corda/samples/lending/FlowTests.java @@ -0,0 +1,104 @@ +package net.corda.samples.lending; + +import com.google.common.collect.ImmutableList; +import net.corda.core.concurrent.CordaFuture; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.transactions.SignedTransaction; +import net.corda.samples.lending.flows.ApproveLoanBidFlow; +import net.corda.samples.lending.flows.SubmitLoanBidFlow; +import net.corda.samples.lending.flows.SubmitProjectProposalFlow; +import net.corda.samples.lending.states.LoanBidState; +import net.corda.samples.lending.states.ProjectState; +import net.corda.testing.node.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.*; + +public class FlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + private StartedMockNode c; + private StartedMockNode d; + + @Before + public void setup() { + network = new MockNetwork(new MockNetworkParameters().withCordappsForAllNodes(ImmutableList.of( + TestCordapp.findCordapp("net.corda.samples.lending.contracts"), + TestCordapp.findCordapp("net.corda.samples.lending.flows"))) + .withNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(CordaX500Name.parse("O=Notary,L=London,C=GB"))))); + a = network.createPartyNode(null); + b = network.createPartyNode(null); + c = network.createPartyNode(null); + d = network.createPartyNode(null); + network.runNetwork(); + } + + @After + public void tearDown() { + network.stopNodes(); + } + + private ProjectState createProject() throws Exception{ + SubmitProjectProposalFlow.Initiator flow = new SubmitProjectProposalFlow.Initiator( + Arrays.asList( + b.getInfo().getLegalIdentities().get(0), + c.getInfo().getLegalIdentities().get(0)), + "Test Project", 10000000, 8000000); + CordaFuture future = a.startFlow(flow); + network.runNetwork(); + SignedTransaction transaction = future.get(); + ProjectState projectState = (ProjectState) transaction.getTx().getOutput(0); + return projectState; + } + + private LoanBidState submitLoanBid(ProjectState projectState) throws Exception{ + SubmitLoanBidFlow.Initiator flow = new SubmitLoanBidFlow.Initiator( + projectState.getBorrower(), + 8000000, 5, 8, 20000, + projectState.getLinearId() + ); + CordaFuture future = b.startFlow(flow); + network.runNetwork(); + SignedTransaction transaction = future.get(); + LoanBidState loanBidState = (LoanBidState) transaction.getTx().getOutput(0); + return loanBidState; + } + + private LoanBidState approveLoanBid(LoanBidState loanBidState) throws Exception{ + ApproveLoanBidFlow.Initiator flow = new ApproveLoanBidFlow.Initiator(loanBidState.getLinearId()); + CordaFuture future = a.startFlow(flow); + network.runNetwork(); + SignedTransaction transaction = future.get(); + LoanBidState loanBidStateUpdated = (LoanBidState) transaction.getTx().getOutput(0); + return loanBidStateUpdated; + } + + @Test + public void testSubmitProjectProposalFlow() throws Exception{ + ProjectState projectState = createProject(); + assertNotNull(projectState); + } + + @Test + public void testSubmitLoanBidFlow() throws Exception{ + ProjectState projectState = createProject(); + LoanBidState loanBidState = submitLoanBid(projectState); + assertNotNull(loanBidState); + } + + @Test + public void testApproveLoanBidFlow() throws Exception{ + ProjectState projectState = createProject(); + LoanBidState loanBidState = submitLoanBid(projectState); + LoanBidState loanBidStateApproved = approveLoanBid(loanBidState); + assertNotNull(loanBidStateApproved); + assertEquals("APPROVED", loanBidStateApproved.getStatus()); + } + + +} diff --git a/Basic/README.md b/Basic/README.md index e130d106..1e89b325 100644 --- a/Basic/README.md +++ b/Basic/README.md @@ -1,23 +1,17 @@ -## samples-java/basic-cordapps +## Basic Cordapp Sample -This folder features basic sample projects, each of them demonstrates low level cordapp functionalities, such rpc connection, messing, and etc. +This folder features basic sample projects, each of them demonstrates low level cordapp functionalities, such as [RPC connection](https://docs.corda.net/docs/corda-os/api-rpc.html#api-rpc-operations), messaging, etc. -### [database access](./flow-database-access): +### [Cordapp Example](./cordapp-example): +A simple exploratory sample for the official [Corda online training](https://training.corda.net). + +### [Database Access](./flow-database-access): This CorDapp provides a simple example of how the node database can be accessed within flows. In this case, the flows maintain a table of cryptocurrency values in the node's database. -### [http access](./flow-http-access): +### [Http Access](./flow-http-access): This CorDapp provides a simple example of how HTTP requests can be made in flows. In this case, the flow makes an HTTP request to retrieve the original BitCoin readme from GitHub. -### [ping](./flow-send-msg): -This CorDapp allows a node to ping any other node on the network that also has this CorDapp installed. - -### [rpc node info](./rpc-nodeinfo): -Allows one to get some rudimentary information about a running Corda node via RPC - -### [yo cordapp](./yo-cordapp): -Send Yo's! to all your friends running Corda nodes! - - - +### [Ping Pong](./ping-pong): +This CorDapp allows a node to ping any other node on the network, demonstrating stateless communication between nodes. diff --git a/Basic/constants.properties b/Basic/constants.properties index 0b0f28b2..eb9e0ed4 100644 --- a/Basic/constants.properties +++ b/Basic/constants.properties @@ -1,12 +1,12 @@ cordaReleaseGroup=net.corda cordaCoreReleaseGroup=net.corda -cordaVersion=4.5-RC02 -cordaCoreVersion=4.5-RC02 -gradlePluginsVersion=5.0.10 +cordaVersion=4.10 +cordaCoreVersion=4.10 +gradlePluginsVersion=5.0.12 kotlinVersion=1.2.71 junitVersion=4.12 quasarVersion=0.7.10 -log4jVersion =2.11.2 -platformVersion=5 +log4jVersion =2.17.1 +platformVersion=10 slf4jVersion=1.7.25 nettyVersion=4.1.22.Final diff --git a/Basic/cordapp-example/.idea/compiler.xml b/Basic/cordapp-example/.idea/compiler.xml index b31e7c34..61a9130c 100644 --- a/Basic/cordapp-example/.idea/compiler.xml +++ b/Basic/cordapp-example/.idea/compiler.xml @@ -1,30 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/gradle.xml b/Basic/cordapp-example/.idea/gradle.xml index 5906f8ac..7518eaeb 100644 --- a/Basic/cordapp-example/.idea/gradle.xml +++ b/Basic/cordapp-example/.idea/gradle.xml @@ -1,21 +1,22 @@ + diff --git a/Basic/cordapp-example/.idea/jarRepositories.xml b/Basic/cordapp-example/.idea/jarRepositories.xml index d9160e27..554006bf 100644 --- a/Basic/cordapp-example/.idea/jarRepositories.xml +++ b/Basic/cordapp-example/.idea/jarRepositories.xml @@ -24,27 +24,17 @@ - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/misc.xml b/Basic/cordapp-example/.idea/misc.xml index 7bf4745e..b0051c81 100644 --- a/Basic/cordapp-example/.idea/misc.xml +++ b/Basic/cordapp-example/.idea/misc.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Debug_CorDapp.xml b/Basic/cordapp-example/.idea/runConfigurations/Debug_CorDapp.xml deleted file mode 100644 index 99ec0b2d..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Debug_CorDapp.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Run_Contract_Tests___Java.xml b/Basic/cordapp-example/.idea/runConfigurations/Run_Contract_Tests___Java.xml deleted file mode 100644 index 3e8b4d6e..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Run_Contract_Tests___Java.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Run_Contract_Tests___Kotlin.xml b/Basic/cordapp-example/.idea/runConfigurations/Run_Contract_Tests___Kotlin.xml deleted file mode 100644 index 9f79ca8c..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Run_Contract_Tests___Kotlin.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Run_Example_Cordapp___Java.xml b/Basic/cordapp-example/.idea/runConfigurations/Run_Example_Cordapp___Java.xml deleted file mode 100644 index 9a6c2ad8..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Run_Example_Cordapp___Java.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Run_Example_Cordapp___Kotlin.xml b/Basic/cordapp-example/.idea/runConfigurations/Run_Example_Cordapp___Kotlin.xml deleted file mode 100644 index 5746ab23..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Run_Example_Cordapp___Kotlin.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Run_Flow_Tests___Java.xml b/Basic/cordapp-example/.idea/runConfigurations/Run_Flow_Tests___Java.xml deleted file mode 100644 index 51c71dab..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Run_Flow_Tests___Java.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Run_Flow_Tests___Kotlin.xml b/Basic/cordapp-example/.idea/runConfigurations/Run_Flow_Tests___Kotlin.xml deleted file mode 100644 index 5672785b..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Run_Flow_Tests___Kotlin.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Run_Integration_Tests___Java.xml b/Basic/cordapp-example/.idea/runConfigurations/Run_Integration_Tests___Java.xml deleted file mode 100644 index e70a32fe..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Run_Integration_Tests___Java.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Run_Integration_Tests___Kotlin.xml b/Basic/cordapp-example/.idea/runConfigurations/Run_Integration_Tests___Kotlin.xml deleted file mode 100644 index d11b22ad..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Run_Integration_Tests___Kotlin.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/runConfigurations/Unit_tests.xml b/Basic/cordapp-example/.idea/runConfigurations/Unit_tests.xml deleted file mode 100644 index 0d8cb29e..00000000 --- a/Basic/cordapp-example/.idea/runConfigurations/Unit_tests.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/Basic/cordapp-example/.idea/workspace.xml b/Basic/cordapp-example/.idea/workspace.xml index 1da3bfca..972fede0 100644 --- a/Basic/cordapp-example/.idea/workspace.xml +++ b/Basic/cordapp-example/.idea/workspace.xml @@ -1,13 +1,21 @@ + + - - - - - - - + + + + + + + + + + + +