Skip to content

Commit 510b988

Browse files
Initial commit
0 parents  commit 510b988

27 files changed

Lines changed: 7237 additions & 0 deletions

.env.template

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
BLOCKFROST_PROJECT_ID_TESTNET=testnetMySecretBlockFrostProjectId
2+
BLOCKFROST_PROJECT_ID_MAINNET=mainnetMySecretBlockFrostProjectId

.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

.github/listing_assets.png

42.2 KB
Loading
50.3 KB
Loading

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env
30+
.env*.local
31+
32+
# vercel
33+
.vercel
34+
35+
# typescript
36+
*.tsbuildinfo
37+
next-env.d.ts

LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2022 Alan Smithee
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Lucid Blockfrost Proxy Example
2+
3+
## DISCLAIMER
4+
5+
Always be careful when interacting with the blockchain. Misuse can result in a loss of funds. This is only a technical example, and should be treated as such, I don't take any responsibility for any damage it might cause.
6+
7+
8+
## Examples
9+
10+
### Processing a transaction
11+
12+
![processing_a_transaction](.github/processing_a_transaction.png)
13+
14+
### Listing assets
15+
16+
![listing_assets](.github/listing_assets.png)
17+
18+
## Explanation
19+
20+
This is an example repo for using a proxy for the Lucid Blockfrost provider, so that the Project Id is not leaked to the client, as discussed in [this github issue](https://github.com/spacebudz/lucid/issues/25).
21+
22+
The example itself allows you to send a transaction over either testnet or mainnet.
23+
24+
This repo uses `next.js` and React. The code itself is also quite React-ish, building mainly on custom hooks. I don't necessarily suggest building your code like this, but since I have to deal with network changes, this was a suitable pattern.
25+
26+
## Structure
27+
28+
This is a standard next.js project, with only one page; `pages/index.tsx`. All the important code is located in the `hooks` folder.
29+
30+
## Features
31+
32+
- Forwards all blockfrost requests via the `pages/api/[[...all]].ts` routes, making it possible to hide your blockfrost keys
33+
- The keys are injected via env variables (see the `.env.template` file)
34+
- Handles both `mainnet` and `testnet`
35+
- Listens to network changes using the `experiments` api that - in this case - nami injects
36+
37+
## Limitations / Considerations
38+
39+
Since the Lucid package doesn't seamlessly support network changes, I had to keep track of it in states. Not sure I like this pattern, but it is what it is. Not sure if there are any implications to doing it this way.
40+
41+
The proxy API might not be 100% compatible with the blockfrost API. IE. if there are any unforeseen issues, the API will return an empty 400 response, which the Lucid Blockfrost provider might be incompatible with. I will research this more. Improvements are welcome.
42+
43+
## Contributing
44+
45+
Just send an issue and/or PR my way if you have improvement suggestions. Much welcome.
46+
47+
# License
48+
49+
MIT, see LICENSE file.

hooks/use-assets.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { sortBy } from "lodash"
2+
import { fromUnit, Lucid } from "lucid-cardano"
3+
import { useCallback, useEffect, useState } from "react"
4+
5+
import { Responses } from "@blockfrost/blockfrost-js"
6+
7+
type Value = ReturnType<typeof fromUnit>
8+
9+
interface ValueWithName extends Omit<Value, "name"> {
10+
value: number
11+
name: string
12+
}
13+
14+
const useAssets = (lucid?: Lucid, networkId?: number) => {
15+
const [assets, setAssets] = useState<Responses["asset"][]>([])
16+
const [lovelace, setLovelace] = useState(0)
17+
18+
const fetchAssets = useCallback(async () => {
19+
if (!lucid?.wallet) return
20+
21+
const utxos = await lucid.wallet.getUtxos()
22+
23+
const allUtxos = utxos
24+
.map((u) => Object.keys(u.assets).map((key) => ({ key, value: u.assets[key] })))
25+
.reduce((acc, curr) => [...acc, ...curr], [])
26+
.map((a) => ({
27+
...fromUnit(a.key),
28+
value: Number(a.value),
29+
}))
30+
31+
const lovelaces = allUtxos
32+
.filter((u) => u.policyId === "lovelace")
33+
.reduce((acc, curr) => acc + curr.value, 0)
34+
35+
const utxoAssets = allUtxos
36+
.filter((u) => u.policyId !== "lovelace")
37+
.filter((v: Value): v is ValueWithName => v.name !== null)
38+
.map((a) => ({
39+
...a,
40+
fullyQualifiedAssetName: `${a.policyId}${a.name}`,
41+
}))
42+
43+
const assetsWithMetadata: Responses["asset"][] = await Promise.all(
44+
utxoAssets.map((a) =>
45+
fetch(`/api/blockfrost/${networkId}/assets/${a.fullyQualifiedAssetName}`).then((r) =>
46+
r.json()
47+
)
48+
)
49+
)
50+
51+
const sortedAssets = sortBy(
52+
assetsWithMetadata,
53+
(a) => (Number(a.quantity) === 1 ? 1 : -1),
54+
"policy_id",
55+
"metadata.name",
56+
"onchain_metadata.name"
57+
)
58+
59+
setLovelace(lovelaces)
60+
setAssets(sortedAssets)
61+
}, [lucid?.wallet, networkId])
62+
63+
useEffect(() => {
64+
fetchAssets()
65+
}, [fetchAssets])
66+
67+
return {
68+
lovelace,
69+
assets,
70+
}
71+
}
72+
73+
export { useAssets }

hooks/use-has-nami-extension.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useEffect, useState } from 'react';
2+
3+
const useHasNamiExtension = () => {
4+
const [hasNamiExtension, setHasNamiExtension] = useState<boolean>()
5+
6+
useEffect(() => {
7+
// give the browser a chance to load the extension
8+
// and for it to inject itself into the window object
9+
const timeout = setTimeout(() => {
10+
setHasNamiExtension(!!window.cardano?.nami)
11+
}, 10)
12+
13+
return () => {
14+
clearTimeout(timeout)
15+
}
16+
}, [])
17+
18+
return hasNamiExtension
19+
}
20+
21+
export { useHasNamiExtension }

hooks/use-lucid.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { isNil } from "lodash"
2+
import { Blockfrost, Lucid } from "lucid-cardano"
3+
import { useCallback, useEffect, useState } from "react"
4+
5+
import { useNetworkId } from "./use-network-id"
6+
import { useWalletApi } from "./use-wallet-api"
7+
8+
const useLucid = () => {
9+
const [lucid, setLucid] = useState<Lucid>()
10+
const walletApi = useWalletApi()
11+
const networkId = useNetworkId(walletApi)
12+
13+
const initializeLucid = useCallback(async () => {
14+
if (isNil(networkId) || isNil(walletApi)) return
15+
16+
const provider = new Blockfrost(`/api/blockfrost/${networkId}`)
17+
const network = networkId === 0 ? "Testnet" : "Mainnet"
18+
19+
const updatedLucid = await (isNil(lucid)
20+
? Lucid.new(provider, network)
21+
: lucid.switchProvider(provider, network))
22+
23+
const lucidWithWallet = updatedLucid.selectWallet(walletApi)
24+
25+
setLucid(lucidWithWallet)
26+
}, [lucid, networkId, walletApi])
27+
28+
useEffect(() => {
29+
initializeLucid()
30+
}, [initializeLucid])
31+
32+
return {
33+
networkId,
34+
walletApi,
35+
lucid,
36+
}
37+
}
38+
39+
export { useLucid }

0 commit comments

Comments
 (0)