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

feat(QUI-115): UTXO support in SDK #62

Merged
merged 11 commits into from
Nov 30, 2024
Merged
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ rusqlite = { version = "0.32.1", features = ["serde_json"] }
secp256k1 = { version = "0.29.1", features = ["recovery", "rand", "global-context"] }
serde = { version = "1.0.208", features = ["derive"] }
serde_json = "1.0.125"
serde_with = "3.11.0"
sha3 = "0.10.8"
surrealdb = { version = "1.5.4" }
tokio = { version = "1.39.2", features = ["macros", "rt", "rt-multi-thread", "time"] }
Expand Down
16 changes: 0 additions & 16 deletions sdk/apps/nft-minting-example/contracts/MyNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,8 @@ pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@quible/verifier-solidity-sdk/contracts/QuibleVerifier.sol";

function bytesToHexString(bytes memory data) pure returns (string memory) {
bytes memory converted = new bytes(data.length * 2);

bytes memory _base = "0123456789abcdef";

for (uint256 i = 0; i < data.length; i++) {
converted[i * 2] = _base[uint8(data[i]) / _base.length];
converted[i * 2 + 1] = _base[uint8(data[i]) % _base.length];
}

return string(abi.encodePacked("0x", converted));
}

contract MyNFT is ERC721, ERC721Enumerable, Ownable {
uint256 private _nextTokenId;
bytes32 public quirkleRoot;
Expand Down
7 changes: 5 additions & 2 deletions sdk/apps/nft-minting-example/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
experimental: {
esmExternals: 'loose',
},
}

module.exports = nextConfig;
module.exports = nextConfig
84 changes: 48 additions & 36 deletions sdk/apps/nft-minting-example/src/components/LaunchToken.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,67 @@
import { useCallback, useState } from "react";
import { waitForTransactionReceipt } from "@wagmi/core";
import { useDeployContract, useConfig, useSignMessage } from "wagmi";
import MyNFTArtifacts from "../../artifacts/contracts/MyNFT.sol/MyNFT.json";
import Link from "next/link";
import ReactEditList, * as REL from "react-edit-list";
import { QuibleProvider } from '@quible/js-sdk';
import { useCallback, useState } from 'react'
import { waitForTransactionReceipt } from '@wagmi/core'
import { useDeployContract, useConfig, useSignMessage } from 'wagmi'
import MyNFTArtifacts from '../../artifacts/contracts/MyNFT.sol/MyNFT.json'
import Link from 'next/link'
import ReactEditList, * as REL from 'react-edit-list'
import { QuibleSigner, QuibleProvider } from '@quible/js-sdk'
import { convertHexStringToUint8Array } from '@quible/js-sdk/lib/utils'

const quibleProvider = new QuibleProvider('http://localhost:9013')

const schema: REL.Schema = [
{ name: "id", type: "id" },
{ name: "address", type: "string" },
];
{ name: 'id', type: 'id' },
{ name: 'address', type: 'string' },
]

const LaunchToken = (props: {
accountAddress: string;
}) => {
const [contractAddress, setContractAddress] = useState<string | null>(null);
const [isPending, setIsPending] = useState(false);
const [accessList, setAccessList] = useState<string[]>([props.accountAddress]);
const config = useConfig();
const LaunchToken = (props: { accountAddress: string }) => {
const [contractAddress, setContractAddress] = useState<string | null>(null)
const [isPending, setIsPending] = useState(false)
const [accessList, setAccessList] = useState<string[]>([props.accountAddress])
const config = useConfig()

const { signMessageAsync } = useSignMessage()
const { deployContractAsync } = useDeployContract();
const { deployContractAsync } = useDeployContract()

const handleDeployContract = useCallback(async () => {
setIsPending(true);
const wallet = quibleProvider.getWallet(props.accountAddress);
setIsPending(true)
const signer = QuibleSigner.fromAddress(
{ raw: props.accountAddress },
(message) =>
signMessageAsync({ message: { raw: message } }).then(
convertHexStringToUint8Array,
),
)

const quirkleRoot = await wallet.createQuirkle({
members: accessList,
proofTtl: 86400,
signMessage: (message) => signMessageAsync({message: { raw: message }})
const wallet = quibleProvider.getWallet(signer)

const identity = await wallet.createIdentity({
claims: accessList,
certificateLifespan: 86400,
})

const hash = await deployContractAsync({
abi: MyNFTArtifacts.abi,
bytecode: MyNFTArtifacts.bytecode as unknown as `0x${string}`,
args: [props.accountAddress, `0x${quirkleRoot.toHex()}`],
});
args: [props.accountAddress, identity.id.toHexString()],
})

const { contractAddress: newContractAddress } =
await waitForTransactionReceipt(config, { hash });
await waitForTransactionReceipt(config, { hash })

setContractAddress(newContractAddress as unknown as string);
setIsPending(false);
}, [props.accountAddress, accessList, config, signMessageAsync, deployContractAsync]);
setContractAddress(newContractAddress as unknown as string)
setIsPending(false)
}, [
props.accountAddress,
accessList,
config,
signMessageAsync,
deployContractAsync,
])

