Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add graphql provider #111

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

zachyking
Copy link
Contributor

draft, I have no idea if it works, needs some debug, but somebody might want to get to it sooner

Allows using Cardano GraphQL instance as data provider in combination with Cardano submit API for submitting txs.
(e.g. dandelion.link for free GraphQL by the community and https://www.freeloaderz.io/ for free load balanced submit api from SPOs)

I have a question though @alessandrokonrad, I am not sure about delegation endpoint, I can get list of records with poolId and reward, so I just return first record rn. Should I count those reward amounts together from all records and return sum with latest poolId?

@GGAlanSmithee
Copy link
Contributor

This is really cool. I was thinking about this the other day, and would love to try this PR.

If you don't mind me asking here, I haven't really been able to wrap my head around how actual transaction processing works. Does the wallets handle this, much like Metamask does on ethereum with Infura, or does the providers do that? And if so, is it possible to do that via the dandelion API? Thanks! :)

@zachyking
Copy link
Contributor Author

I guess I have couple more things for now @alessandrokonrad 😃, getDatum is sh*t because I can't add condition to filter records where datum bytes are null and just take one. Some time ago datum hash wasn't available in GraphQL scheme even though it was already on-chain, so I guess it just takes time to catch-up and we might get that at some point. I noted that in comments.
Even then I am not sure if current way is the best way to try getting datum so I do welcome suggestions.

I could write some test functions to call all methods, set results into some object, then I can deep compare between providers. But changes in graphql scheme etc might be breaking builds too frequently. So maybe there could be a limit, like 70-90% similar key and values pairs means pass? Or maybe test only protocol params from each? I can also totally ignore that, but I am fine getting the test together when I'll be debugging.

@zachyking
Copy link
Contributor Author

zachyking commented Oct 29, 2022

This is really cool. I was thinking about this the other day, and would love to try this PR.

If you don't mind me asking here, I haven't really been able to wrap my head around how actual transaction processing works. Does the wallets handle this, much like Metamask does on ethereum with Infura, or does the providers do that? And if so, is it possible to do that via the dandelion API? Thanks! :)

Transaction is built off-chain. For that you need live blockchain data, specifically UTXOs: e.g. Blockfrost, cardano-db-sync with postgress, GraphQL, or alternatives. If it's on website client, you can get them also from wallet and deserialize using cardano-multiplatform-lib, but you might miss some information anyway to build the tx, so data from CIP-30 Dapp connector wallet are usually not enough for whole dapp.

When you build the tx, you can submit it using freeloaderz for example, that's free and community run, load balanced. I think dandelion runs submit-api endpoint so that's fine too. If you want higher reliability, paying for blockfrost subscription might be worth it as they will resubmit txs that are dropped because of chain forks (forks are usually like 1-2 blocks, totally normal thing) in higher pay tier. Or again if it's a web client, you can use wallet endpoint to submit.

You can also run all of these pieces of infrastructure, Cardano blockchain is still pretty lightweight (even thought GraphQL/db-sync are beasts, if you think about playing with that, you might wanna look into carp, scrolls, kupo or maybe even ogmios ) @GGAlanSmithee

};
}

async getCurrentSlot(): Promise<Slot> {
Copy link
Contributor

@alessandrokonrad alessandrokonrad Oct 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint is not necessary and not a part of the provider interface anymore. The current slot can be calculated off-chain.

Comment on lines +120 to +140
txHash: r.txHash,
outputIndex: r.index,
assets: (() => {
const a: Assets = {};
r.tokens.forEach((token: {
asset: {
policyId: string;
assetName: string;
};
quantity: string;
}) => {
a[token.asset.policyId + token.asset.assetName] = BigInt(
token.quantity,
);
});
a["lovelace"] = BigInt(r.value);
return a;
})(),
address,
datumHash: r.datum?.hash,
datum: r.datum?.bytes,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scriptRef is missing :)

@alessandrokonrad
Copy link
Contributor

I have a question though @alessandrokonrad, I am not sure about delegation endpoint, I can get list of records with poolId and reward, so I just return first record rn. Should I count those reward amounts together from all records and return sum with latest poolId?

In general the getDelgation functions returns to you the current delegation state.
Are you delegated to a pool? Yes => Return pool id; No => Return null
And return the current withdrawable amount, otherwise 0.

Does the GraphQL API have insights into the current state of things? Or is it only historic data?

@GGAlanSmithee
Copy link
Contributor

@zachyking tyvm for taking your time, very valuable!

@alessandrokonrad
Copy link
Contributor

I can't add condition to filter records where datum bytes are null and just take one

What do you mean by that?

Some time ago datum hash wasn't available in GraphQL scheme even though it was already on-chain, so I guess it just takes time to catch-up and we might get that at some point

So the datum is not directly available to query even tho it was just exposed by a tx?

@zachyking
Copy link
Contributor Author

zachyking commented Oct 30, 2022

I have a question though @alessandrokonrad, I am not sure about delegation endpoint, I can get list of records with poolId and reward, so I just return first record rn. Should I count those reward amounts together from all records and return sum with latest poolId?

In general the getDelgation functions returns to you the current delegation state. Are you delegated to a pool? Yes => Return pool id; No => Return null And return the current withdrawable amount, otherwise 0.

Does the GraphQL API have insights into the current state of things? Or is it only historic data?

GraphQL API has insights to current state sometimes, querying utxos directly seem to return only unspent ones.

I tried withdrawing my staking rewards, and I see the same records in case of Delegation. I'll try again later, maybe there is just some longer delay, but if not, I can just get both delegation and withdrawals to calculate currently available rewards.

@zachyking
Copy link
Contributor Author

zachyking commented Oct 30, 2022

I can't add condition to filter records where datum bytes are null and just take one

