Skip to content

Commit

Permalink
Merge pull request #195 from cosmos/feat/use-dgraph
Browse files Browse the repository at this point in the history
Use Dgraph instead of Fauna
  • Loading branch information
abefernan authored Mar 5, 2024
2 parents 5e8404c + 1ae3a54 commit 52e9c03
Show file tree
Hide file tree
Showing 17 changed files with 137 additions and 298 deletions.
4 changes: 2 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FAUNADB_SECRET=
FAUNADB_URL=https://graphql.eu.fauna.com/graphql
DGRAPH_SECRET=
DGRAPH_URL=https://nameless-brook-560056.eu-central-1.aws.cloud.dgraph.io/graphql
NEXT_PUBLIC_MULTICHAIN=true
NEXT_PUBLIC_REGISTRY_NAME=cosmoshub
4 changes: 2 additions & 2 deletions .env.test.sample
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FAUNADB_SECRET=
FAUNADB_URL=https://graphql.eu.fauna.com/graphql
DGRAPH_SECRET=
DGRAPH_URL=https://nameless-brook-560056.eu-central-1.aws.cloud.dgraph.io/graphql
NEXT_PUBLIC_MULTICHAIN=true
NEXT_PUBLIC_REGISTRY_NAME=junotestnet
36 changes: 17 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Cosmoshub Multisig App

This app allows for multisig users to create, sign and broadcast transactions on any stargate enabled chain. It's built with Cosmjs, Next.js, FaunaDB and Vercel.
This app allows for multisig users to create, sign and broadcast transactions on any stargate enabled chain. It's built with Cosmjs, Next.js, Dgraph and Vercel.

[The app is live here](https://cosmos-multisig-ui-kohl.vercel.app/).
[The app is live here](https://multisig.confio.run).

[Here is a user guide on how to use the app](https://github.com/samepant/cosmoshub-legacy-multisig/blob/master/docs/App%20User%20Guide.md)

Expand All @@ -16,19 +16,18 @@ This app uses Vercel for deployment and hosting, since they support next.js's se

### 2. Setup environment variables

In the Vercel control panel for your new app, go to `Settings -> Environment Variables` and add in the keys and values from this repo's `.env.sample` file. The only remaining variable should be the `FAUNADB_SECRET`, which will be available once you setup your FaunaDB instance.
In the Vercel control panel for your new app, go to `Settings -> Environment Variables` and add in the keys and values from this repo's `.env.sample` file. The only remaining variable should be the `DGRAPH_SECRET`, which will be available once you setup your DGraph instance.

### 3. Initializing FaunaDB
### 3. Initializing DGraph

This app relies on FaunaDB as for storing account, transaction and signature details.
This app relies on DGraph as for storing account, transaction and signature details.

- Create a [FaunaDB](https://dashboard.fauna.com/) account
- Create a new database
- Use the "Classic" region
- Click the "Graphql" tab, and import the `db-schema.graphql` file in the root of this repo
- Click the "Security" tab, and create a key. Copy that key into your vercel app's environment variables as the `FAUNADB_SECRET` value
- Create a [DGraph](https://cloud.dgraph.io) account
- Launch a new backend
- Click the `Develop -> Schema` menu item, and past the contents of the `db-schema.graphql` file in the root of this repo
- Click the `Admin -> Settings` menu item, and create a key. Copy that key into your vercel app's environment variables as the `DGRAPH_SECRET` value

As your instance of the app is used, you can return to the FaunaDB dashboard to view records for any accounts, transactions or signatures.
As your instance of the app is used, you can return to the DGraph dashboard to view records for any accounts, transactions or signatures.

### 4. Successful Deployment

Expand All @@ -46,17 +45,16 @@ It's recommended that you make your simapp instance mimic the denomination of co

A more in depth tutorial on this is coming soon :)

### 3. Initializing FaunaDB
### 3. Initializing DGraph

This app relies on FaunaDB as for storing account, transaction and signature details.
This app relies on DGraph as for storing account, transaction and signature details.

- Create a [FaunaDB](https://dashboard.fauna.com/) account
- Create a new database
- Use the "Europe (EU)" region
- Click the "Graphql" tab, and import the `db-schema.graphql` file in the root of this repo
- Click the "Security" tab, and create a key. Copy that key into the `.env.local` file for the `FAUNADB_SECRET` value
- Create a [DGraph](https://cloud.dgraph.io) account
- Launch a new backend
- Click the `Develop -> Schema` menu item, and past the contents of the `db-schema.graphql` file in the root of this repo
- Click the `Admin -> Settings` menu item, and create a key. Copy that key into your vercel app's environment variables as the `DGRAPH_SECRET` value

As your instance of the app is used, you can return to the FaunaDB dashboard to view records for any accounts, transactions or signatures created.
As your instance of the app is used, you can return to the DGraph dashboard to view records for any accounts, transactions or signatures.

### 3. Run your instance

Expand Down
24 changes: 0 additions & 24 deletions components/MigrationWarning.tsx

This file was deleted.

26 changes: 10 additions & 16 deletions db-schema.graphql
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
type Multisig {
id: ID!
# The @search annotation allows us to query the list of multisigs and filter
# by (chainId, address) pairs. We use "hash" since we only need exact matches.
# See https://dgraph.io/docs/graphql/schema/directives/search/#string
chainId: String! @search(by: [hash])
address: String! @search(by: [hash])
pubkeyJSON: String!
address: String!
chainId: String!
}

type SourceAddress {
nickname: String
address: String!
pubkey: String!
multisig: Multisig @relation
}

type Transaction {
signatures: [Signature] @relation
dataJSON: String
id: ID!
txHash: String
dataJSON: String
signatures: [Signature] @hasInverse(field: transaction)
}

type Signature {
transaction: Transaction! @relation
transaction: Transaction!
bodyBytes: String!
signature: String!
address: String!
}

type Query {
getMultisig(address: String!, chainId: String!): Multisig
}
145 changes: 86 additions & 59 deletions lib/graphqlHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@ import { DbAccount, DbSignature, DbTransaction } from "../types";
import { requestGraphQlJson } from "./request";

/**
* Creates multisig record in faunadb
* Creates multisig record in dgraph
*
* @param {object} multisig an object with address (string), pubkey JSON and chainId
* @return Returns async function that makes a request to the faunadb graphql endpoint
* @return Returns async function that makes a request to the dgraph graphql endpoint
*/
const createMultisig = async (multisig: DbAccount) => {
console.log(multisig);
return requestGraphQlJson({
body: {
query: `
mutation {
createMultisig(data: {
address: "${multisig.address}"
pubkeyJSON: ${JSON.stringify(multisig.pubkeyJSON)}
chainId: "${multisig.chainId}"
}) {
_id
address
chainId
mutation AddMultisig {
addMultisig(
input: {
chainId: "${multisig.chainId}"
address: "${multisig.address}"
pubkeyJSON: ${JSON.stringify(multisig.pubkeyJSON)}
}
) {
multisig {
id
chainId
address
}
}
}
`,
Expand All @@ -29,41 +32,62 @@ const createMultisig = async (multisig: DbAccount) => {
};

/**
* Gets multisig pubkey from faundb
* This is the format returned by the graphQL API.
*
* Keep the format in sync with `GetMultisigAccountResponse` because
* we return the full object in the API. Right now address and chainId
* are somewhat unnecessary to query but still nice for debgging.
*/
interface MultisigFromQuery {
address: string;
chainId: string;
pubkeyJSON: string;
}

/**
* Gets multisig pubkey from DB
*
* @param {string} address A multisig address.
* @param {string} chainId The chainId the multisig belongs to.
* @return Returns async function that makes a request to the faunadb graphql endpoint
* @return Returns async function that makes a request to the dgraph graphql endpoint
*/
const getMultisig = async (address: string, chainId: string) => {
return requestGraphQlJson({
async function getMultisig(
address: string,
chainId: string,
): Promise<MultisigFromQuery | undefined> {
const result = await requestGraphQlJson({
body: {
query: `
query {
getMultisig(address: "${address}", chainId: "${chainId}",) {
query MultisigsByAddressAndChainId {
queryMultisig(filter: {address: {eq: "${address}"}, chainId: {eq: "${chainId}"}}) {
address
pubkeyJSON
chainId
pubkeyJSON
}
}
`,
},
});
};
const elements: [MultisigFromQuery] = result.data.queryMultisig;
const first = elements.find(() => true);
return first;
}

/**
* Creates transaction record in faunadb
* Creates transaction record in dgraph
*
* @param {object} transaction The base transaction
* @return Returns async function that makes a request to the faunadb graphql endpoint
* @return Returns async function that makes a request to the dgraph graphql endpoint
*/
const createTransaction = async (transaction: DbTransaction) => {
return requestGraphQlJson({
body: {
query: `
mutation {
createTransaction(data: {dataJSON: ${JSON.stringify(transaction)}}) {
_id
mutation AddTransaction {
addTransaction(input: { dataJSON: ${JSON.stringify(transaction)} }) {
transaction {
id
}
}
}
`,
Expand All @@ -72,26 +96,23 @@ const createTransaction = async (transaction: DbTransaction) => {
};

/**
* Retrieves a transaction from faunadb
* Retrieves a transaction from dgraph
*
* @param {string} id Faunadb resource id
* @return Returns async function that makes a request to the faunadb graphql endpoint
* @param {string} id dgraph resource id
* @return Returns async function that makes a request to the dgraph graphql endpoint
*/
const findTransactionByID = async (id: string) => {
return requestGraphQlJson({
body: {
query: `
query {
findTransactionByID(id: "${id}") {
_id
query GetTransaction {
getTransaction(id: "${id}") {
dataJSON
txHash
signatures {
data {
address
signature
bodyBytes
}
address
signature
bodyBytes
}
}
}
Expand All @@ -101,23 +122,25 @@ const findTransactionByID = async (id: string) => {
};

/**
* Updates txHash of transaction on FaunaDB
* Updates txHash of transaction on dgraph
*
* @param {string} id Faunadb resource id
* @param {string} id dgraph resource id
* @param {string} txHash tx hash returned from broadcasting a tx
* @return Returns async function that makes a request to the faunadb graphql endpoint
* @return Returns async function that makes a request to the dgraph graphql endpoint
*/
const updateTxHash = async (id: string, txHash: string) => {
return requestGraphQlJson({
body: {
query: `
mutation {
updateTransaction(id: ${id}, data: {txHash: "${txHash}"}) {
_id
dataJSON
txHash
signatures {
data {
mutation UpdateTransaction {
updateTransaction(
input: { filter: { id: "${id}" }, set: { txHash: "${txHash}" } }
) {
transaction {
id
dataJSON
txHash
signatures {
address
signature
bodyBytes
Expand All @@ -131,27 +154,31 @@ const updateTxHash = async (id: string, txHash: string) => {
};

/**
* Creates signature record in faunadb
* Creates signature record in dgraph
*
* @param {object} signature an object with bodyBytes (string) and signature set (Uint8 Array)
* @param {string} transactionId id of the transaction to relate the signature with
* @return Returns async function that makes a request to the faunadb graphql endpoint
* @return Returns async function that makes a request to the dgraph graphql endpoint
*/
const createSignature = async (signature: DbSignature, transactionId: string) => {
return requestGraphQlJson({
body: {
query: `
mutation {
createSignature(data: {
transaction: {connect: ${transactionId}},
bodyBytes: "${signature.bodyBytes}",
signature: "${signature.signature}",
address: "${signature.address}"
}) {
_id
address
signature
address
mutation AddSignature {
addSignature(
input: {
transaction: { id: "${transactionId}" }
address: "${signature.address}"
signature: "${signature.signature}"
bodyBytes: "${signature.bodyBytes}"
}
) {
signature {
transaction {
id
}
signature
}
}
}
`,
Expand Down
4 changes: 2 additions & 2 deletions lib/multisigHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const createMultisigFromCompressedSecp256k1Pubkeys = async (
const multisigPubkey = createMultisigThresholdPubkey(pubkeys, threshold);
const multisigAddress = pubkeyToAddress(multisigPubkey, addressPrefix);

// save multisig to fauna
// save multisig to relational offchain database
const multisig = {
address: multisigAddress,
pubkeyJSON: JSON.stringify(multisigPubkey),
Expand Down Expand Up @@ -73,7 +73,7 @@ const getMultisigAccount = async (
// we need the multisig pubkeys to create transactions, if the multisig
// is new, and has never submitted a transaction its pubkeys will not be
// available from a node. If the multisig was created with this instance
// of this tool its pubkey will be available in the fauna datastore
// of this tool its pubkey will be available in the relational offchain database
const addressError = checkAddress(address, addressPrefix);
if (addressError) {
throw new Error(addressError);
Expand Down
Loading

0 comments on commit 52e9c03

Please sign in to comment.