const handleAccessListChange = (list: REL.Row[]) => {
setAccessList(list.map((row) => row.id as string));
};
setAccessList(list.map((row) => row.id as string))
}

return (
<div>
Expand All @@ -71,7 +83,7 @@ const LaunchToken = (props: {
<div>
<Link
href={`/tokens/${contractAddress}`}
style={{ textDecoration: "underline", color: "blue" }}
style={{ textDecoration: 'underline', color: 'blue' }}
>
Contract deployed at <code>{contractAddress}</code>
</Link>
Expand All @@ -80,7 +92,7 @@ const LaunchToken = (props: {
</>
)}
</div>
);
};
)
}

export default LaunchToken;
export default LaunchToken
76 changes: 43 additions & 33 deletions sdk/apps/nft-minting-example/src/components/Minting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,86 @@ import { useCallback } from 'react'
import { waitForTransactionReceipt, readContract } from '@wagmi/core'
import { useReadContract, useWriteContract, useConfig } from 'wagmi'
import MyNFTArtifacts from '../../artifacts/contracts/MyNFT.sol/MyNFT.json'
import { convertHexStringToUint8Array } from '@quible/js-sdk/lib/utils'

const Minting = (props: { accountAddress: string, tokenAddress: string }) => {
const Minting = (props: { accountAddress: string; tokenAddress: string }) => {
const config = useConfig()
const { data: hash, writeContractAsync } = useWriteContract()

const { data, isSuccess, refetch } = useReadContract({
abi: MyNFTArtifacts.abi,
address: props.tokenAddress as unknown as `0x${string}`,
functionName: 'balanceOf',
args: [props.accountAddress]
args: [props.accountAddress],
})

const handleMint = useCallback(async () => {
console.log('querying quirkle root', props.tokenAddress);
console.log('querying quirkle root', props.tokenAddress)
const quirkleRoot = await readContract(config, {
abi: MyNFTArtifacts.abi,
address: props.tokenAddress as `0x${string}`,
functionName: 'getQuirkleRoot'
functionName: 'getQuirkleRoot',
})

console.log('got quirkle root', quirkleRoot);
console.log('got quirkle root', quirkleRoot)

const response = await fetch(
'http://localhost:9013',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'quible_requestProof',
id: 67,
params: [quirkleRoot, props.accountAddress.toLowerCase(), 0]
})
}
)
const response = await fetch('http://localhost:9013', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'quible_requestCertificate',
id: 67,
params: [
[...convertHexStringToUint8Array(quirkleRoot as string)],
[...convertHexStringToUint8Array(props.accountAddress.toLowerCase())],
],
}),
})

const body = await response.json();
const body = await response.json()

console.log('got body', body);
console.log('got body', body)

if (body.error) {
throw new Error(JSON.stringify(body.error));
throw new Error(JSON.stringify(body.error))
}

const {signature, expires_at} = body.result;
const {
signature,
details: { expires_at },
} = body.result

const hash = await writeContractAsync({
abi: MyNFTArtifacts.abi,
address: props.tokenAddress as unknown as `0x${string}`,
functionName: 'safeMint',
args: [props.accountAddress, expires_at, `0x${signature}`]
args: [props.accountAddress, BigInt(expires_at), `0x${signature}`],
})

await waitForTransactionReceipt(config, { hash })
refetch()
}, [props.accountAddress, props.tokenAddress, config, refetch, writeContractAsync]);
}, [
props.accountAddress,
props.tokenAddress,
config,
refetch,
writeContractAsync,
])

if (!isSuccess) { return <div>Loading...</div> }
if (!isSuccess) {
return <div>Loading...</div>
}

return (
<div>
<button onClick={handleMint}>Mint</button>
<p>
total NFT count: {`${data}`}
</p>
<p>total NFT count: {`${data}`}</p>
{hash && <p>Transaction hash: {hash}</p>}
</div>
);
};
)
}

export default Minting;
export default Minting
Loading