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: bitcoin #568

Merged
merged 21 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions advanced/dapps/react-dapp-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@walletconnect/utils": "2.13.0",
"@web3modal/standalone": "2.4.3",
"axios": "^1.0.0",
"bitcoinjs-message": "^2.2.0",
"blockies-ts": "^1.0.0",
"bs58": "^5.0.0",
"cosmos-wallet": "^1.2.0",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions advanced/dapps/react-dapp-v2/src/chains/bip122.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { NamespaceMetadata, ChainMetadata, ChainsMap } from "../helpers";

export const BtcChainData: ChainsMap = {
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943": {
id: "bip122:000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
name: "BTC Signet",
rpc: ["https://api.devnet.solana.com"],
ganchoradkov marked this conversation as resolved.
Show resolved Hide resolved
slip44: 501,
testnet: true,
},
};

export const BtcMetadata: NamespaceMetadata = {
// Solana Devnet
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943": {
logo: "/assets/btc-testnet.png",
rgb: "247, 147, 25",
},
};

export function getChainMetadata(chainId: string): ChainMetadata {
const reference = chainId.split(":")[1];
const metadata = BtcMetadata[reference];
if (typeof metadata === "undefined") {
throw new Error(`No chain metadata found for chainId: ${chainId}`);
}
return metadata;
}
3 changes: 3 additions & 0 deletions advanced/dapps/react-dapp-v2/src/chains/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as multiversx from "./multiversx";
import * as tron from "./tron";
import * as tezos from "./tezos";
import * as kadena from "./kadena";
import * as bip122 from "./bip122";

import { ChainMetadata, ChainRequestRender } from "../helpers";

Expand All @@ -33,6 +34,8 @@ export function getChainMetadata(chainId: string): ChainMetadata {
return tron.getChainMetadata(chainId);
case "tezos":
return tezos.getChainMetadata(chainId);
case "bip122":
return bip122.getChainMetadata(chainId);
default:
throw new Error(`No metadata handler for namespace ${namespace}`);
}
Expand Down
5 changes: 5 additions & 0 deletions advanced/dapps/react-dapp-v2/src/components/Asset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { getChainMetadata } from "../chains";
const xdaiLogo = getChainMetadata("eip155:100").logo;
const maticLogo = getChainMetadata("eip155:137").logo;
const kadenaLogo = getChainMetadata("kadena:testnet04").logo;
const btcLogo = getChainMetadata(
"bip122:000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
).logo;