What do you mean by that?

Some time ago datum hash wasn't available in GraphQL scheme even though it was already on-chain, so I guess it just takes time to catch-up and we might get that at some point

So the datum is not directly available to query even tho it was just exposed by a tx?

There is a datum object on utxos, which has bytes and hash. I can have where condition using hash, but not bytes. This means instead of getting only one valid record if exists where bytes are not null, I have to get all, and try finding not null bytes in lucid.
Thinking about it, it for sure needs a change to go over query { transactions(where: { outputs: { datum: { hash: { _eq: so it includes spent utxo data, and that way it should work.
But querying for datum hashes that are common will be returning a lot of results that we don't need (until it's possible to add condition to the graphql query bytes !== null, then we can add limit=1)

@alessandrokonrad
Copy link
Contributor

alessandrokonrad commented Oct 30, 2022

There is a datum object on utxos, which has bytes and hash. I can have where condition using hash, but not bytes. This means instead of getting only one valid record if exists where bytes are not null, I have to get all, and try finding not null bytes in lucid.
Thinking about it, it for sure needs a change to go over query { transactions(where: { outputs: { datum: { hash: { _eq: so it includes spent utxo data, and that way it should work.
But querying for datum hashes that are common will be returning a lot of results that we don't need (until it's possible to add condition to the graphql query bytes !== null, then we can add limit=1)

@zachyking so you think it could be a problem because you also query other utxos where datum is not set and you can't filter them out? But the query you suggested there seems sufficient enough I think. Maybe it's not really a problem with all these outputs, but I'm not a graphql user really. What happens if you do {datum: {hash: {_neq: null}}}. Would this not remove outputs with no datum?

Maybe you want to extend the query to also redeemers and collateralOutputs, just to really capture all datums:

query queryDatum {
  transactions(
    limit: 1
    where: {
      outputs: {
        datum: {
          hash: {
            _eq: "923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec"
          }
        }
      }
    }
  ) {
    outputs {
      datum {
        bytes
      }
    }
  }
  redeemers(
    limit: 1
    where: {
      datum: {
        hash: {
          _eq: "923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec"
        }
      }
    }
  ) {
    datum {
      bytes
    }
  }
  collateralOutputs(
    limit: 1
    where: {
      datum: {
        hash: {
          _eq: "923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec"
        }
      }
    }
  ) {
    datum {
      bytes
    }
  }
}

And it returns this:

{
  "data": {
    "transactions": [
      {
        "outputs": [
          {
            "datum": null
          },
          {
            "datum": {
              "bytes": "d87980"
            }
          },
          {
            "datum": {
              "bytes": "d87980"
            }
          }
        ]
      }
    ],
    "redeemers": [
      {
        "datum": {
          "bytes": "d87980"
        }
      }
    ],
    "collateralOutputs": []
  }
}

And then locally in lucid you try to find in any of these a datum where bytes is defined.

EDIT:
Trying to query from hash 0298f8f6c4731e0aad27c90421920148cc16c2e800fdf1cd13e548409b25d548 doesn't even yield a result. GraphQL is weird.

@zachyking
Copy link
Contributor Author

There is a datum object on utxos, which has bytes and hash. I can have where condition using hash, but not bytes. This means instead of getting only one valid record if exists where bytes are not null, I have to get all, and try finding not null bytes in lucid.
Thinking about it, it for sure needs a change to go over query { transactions(where: { outputs: { datum: { hash: { _eq: so it includes spent utxo data, and that way it should work.
But querying for datum hashes that are common will be returning a lot of results that we don't need (until it's possible to add condition to the graphql query bytes !== null, then we can add limit=1)

@zachyking so you think it could be a problem because you also query other utxos where datum is not set and you can't filter them out? But the query you suggested there seems sufficient enough I think. Maybe it's not really a problem with all these outputs, but I'm not a graphql user really. What happens if you do {datum: {hash: {_neq: null}}}. Would this not remove outputs with no datum?

Maybe you want to extend the query to also redeemers and collateralOutputs, just to really capture all datums:

query queryDatum {
  transactions(
    limit: 1
    where: {
      outputs: {
        datum: {
          hash: {
            _eq: "923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec"
          }
        }
      }
    }
  ) {
    outputs {
      datum {
        bytes
      }
    }
  }
  redeemers(
    limit: 1
    where: {
      datum: {
        hash: {
          _eq: "923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec"
        }
      }
    }
  ) {
    datum {
      bytes
    }
  }
  collateralOutputs(
    limit: 1
    where: {
      datum: {
        hash: {
          _eq: "923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec"
        }
      }
    }
  ) {
    datum {
      bytes
    }
  }
}

And it returns this:

{
  "data": {
    "transactions": [
      {
        "outputs": [
          {
            "datum": null
          },
          {
            "datum": {
              "bytes": "d87980"
            }
          },
          {
            "datum": {
              "bytes": "d87980"
            }
          }
        ]
      }
    ],
    "redeemers": [
      {
        "datum": {
          "bytes": "d87980"
        }
      }
    ],
    "collateralOutputs": []
  }
}

And then locally in lucid you try to find in any of these a datum where bytes is defined.

EDIT: Trying to query from hash 0298f8f6c4731e0aad27c90421920148cc16c2e800fdf1cd13e548409b25d548 doesn't even yield a result. GraphQL is weird.

Extending to redeemers and collateral outputs is definitely good idea. I don't think limit=1 should be used in transaction query until condition bytes: { _neq: null is possible.

Hash 0298f8f6c4731e0aad27c90421920148cc16c2e800fdf1cd13e548409b25d548 is definitely interesting, returning result from Blockfrost... I'll check BF implementation when I have a chance, maybe it will help figuring it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants