@lombard.finance/sdk 2.0.7 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ import { FormControl, InputLabel, MenuItem, Select } from '@mui/material';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { useState } from 'react';
4
+ import { OChainId, OEnv } from '../../common/types/types';
5
+ import { Button } from '../../stories/components/Button';
6
+ import { CodeBlock } from '../../stories/components/CodeBlock';
7
+ import { useConnect } from '../../stories/hooks/useConnect';
8
+ import useQuery from '../../stories/hooks/useQuery';
9
+ import { signStakeAndBake } from '../../web3Sdk/signStakeAndBake/signStakeAndBake';
10
+ import {
11
+ IStoreStakeAndBakeSignatureParams,
12
+ storeStakeAndBakeSignature,
13
+ } from './storeStakeAndBakeSignature';
14
+
15
+ const EXPIRY_OPTIONS = {
16
+ '10 seconds': 10,
17
+ '1 minute': 60,
18
+ '1 hour': 3600,
19
+ '1 day': 86400,
20
+ '1 year': 31536000,
21
+ } as const;
22
+
23
+ type ExpiryOption = keyof typeof EXPIRY_OPTIONS;
24
+
25
+ const meta = {
26
+ title: 'SDK/storeStakeAndBakeSignature',
27
+ component: StoryView,
28
+ tags: ['autodocs'],
29
+ } satisfies Meta<typeof StoryView>;
30
+
31
+ export default meta;
32
+
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const WithParams: Story = {
36
+ args: {
37
+ env: OEnv.stage,
38
+ },
39
+ };
40
+
41
+ type StoreStakeAndBakeSignatureProps = Pick<
42
+ IStoreStakeAndBakeSignatureParams,
43
+ 'env'
44
+ >;
45
+
46
+ export function StoryView(props: StoreStakeAndBakeSignatureProps) {
47
+ const [selectedExpiry, setSelectedExpiry] =
48
+ useState<ExpiryOption>('10 seconds');
49
+
50
+ const {
51
+ data: connectData,
52
+ error: connectError,
53
+ isLoading: isConnectLoading,
54
+ connect,
55
+ } = useConnect();
56
+
57
+ const request = async () => {
58
+ if (!connectData || !connectData.provider) {
59
+ return;
60
+ }
61
+
62
+ const chainId = OChainId.holesky;
63
+ const expiryDate =
64
+ Math.floor(Date.now() / 1000) + EXPIRY_OPTIONS[selectedExpiry];
65
+
66
+ const { signature, typedData } = await signStakeAndBake({
67
+ provider: connectData.provider,
68
+ address: connectData.account,
69
+ chainId,
70
+ env: props.env,
71
+ value: '1999',
72
+ expiryDate,
73
+ });
74
+
75
+ return storeStakeAndBakeSignature({
76
+ ...props,
77
+ signature,
78
+ typedData,
79
+ });
80
+ };
81
+
82
+ const { data, error, isLoading, refetch } = useQuery(
83
+ request,
84
+ [selectedExpiry],
85
+ false,
86
+ );
87
+
88
+ const formattedConnectData = connectData && {
89
+ account: connectData.account,
90
+ chainId: connectData.chainId,
91
+ };
92
+
93
+ return (
94
+ <>
95
+ <p>
96
+ This method stores the stake and bake signature in the backend. The
97
+ signature is used to approve spending of tokens.
98
+ </p>
99
+
100
+ <div className="mb-4">
101
+ <Button
102
+ onClick={connect}
103
+ disabled={isConnectLoading}
104
+ isLoading={isConnectLoading}
105
+ >
106
+ Connect
107
+ </Button>
108
+
109
+ <CodeBlock text={connectError || formattedConnectData} />
110
+ </div>
111
+
112
+ <div className="mb-4">
113
+ <FormControl fullWidth>
114
+ <InputLabel id="expiry-select-label">Expiry Time</InputLabel>
115
+ <Select
116
+ labelId="expiry-select-label"
117
+ value={selectedExpiry}
118
+ label="Expiry Time"
119
+ onChange={e => setSelectedExpiry(e.target.value as ExpiryOption)}
120
+ >
121
+ {Object.keys(EXPIRY_OPTIONS).map(option => (
122
+ <MenuItem key={option} value={option}>
123
+ {option}
124
+ </MenuItem>
125
+ ))}
126
+ </Select>
127
+ </FormControl>
128
+ </div>
129
+
130
+ <Button
131
+ onClick={refetch}
132
+ disabled={
133
+ isLoading || !connectData || connectData.chainId !== OChainId.holesky
134
+ }
135
+ isLoading={isLoading}
136
+ >
137
+ Store Stake and Bake Signature
138
+ </Button>
139
+
140
+ <CodeBlock text={error || data} />
141
+ </>
142
+ );
143
+ }
@@ -0,0 +1,56 @@
1
+ import axios from 'axios';
2
+ import { IEnvParam } from '../../common/types/internalTypes';
3
+ import { getErrorMessage } from '../../common/utils/getErrorMessage';
4
+ import { getApiConfig } from '../apiConfig';
5
+
6
+ export type IStoreStakeAndBakeSignatureStatus = 'success';
7
+
8
+ interface IStoreStakeAndBakeSignatureResponse {
9
+ status: IStoreStakeAndBakeSignatureStatus;
10
+ }
11
+
12
+ export interface IStoreStakeAndBakeSignatureParams extends IEnvParam {
13
+ /**
14
+ * signature
15
+ */
16
+ signature: string;
17
+ /**
18
+ * JSON typed data used for the signature
19
+ */
20
+ typedData: string;
21
+ }
22
+
23
+ /**
24
+ * Store stake and bake signature
25
+ *
26
+ * @param {IStoreStakeAndBakeSignatureParams} params - The parameters for storing stake and bake signature
27
+ *
28
+ * @returns {Promise<IStoreStakeAndBakeSignatureStatus>} Response promise with status
29
+ *
30
+ */
31
+ export async function storeStakeAndBakeSignature({
32
+ signature,
33
+ typedData,
34
+ env,
35
+ }: IStoreStakeAndBakeSignatureParams): Promise<IStoreStakeAndBakeSignatureStatus> {
36
+ const { baseApiUrl } = getApiConfig(env);
37
+
38
+ try {
39
+ const { data } = await axios.post<IStoreStakeAndBakeSignatureResponse>(
40
+ `${baseApiUrl}/api/v1/claimer/save-stake-and-bake-signature`,
41
+ null,
42
+ {
43
+ params: {
44
+ typed_data: typedData,
45
+ signature,
46
+ },
47
+ },
48
+ );
49
+
50
+ return data.status;
51
+ } catch (error) {
52
+ const errorMsg = getErrorMessage(error);
53
+
54
+ throw new Error(errorMsg);
55
+ }
56
+ }
@@ -16,9 +16,9 @@ export default meta;
16
16
 
17
17
  type Story = StoryObj<typeof meta>;
18
18
 
19
- export const Sepolia: Story = {
19
+ export const Holesky: Story = {
20
20
  args: {
21
- chainId: OChainId.sepolia,
21
+ chainId: OChainId.holesky,
22
22
  bakeGasEstimate: 77,
23
23
  },
24
24
  };
@@ -1,15 +1,6 @@
1
- export * from '../sdk/getNetworkFeeSignature';
2
- export * from '../sdk/storeNetworkFeeSignature';
3
- export * from './approveLBTC';
4
- export * from './claimLBTC';
5
- export * from './getBasculeDepositStatus';
6
1
  export * from './getLBTCMintingFee';
7
- export * from './getLBTCTotalSupply';
8
- export * from './getPermitNonce';
9
- export * from './lbtcAddressConfig';
10
- export * from './lbtcOFTAdapterAddressConfig';
11
2
  export * from './signLbtcDestionationAddr';
12
3
  export * from './signNetworkFee';
13
- export * from './types';
14
- export * from './unstakeLBTC';
4
+ export * from './signStakeAndBake';
5
+ export * from './signStakeAndBake/config';
15
6
 
@@ -0,0 +1,13 @@
1
+ import { OChainId, TChainId } from '../../common/types/types';
2
+
3
+ export const STAKE_AND_BAKE_SPENDER_ADDRESSES: Record<number, string> = {
4
+ [OChainId.holesky]: '0x52BD640617eeD47A00dA0da93351092D49208d1d',
5
+ };
6
+
7
+ export const getStakeAndBakeSpenderAddress = (chainId: TChainId): string => {
8
+ const address = STAKE_AND_BAKE_SPENDER_ADDRESSES[chainId];
9
+ if (!address) {
10
+ throw new Error(`No spender address configured for chain ID ${chainId}`);
11
+ }
12
+ return address;
13
+ };
@@ -0,0 +1,82 @@
1
+ import { IEnvParam } from '../../common/types/internalTypes';
2
+ import { TChainId } from '../../common/types/types';
3
+ import { getPermitNonce } from '../getPermitNonce';
4
+ import { getLbtcAddressConfig } from '../lbtcAddressConfig';
5
+
6
+ export interface IStakeAndBakeTypedData extends IEnvParam {
7
+ chainId: TChainId;
8
+ expiryDate: number;
9
+ owner: string;
10
+ spender: string;
11
+ value: string;
12
+ rpcUrl?: string;
13
+ }
14
+
15
+ /**
16
+ * Generates EIP-712 typed data for stake and bake signature
17
+ *
18
+ * @param {IStakeAndBakeTypedData} params - Parameters for generating typed data
19
+ * @returns {object} The typed data object conforming to EIP-712
20
+ */
21
+ export async function getStakeAndBakeTypedData({
22
+ chainId,
23
+ expiryDate,
24
+ owner,
25
+ spender,
26
+ value,
27
+ env,
28
+ rpcUrl,
29
+ }: IStakeAndBakeTypedData) {
30
+ const lbtcAddresses = getLbtcAddressConfig(env);
31
+ const verifyingContract = lbtcAddresses[chainId];
32
+
33
+ const nonce = await getPermitNonce({
34
+ owner,
35
+ chainId,
36
+ rpcUrl,
37
+ });
38
+
39
+ return {
40
+ domain: {
41
+ name: 'Lombard Staked Bitcoin',
42
+ version: '1',
43
+ chainId,
44
+ verifyingContract,
45
+ },
46
+ types: {
47
+ EIP712Domain: [
48
+ {
49
+ name: 'name',
50
+ type: 'string',
51
+ },
52
+ {
53
+ name: 'version',
54
+ type: 'string',
55
+ },
56
+ {
57
+ name: 'chainId',
58
+ type: 'uint256',
59
+ },
60
+ {
61
+ name: 'verifyingContract',
62
+ type: 'address',
63
+ },
64
+ ],
65
+ Permit: [
66
+ { name: 'owner', type: 'address' },
67
+ { name: 'spender', type: 'address' },
68
+ { name: 'value', type: 'uint256' },
69
+ { name: 'nonce', type: 'uint256' },
70
+ { name: 'deadline', type: 'uint256' },
71
+ ],
72
+ },
73
+ primaryType: 'Permit',
74
+ message: {
75
+ owner,
76
+ spender,
77
+ value,
78
+ nonce,
79
+ deadline: expiryDate.toString(),
80
+ },
81
+ };
82
+ }
@@ -0,0 +1,2 @@
1
+ export * from './getTypedData';
2
+ export * from './signStakeAndBake';
@@ -0,0 +1,143 @@
1
+ import { FormControl, InputLabel, MenuItem, Select } from '@mui/material';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { useState } from 'react';
4
+ import { OChainId, OEnv } from '../../common/types/types';
5
+ import { Button } from '../../stories/components/Button';
6
+ import { CodeBlock } from '../../stories/components/CodeBlock';
7
+ import { useConnect } from '../../stories/hooks/useConnect';
8
+ import useQuery from '../../stories/hooks/useQuery';
9
+ import { fromCamelCase } from '../../stories/utils/fromCamelCase';
10
+ import { ISignStakeAndBakeParams, signStakeAndBake } from './signStakeAndBake';
11
+
12
+ const { name } = signStakeAndBake;
13
+ const nameWithWhitespaces = fromCamelCase(name);
14
+
15
+ const EXPIRY_OPTIONS = {
16
+ '10 seconds': 10,
17
+ '1 minute': 60,
18
+ '1 hour': 3600,
19
+ '1 day': 86400,
20
+ '1 year': 31536000,
21
+ } as const;
22
+
23
+ type ExpiryOption = keyof typeof EXPIRY_OPTIONS;
24
+
25
+ const meta = {
26
+ title: 'Web3SDK/signStakeAndBake',
27
+ component: StoryView,
28
+ tags: ['autodocs'],
29
+ } satisfies Meta<typeof StoryView>;
30
+
31
+ export default meta;
32
+
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const WithParams: Story = {
36
+ args: {
37
+ env: OEnv.stage,
38
+ },
39
+ };
40
+
41
+ type SignStakeAndBakeProps = Pick<ISignStakeAndBakeParams, 'env'>;
42
+
43
+ export function StoryView(props: SignStakeAndBakeProps) {
44
+ const [selectedExpiry, setSelectedExpiry] =
45
+ useState<ExpiryOption>('10 seconds');
46
+
47
+ const {
48
+ data: connectData,
49
+ error: connectError,
50
+ isLoading: isConnectLoading,
51
+ connect,
52
+ } = useConnect();
53
+
54
+ const request = async () => {
55
+ if (!connectData || !connectData.provider) {
56
+ return;
57
+ }
58
+
59
+ const chainId = OChainId.holesky;
60
+ const expiryDate =
61
+ Math.floor(Date.now() / 1000) + EXPIRY_OPTIONS[selectedExpiry];
62
+
63
+ return signStakeAndBake({
64
+ provider: connectData.provider,
65
+ address: connectData.account,
66
+ chainId,
67
+ env: props.env,
68
+ value: '1999',
69
+ expiryDate,
70
+ });
71
+ };
72
+
73
+ const { data, error, isLoading, refetch } = useQuery(
74
+ request,
75
+ [selectedExpiry],
76
+ false,
77
+ );
78
+
79
+ const formattedConnectData = connectData && {
80
+ account: connectData.account,
81
+ chainId: connectData.chainId,
82
+ };
83
+
84
+ return (
85
+ <>
86
+ <p>
87
+ This method is used to sign a permit for stake and bake operations. The
88
+ signature is used to approve spending of tokens.
89
+ </p>
90
+
91
+ <div className="mb-4">
92
+ <Button
93
+ onClick={connect}
94
+ disabled={isConnectLoading}
95
+ isLoading={isConnectLoading}
96
+ >
97
+ Connect
98
+ </Button>
99
+
100
+ <CodeBlock text={connectError || formattedConnectData} />
101
+ </div>
102
+
103
+ <div className="mb-4">
104
+ <FormControl fullWidth>
105
+ <InputLabel id="expiry-select-label">Expiry Time</InputLabel>
106
+ <Select
107
+ labelId="expiry-select-label"
108
+ value={selectedExpiry}
109
+ label="Expiry Time"
110
+ onChange={e => setSelectedExpiry(e.target.value as ExpiryOption)}
111
+ >
112
+ {Object.keys(EXPIRY_OPTIONS).map(option => (
113
+ <MenuItem key={option} value={option}>
114
+ {option}
115
+ </MenuItem>
116
+ ))}
117
+ </Select>
118
+ </FormControl>
119
+ </div>
120
+
121
+ <Button
122
+ onClick={refetch}
123
+ disabled={
124
+ isLoading || !connectData || connectData.chainId !== OChainId.holesky
125
+ }
126
+ isLoading={isLoading}
127
+ >
128
+ {nameWithWhitespaces}
129
+ </Button>
130
+
131
+ <CodeBlock
132
+ text={
133
+ error ||
134
+ (data && {
135
+ ...data,
136
+ signature: data.signature,
137
+ typedData: data.typedData ? JSON.parse(data.typedData) : '',
138
+ })
139
+ }
140
+ />
141
+ </>
142
+ );
143
+ }
@@ -0,0 +1,99 @@
1
+ import { IEnvParam } from '../../common/types/internalTypes';
2
+ import { TChainId } from '../../common/types/types';
3
+ import { Provider } from '../../provider';
4
+ import { IProviderBasedParams } from '../types';
5
+ import { getStakeAndBakeSpenderAddress } from './config';
6
+ import { getStakeAndBakeTypedData } from './getTypedData';
7
+
8
+ const NO_SIGNATURE_ERROR =
9
+ 'Failed to obtain a valid signature. The response is undefined or invalid.';
10
+
11
+ export interface ISignStakeAndBakeParams
12
+ extends Pick<IProviderBasedParams, 'provider'>,
13
+ IEnvParam {
14
+ /**
15
+ * The address to sign with (owner)
16
+ */
17
+ address: string;
18
+ /**
19
+ * Chain ID for the signature
20
+ */
21
+ chainId: TChainId;
22
+ /**
23
+ * The value to approve
24
+ */
25
+ value: string;
26
+ /**
27
+ * Expiry date in seconds
28
+ */
29
+ expiryDate: number;
30
+ /**
31
+ * Optional RPC URL for the network
32
+ */
33
+ rpcUrl?: string;
34
+ }
35
+
36
+ export interface ISignStakeAndBakeResult {
37
+ /**
38
+ * The signature
39
+ */
40
+ signature: string;
41
+ /**
42
+ * The typed data used to generate the signature
43
+ */
44
+ typedData: string;
45
+ }
46
+
47
+ /**
48
+ * Signs stake and bake authorization with EIP-712
49
+ *
50
+ * @param {ISignStakeAndBakeParams} params - Parameters for signing
51
+ * @returns {Promise<ISignStakeAndBakeResult>} The signature and typed data
52
+ */
53
+ export async function signStakeAndBake({
54
+ address,
55
+ provider,
56
+ chainId,
57
+ value,
58
+ env,
59
+ expiryDate,
60
+ rpcUrl,
61
+ }: ISignStakeAndBakeParams): Promise<ISignStakeAndBakeResult> {
62
+ const providerInstance = new Provider({
63
+ provider,
64
+ account: address,
65
+ chainId,
66
+ });
67
+
68
+ const spender = getStakeAndBakeSpenderAddress(chainId);
69
+
70
+ const typedDataObject = await getStakeAndBakeTypedData({
71
+ chainId,
72
+ expiryDate,
73
+ owner: address,
74
+ spender,
75
+ value,
76
+ env,
77
+ rpcUrl,
78
+ });
79
+
80
+ const typedData = JSON.stringify(typedDataObject);
81
+
82
+ const signature = await providerInstance.web3?.currentProvider?.request<
83
+ 'eth_signTypedData_v4',
84
+ string
85
+ >({
86
+ method: 'eth_signTypedData_v4',
87
+ params: [address, typedData],
88
+ });
89
+
90
+ if (typeof signature === 'string') {
91
+ return { signature, typedData };
92
+ }
93
+
94
+ if (!signature?.result) {
95
+ throw new Error(NO_SIGNATURE_ERROR);
96
+ }
97
+
98
+ return { signature: signature.result, typedData };
99
+ }