const SAsset = styled.div`
width: 100%;
Expand Down Expand Up @@ -48,6 +51,8 @@ function getAssetIcon(asset: AssetData): JSX.Element {
return <Icon src={maticLogo} />;
case "kda":
return <Icon src={kadenaLogo} />;
case "btc":
return <Icon src={btcLogo} />;
default:
return <Icon src={"/assets/eth20.svg"} />;
}
Expand Down
10 changes: 10 additions & 0 deletions advanced/dapps/react-dapp-v2/src/constants/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const DEFAULT_TEST_CHAINS = [
"tron:0xcd8690dc",
"tezos:testnet",
"kadena:testnet04",
"bip122:000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
];

export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS];
Expand Down Expand Up @@ -224,6 +225,15 @@ export enum DEFAULT_KADENA_METHODS {
}

export enum DEFAULT_KADENA_EVENTS {}
/**
* BITCOIN
*/
export enum DEFAULT_BIP122_METHODS {
BIP122_SIGN_MESSAGE = "btc_signMessage",
BIP122_SEND_TRANSACTION = "btc_sendTransaction",
}

export enum DEFAULT_BIP122_EVENTS {}

export const REGIONALIZED_RELAYER_ENDPOINTS: RelayerType[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CosmosChainData } from "../chains/cosmos";
import { EIP155ChainData } from "../chains/eip155";
import { TezosChainData } from "../chains/tezos";
import { KadenaChainData } from "../chains/kadena";
import { BtcChainData } from "../chains/bip122";

/**
* Types
Expand All @@ -41,6 +42,7 @@ export function ChainDataContextProvider({

const loadChainData = async () => {
const namespaces = getAllChainNamespaces();
console.log("namespaces", namespaces);
const chainData: ChainNamespaces = {};
await Promise.all(
namespaces.map(async (namespace) => {
Expand Down Expand Up @@ -73,6 +75,10 @@ export function ChainDataContextProvider({
case "kadena":
chains = KadenaChainData;
break;
case "bip122":
console.log("BIP122", BtcChainData);
chains = BtcChainData;
break;
default:
console.error("Unknown chain namespace: ", namespace);
}
Expand Down
94 changes: 94 additions & 0 deletions advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
createWalletConnectSign,
} from "@kadena/client";
import { PactNumber } from "@kadena/pactjs";
import BitcoinMessage from "bitcoinjs-message";
import {
KadenaAccount,
eip712,
Expand Down Expand Up @@ -54,6 +55,7 @@ import {
SendCallsParams,
GetCapabilitiesResult,
GetCallsResult,
DEFAULT_BIP122_METHODS,
} from "../constants";
import { useChainData } from "./ChainDataContext";
import { rpcProvidersByChainId } from "../../src/helpers/api";
Expand All @@ -68,6 +70,10 @@ import {
import { UserVerifier } from "@multiversx/sdk-wallet/out/userVerifier";
import { SignClient } from "@walletconnect/sign-client/dist/types/client";
import { parseEther } from "ethers/lib/utils";
import {
apiGetAddressUtxos,
getAvailableBalanceFromUtxos,
} from "../helpers/bip122";

/**
* Types
Expand Down Expand Up @@ -133,6 +139,10 @@ interface IContext {
testSign: TRpcRequestCallback;
testQuicksign: TRpcRequestCallback;
};
bip122Rpc: {
testSignMessage: TRpcRequestCallback;
testSendTransaction: TRpcRequestCallback;
};
rpcResult?: IFormattedRpcResponse | null;
isRpcRequestPending: boolean;
isTestnet: boolean;
Expand Down Expand Up @@ -1621,6 +1631,89 @@ export function JsonRpcContextProvider({
),
};

const bip122Rpc = {
testSignMessage: _createJsonRpcRequestHandler(
async (
chainId: string,
address: string
): Promise<IFormattedRpcResponse> => {
const method = DEFAULT_BIP122_METHODS.BIP122_SIGN_MESSAGE;
console.log({
chainId,
address,
method,
});
const message = "This is a message to be signed for BIP122";
const result = await client!.request<{
signature: string;
segwitType: string;
}>({
topic: session!.topic,
chainId: chainId,
request: {
method,
params: [message, address],
},
});
console.log(result, result.signature);
const checkSegwitAlways =
result.segwitType === ("p2wpkh" || "p2sh(p2wpkh)");
return {
method,
address: address,
valid: BitcoinMessage.verify(
message,
address,
result.signature,
undefined,
checkSegwitAlways
),
result: `
signature: ${result.signature}\n
segwitType: ${result.segwitType}
`,
};
}
),
testSendTransaction: _createJsonRpcRequestHandler(
async (
chainId: string,
address: string
): Promise<IFormattedRpcResponse> => {
const method = DEFAULT_BIP122_METHODS.BIP122_SEND_TRANSACTION;

const utxos = await apiGetAddressUtxos(address, chainId);
const availableBalance = getAvailableBalanceFromUtxos(utxos); // in satoshis
console.log({
chainId,
address,
method,
availableBalance,
});

const result = await client!.request<any>({
topic: session!.topic,
chainId: chainId,
request: {
method,
params: {
address,
value: availableBalance,
transactionType: "p2wpkh",
},
},
});

return {
method,
address: address,
valid: true,
result: result,
};
}
),
};

return (
<JsonRpcContext.Provider
value={{
Expand All @@ -1638,6 +1731,7 @@ export function JsonRpcContextProvider({
isRpcRequestPending: pending,
isTestnet,
setIsTestnet,
bip122Rpc,
}}
>
{children}
Expand Down
5 changes: 5 additions & 0 deletions advanced/dapps/react-dapp-v2/src/helpers/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { apiGetKadenaAccountBalance } from "./kadena";

import { AssetData } from "./types";
import { PactCommand } from "@kadena/client";
import { apiGetBip122AccountBalance } from "./bip122";

export type RpcProvidersByChainId = Record<
number,
Expand Down Expand Up @@ -155,6 +156,10 @@ export async function apiGetAccountBalance(
);
}

if (namespace === "bip122") {
return apiGetBip122AccountBalance(address, networkId as string);
}

if (namespace !== "eip155") {
return { balance: "", symbol: "", name: "" };
}
Expand Down
25 changes: 25 additions & 0 deletions advanced/dapps/react-dapp-v2/src/helpers/bip122.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export async function apiGetBip122AccountBalance(
address: string,
chainId: string
) {
const utxo = await apiGetAddressUtxos(address, chainId);
const balanceInSatoshis = getAvailableBalanceFromUtxos(utxo);
const balanceInBtc = balanceInSatoshis * 0.00000001;

console.log("utxo for address", address, utxo);
console.log("balance", balanceInBtc);
return { balance: balanceInBtc.toString(), symbol: "BTC", name: "BTC" };
}

export async function apiGetAddressUtxos(address: string, chainId: string) {
return await (
await fetch(`https://mempool.space/signet/api/address/${address}/utxo`)
).json();
}

export function getAvailableBalanceFromUtxos(utxos: any[]) {
ganchoradkov marked this conversation as resolved.
Show resolved Hide resolved
if (!utxos || !utxos.length) {
return 0;
}
return utxos.reduce((acc, { value }) => acc + value, 0);
}
7 changes: 7 additions & 0 deletions advanced/dapps/react-dapp-v2/src/helpers/namespaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
DEFAULT_TEZOS_METHODS,
DEFAULT_TEZOS_EVENTS,
DEFAULT_OPTIONAL_METHODS,
DEFAULT_BIP122_METHODS,
DEFAULT_BIP122_EVENTS,
} from "../constants";

export const getNamespacesFromChains = (chains: string[]) => {
Expand Down Expand Up @@ -53,6 +55,8 @@ export const getSupportedRequiredMethodsByNamespace = (namespace: string) => {
return Object.values(DEFAULT_TEZOS_METHODS);
case "kadena":
return Object.values(DEFAULT_KADENA_METHODS);
case "bip122":
return Object.values(DEFAULT_BIP122_METHODS);
default:
throw new Error(
`No default required methods for namespace: ${namespace}`
Expand All @@ -72,6 +76,7 @@ export const getSupportedOptionalMethodsByNamespace = (namespace: string) => {
case "tron":
case "tezos":
case "kadena":
case "bip122":
return [];
default:
throw new Error(
Expand Down Expand Up @@ -100,6 +105,8 @@ export const getSupportedEventsByNamespace = (namespace: string) => {
return Object.values(DEFAULT_TEZOS_EVENTS);
case "kadena":
return Object.values(DEFAULT_KADENA_EVENTS);
case "bip122":
return Object.values(DEFAULT_BIP122_EVENTS);
default:
throw new Error(`No default events for namespace: ${namespace}`);
}
Expand Down
6 changes: 5 additions & 1 deletion advanced/dapps/react-dapp-v2/src/helpers/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,11 @@ export const toWad = (amount: string, decimals = 18): BigNumber => {
};

export const fromWad = (wad: BigNumberish, decimals = 18): string => {
return sanitizeDecimals(utils.formatUnits(wad, decimals), decimals);
try {
return sanitizeDecimals(utils.formatUnits(wad, decimals), decimals);
} catch (e) {
return wad?.toString();
}
};

export const LOCALSTORAGE_KEY_TESTNET = "TESTNET";
Expand Down
Loading
Loading