@lombard.finance/sdk-starknet 0.3.0-canary.2
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.
- package/README.md +128 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/index2.cjs +229 -0
- package/dist/index2.cjs.map +1 -0
- package/dist/index2.js +30378 -0
- package/dist/index2.js.map +1 -0
- package/dist/index3.cjs +2 -0
- package/dist/index3.cjs.map +1 -0
- package/dist/index3.js +2316 -0
- package/dist/index3.js.map +1 -0
- package/package.json +66 -0
- package/src/contract-functions/approve.stories.tsx +74 -0
- package/src/contract-functions/approve.ts +56 -0
- package/src/contract-functions/balance-of.stories.tsx +54 -0
- package/src/contract-functions/balance-of.ts +41 -0
- package/src/contract-functions/mint.ts +109 -0
- package/src/contract-functions/redeem.ts +107 -0
- package/src/index.ts +25 -0
- package/src/module/createStarknetModule.ts +38 -0
- package/src/services/StarknetServiceImpl.ts +83 -0
- package/src/services/index.ts +7 -0
- package/src/stories/components/Button/Button.css +10 -0
- package/src/stories/components/Button/Button.tsx +52 -0
- package/src/stories/components/Button/index.ts +1 -0
- package/src/stories/components/CodeBlock/CodeBlock.tsx +38 -0
- package/src/stories/components/CodeBlock/CodeBlockStyles.css +3 -0
- package/src/stories/components/CodeBlock/index.ts +1 -0
- package/src/stories/components/ConnectButton/connect-button.tsx +112 -0
- package/src/stories/components/ConnectButton/index.ts +1 -0
- package/src/stories/components/Spinner/Spinner.tsx +24 -0
- package/src/stories/components/Spinner/index.ts +1 -0
- package/src/stories/components/decorators/function-type.tsx +66 -0
- package/src/stories/components/decorators/index.ts +1 -0
- package/src/stories/components/decorators/starknet-context.tsx +21 -0
- package/src/stories/components/error-block.tsx +21 -0
- package/src/stories/hooks/use-connection.ts +70 -0
- package/src/stories/hooks/use-query.ts +56 -0
- package/src/tokens/abi/ERC20_ABI.ts +1122 -0
- package/src/tokens/abi/LBTC_ABI.ts +1615 -0
- package/src/tokens/abi/LBTC_BASCULE_ABI.ts +709 -0
- package/src/tokens/abi/LBTC_BRIDGE_ABI.ts +1568 -0
- package/src/tokens/lib/tokens.ts +267 -0
- package/src/utils/account.ts +81 -0
- package/src/utils/chains.ts +33 -0
- package/src/utils/common.ts +11 -0
- package/src/utils/env.ts +6 -0
- package/src/utils/err.ts +121 -0
- package/src/utils/rpc-providers.ts +25 -0
- package/src/utils/signature.ts +60 -0
- package/src/utils/span.ts +56 -0
- package/src/utils/typed-data.ts +36 -0
- package/src/utils/wallet-account.ts +9 -0
- package/src/wallet-functions/sign-message.stories.tsx +72 -0
- package/src/wallet-functions/sign-message.ts +127 -0
- package/src/wallet-functions/sign-terms-of-service.ts +45 -0
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lombard.finance/sdk-starknet",
|
|
3
|
+
"version": "0.3.0-canary.2",
|
|
4
|
+
"exports": {
|
|
5
|
+
".": {
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"import": "./dist/index.js",
|
|
8
|
+
"require": "./dist/index.cjs"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"types": "./src/index.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">= 22.14.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "yarn types && vite build",
|
|
23
|
+
"build-docs": "rimraf ./sdk-docs && typedoc --out sdk-docs",
|
|
24
|
+
"build-storybook": "storybook build -o sdk-storybook",
|
|
25
|
+
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings=0",
|
|
26
|
+
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
|
|
27
|
+
"format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
|
|
28
|
+
"storybook": "storybook dev -p 6006",
|
|
29
|
+
"types": "tsc --noEmit",
|
|
30
|
+
"watch": "vite build --watch"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@lombard.finance/sdk-common": "3.4.0-canary.2",
|
|
34
|
+
"starknet": "~6.24.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@chromatic-com/storybook": "1.6.1",
|
|
38
|
+
"@starknet-io/get-starknet": "^4.0.7",
|
|
39
|
+
"@storybook/addon-essentials": "^8.2.9",
|
|
40
|
+
"@storybook/addon-interactions": "^8.2.9",
|
|
41
|
+
"@storybook/addon-links": "^8.2.9",
|
|
42
|
+
"@storybook/addon-onboarding": "^8.2.9",
|
|
43
|
+
"@storybook/blocks": "^8.2.9",
|
|
44
|
+
"@storybook/react": "^8.2.9",
|
|
45
|
+
"@storybook/react-vite": "^8.2.9",
|
|
46
|
+
"@storybook/test": "^8.2.9",
|
|
47
|
+
"@tanstack/react-query": "^5.71.1",
|
|
48
|
+
"@types/node": "^24.7.2",
|
|
49
|
+
"axios": "^1.7.4",
|
|
50
|
+
"bignumber.js": "^9.1.2",
|
|
51
|
+
"bootstrap": "^5.3.3",
|
|
52
|
+
"rimraf": "^5",
|
|
53
|
+
"storybook": "^8.2.9",
|
|
54
|
+
"typedoc": "^0.26.6",
|
|
55
|
+
"typescript": "^5.4.5",
|
|
56
|
+
"vite": "^6.3.5"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@noble/hashes": "^1.7.2",
|
|
60
|
+
"axios": "^1",
|
|
61
|
+
"bignumber.js": "^9"
|
|
62
|
+
},
|
|
63
|
+
"resolutions": {
|
|
64
|
+
"starknet": "~6.24.1"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../stories/components/Button';
|
|
5
|
+
import { CodeBlock } from '../stories/components/CodeBlock';
|
|
6
|
+
import { ConnectButton } from '../stories/components/ConnectButton';
|
|
7
|
+
import { functionType } from '../stories/components/decorators';
|
|
8
|
+
import { starknetContext } from '../stories/components/decorators/starknet-context';
|
|
9
|
+
import { useConnection } from '../stories/hooks/use-connection';
|
|
10
|
+
import useQuery from '../stories/hooks/use-query';
|
|
11
|
+
import { Token } from '../tokens/lib/tokens';
|
|
12
|
+
import { approve } from './approve';
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
title: 'write/approve',
|
|
16
|
+
component: StoryView,
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
decorators: [functionType('write'), starknetContext()],
|
|
19
|
+
} satisfies Meta<typeof StoryView>;
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
export const WithParams: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
amount: 1,
|
|
28
|
+
spender:
|
|
29
|
+
'0x07777d51699c68356c9dad77a56ac2fc90e03312f4afe554650791c379d6719d',
|
|
30
|
+
token: Token.LBTC,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type FuncParameters = Parameters<typeof approve>[0];
|
|
35
|
+
type Props = Omit<FuncParameters, 'walletAccount'>;
|
|
36
|
+
|
|
37
|
+
export function StoryView(props: Props) {
|
|
38
|
+
const { account } = useConnection();
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
console.log('s account', account);
|
|
42
|
+
}, [account]);
|
|
43
|
+
|
|
44
|
+
const request = async () => {
|
|
45
|
+
if (!account) {
|
|
46
|
+
console.info('Cannot perform action');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return approve({
|
|
51
|
+
walletAccount: account,
|
|
52
|
+
...props,
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const { data, error, isLoading, refetch } = useQuery(request, [], false);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
<div className="mb-4">
|
|
61
|
+
<ConnectButton />
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<Button
|
|
65
|
+
onClick={refetch}
|
|
66
|
+
disabled={isLoading}
|
|
67
|
+
isLoading={isLoading}
|
|
68
|
+
actionName={approve.name}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<CodeBlock text={error || data} />
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { toBaseDenomination } from '@lombard.finance/sdk-common/utils/numbers';
|
|
2
|
+
|
|
3
|
+
import { getTokenContract, TokenParameters } from '../tokens/lib/tokens';
|
|
4
|
+
import { StarknetChainId } from '../utils/chains';
|
|
5
|
+
import { Address } from '../utils/common';
|
|
6
|
+
import { EnvParameters } from '../utils/env';
|
|
7
|
+
import { getRpcProvider } from '../utils/rpc-providers';
|
|
8
|
+
import { WalletAccountParameters } from '../utils/wallet-account';
|
|
9
|
+
|
|
10
|
+
type ApproveParameters = {
|
|
11
|
+
/** The approved amount. */
|
|
12
|
+
amount: BigNumber.Value;
|
|
13
|
+
/** The spender address. */
|
|
14
|
+
spender: Address;
|
|
15
|
+
} & TokenParameters &
|
|
16
|
+
WalletAccountParameters &
|
|
17
|
+
EnvParameters;
|
|
18
|
+
|
|
19
|
+
export async function approve({
|
|
20
|
+
amount,
|
|
21
|
+
spender,
|
|
22
|
+
token,
|
|
23
|
+
walletAccount,
|
|
24
|
+
env,
|
|
25
|
+
}: ApproveParameters) {
|
|
26
|
+
const chainId = (await walletAccount.getChainId()) as StarknetChainId;
|
|
27
|
+
|
|
28
|
+
// Use SDK's RPC provider for read-only operations to avoid wallet RPC rate limits
|
|
29
|
+
// (Wallet extensions like Braavos/ArgentX use OnFinality which has strict rate limits)
|
|
30
|
+
const rpcProvider = getRpcProvider(chainId);
|
|
31
|
+
|
|
32
|
+
// Read decimals using SDK RPC (avoid wallet rate limits)
|
|
33
|
+
const readOnlyContract = getTokenContract({
|
|
34
|
+
chainId,
|
|
35
|
+
contractType: 'token',
|
|
36
|
+
provider: rpcProvider,
|
|
37
|
+
token,
|
|
38
|
+
env,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const decimals = await readOnlyContract.decimals();
|
|
42
|
+
const amountBaseDenom = toBaseDenomination(amount, Number(decimals));
|
|
43
|
+
|
|
44
|
+
// Use walletAccount for write operation (signing tx)
|
|
45
|
+
const tokenContract = getTokenContract({
|
|
46
|
+
chainId,
|
|
47
|
+
contractType: 'token',
|
|
48
|
+
provider: walletAccount,
|
|
49
|
+
token,
|
|
50
|
+
env,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const tx = await tokenContract.approve(spender, amountBaseDenom.toNumber());
|
|
54
|
+
|
|
55
|
+
return tx.transaction_hash;
|
|
56
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { Button } from '../stories/components/Button';
|
|
4
|
+
import { CodeBlock } from '../stories/components/CodeBlock';
|
|
5
|
+
import { functionType } from '../stories/components/decorators';
|
|
6
|
+
import useQuery from '../stories/hooks/use-query';
|
|
7
|
+
import { Token } from '../tokens/lib/tokens';
|
|
8
|
+
import { StarknetChainId } from '../utils/chains';
|
|
9
|
+
import { balanceOf } from './balance-of';
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'read/balance-of',
|
|
13
|
+
component: StoryView,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
decorators: [functionType('read')],
|
|
16
|
+
} satisfies Meta<typeof StoryView>;
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
|
|
20
|
+
type Story = StoryObj<typeof meta>;
|
|
21
|
+
|
|
22
|
+
export const WithParams: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
account:
|
|
25
|
+
'0x0222cad4d720e1693277a3cc426ae81199ff808f86c10b6dffd946b37f5953d7',
|
|
26
|
+
token: Token.LBTC,
|
|
27
|
+
chainId: StarknetChainId.SN_SEPOLIA,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type Props = Parameters<typeof balanceOf>[0];
|
|
32
|
+
|
|
33
|
+
export function StoryView(props: Props) {
|
|
34
|
+
const request = async () => {
|
|
35
|
+
return balanceOf({
|
|
36
|
+
...props,
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const { data, error, isLoading, refetch } = useQuery(request, [], false);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
<Button
|
|
45
|
+
onClick={refetch}
|
|
46
|
+
disabled={isLoading}
|
|
47
|
+
isLoading={isLoading}
|
|
48
|
+
actionName={balanceOf.name}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
<CodeBlock text={error || data} />
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { fromBaseDenomination } from '@lombard.finance/sdk-common/utils/numbers';
|
|
2
|
+
import { BigNumber } from 'bignumber.js';
|
|
3
|
+
|
|
4
|
+
import { getTokenContract, TokenParameters } from '../tokens/lib/tokens';
|
|
5
|
+
import { ChainParameters } from '../utils/chains';
|
|
6
|
+
import { Address } from '../utils/common';
|
|
7
|
+
import { EnvParameters } from '../utils/env';
|
|
8
|
+
import { getRpcProvider } from '../utils/rpc-providers';
|
|
9
|
+
|
|
10
|
+
type BalanceOfParameters = {
|
|
11
|
+
/** The account address */
|
|
12
|
+
account: Address;
|
|
13
|
+
} & TokenParameters &
|
|
14
|
+
ChainParameters &
|
|
15
|
+
EnvParameters;
|
|
16
|
+
|
|
17
|
+
export async function balanceOf({
|
|
18
|
+
account,
|
|
19
|
+
token,
|
|
20
|
+
chainId,
|
|
21
|
+
env,
|
|
22
|
+
}: BalanceOfParameters) {
|
|
23
|
+
const provider = getRpcProvider(chainId);
|
|
24
|
+
|
|
25
|
+
const contract = getTokenContract({
|
|
26
|
+
chainId,
|
|
27
|
+
contractType: 'token',
|
|
28
|
+
provider,
|
|
29
|
+
token,
|
|
30
|
+
env,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const balanceRaw = (await contract.balance_of(account)) as bigint;
|
|
34
|
+
const balance = BigNumber(String(balanceRaw));
|
|
35
|
+
|
|
36
|
+
const decimals = (await contract.decimals()) as number;
|
|
37
|
+
|
|
38
|
+
const tokenBalance = fromBaseDenomination(balance, decimals);
|
|
39
|
+
|
|
40
|
+
return tokenBalance;
|
|
41
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
2
|
+
|
|
3
|
+
import { getTokenContract, TokenParameters } from '../tokens/lib/tokens';
|
|
4
|
+
import { makeDestinationChainId, StarknetChainId } from '../utils/chains';
|
|
5
|
+
import { Address, ensureHex } from '../utils/common';
|
|
6
|
+
import { EnvParameters } from '../utils/env';
|
|
7
|
+
import { getRpcProvider } from '../utils/rpc-providers';
|
|
8
|
+
import { parseProofHexToU256Tuples } from '../utils/span';
|
|
9
|
+
import { WalletAccountParameters } from '../utils/wallet-account';
|
|
10
|
+
|
|
11
|
+
export type MintParameters = TokenParameters &
|
|
12
|
+
WalletAccountParameters &
|
|
13
|
+
EnvParameters & {
|
|
14
|
+
/** The amount to be minted, satoshi */
|
|
15
|
+
amount: BigNumber.Value;
|
|
16
|
+
/** The deposit tx index, e.g. 0 */
|
|
17
|
+
depositIndex: number | string;
|
|
18
|
+
/** The raw paylod of the BTC deposit */
|
|
19
|
+
depositPayload: string;
|
|
20
|
+
/** The proof signature of the BTC deposit */
|
|
21
|
+
depositProofSignature: string;
|
|
22
|
+
/** The transaction id of the BTC deposit */
|
|
23
|
+
depositTxId: string;
|
|
24
|
+
/** The (optional) recipient address. Uses the connected address if left empty */
|
|
25
|
+
recipientAddress?: Address;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Mints the specified amount of token (e.g LBTC) and transfers the funds
|
|
30
|
+
* to the recipient given address.
|
|
31
|
+
*/
|
|
32
|
+
export async function mint({
|
|
33
|
+
amount,
|
|
34
|
+
depositPayload,
|
|
35
|
+
depositTxId,
|
|
36
|
+
depositIndex,
|
|
37
|
+
depositProofSignature,
|
|
38
|
+
token,
|
|
39
|
+
walletAccount,
|
|
40
|
+
recipientAddress,
|
|
41
|
+
env,
|
|
42
|
+
}: MintParameters) {
|
|
43
|
+
const chainId = (await walletAccount.getChainId()) as StarknetChainId;
|
|
44
|
+
|
|
45
|
+
const btcTxIdBytes = (
|
|
46
|
+
depositTxId.startsWith('0x') ? depositTxId.slice(2) : depositTxId
|
|
47
|
+
).match(/.{2}/g);
|
|
48
|
+
if (!btcTxIdBytes) {
|
|
49
|
+
throw new Error('Missing deposit tx id');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Use SDK's RPC provider for read-only operations to avoid wallet RPC rate limits
|
|
53
|
+
// (Wallet extensions like Braavos/ArgentX use OnFinality which has strict rate limits)
|
|
54
|
+
const rpcProvider = getRpcProvider(chainId);
|
|
55
|
+
|
|
56
|
+
const readOnlyParams = {
|
|
57
|
+
chainId,
|
|
58
|
+
provider: rpcProvider, // Use SDK RPC for reads, not wallet's RPC
|
|
59
|
+
token,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const basculeContract = getTokenContract({
|
|
63
|
+
...readOnlyParams,
|
|
64
|
+
contractType: 'bascule',
|
|
65
|
+
env,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const basculeDepositId = ensureHex(
|
|
69
|
+
Buffer.from(
|
|
70
|
+
keccak_256(Buffer.from(depositPayload.slice(8), 'hex')),
|
|
71
|
+
).toString('hex'),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const status = await basculeContract.get_deposit_status(
|
|
75
|
+
BigInt(basculeDepositId),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (status.activeVariant() !== 'Reported') {
|
|
79
|
+
const msg = `The deposit cannot be claimed. Bascule status: ${status.activeVariant()}`;
|
|
80
|
+
console.warn(msg); // TODO: Remove when Bascule configured
|
|
81
|
+
// throw new Error(msg); // TODO: Re-enable once configured
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const to_chain = BigInt(makeDestinationChainId(chainId));
|
|
85
|
+
const recipient = recipientAddress || walletAccount.address;
|
|
86
|
+
const tx_id = BigInt(ensureHex(btcTxIdBytes.reverse().join('')));
|
|
87
|
+
const vout = Number(depositIndex);
|
|
88
|
+
const proof = parseProofHexToU256Tuples(depositProofSignature);
|
|
89
|
+
|
|
90
|
+
// For write operations (mint), use walletAccount to sign the transaction
|
|
91
|
+
const bridgeContract = getTokenContract({
|
|
92
|
+
chainId,
|
|
93
|
+
provider: walletAccount, // Need wallet account to sign tx
|
|
94
|
+
token,
|
|
95
|
+
contractType: 'bridge',
|
|
96
|
+
env,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const { transaction_hash } = await bridgeContract.mint(
|
|
100
|
+
to_chain,
|
|
101
|
+
recipient,
|
|
102
|
+
BigInt(amount.toString()),
|
|
103
|
+
tx_id,
|
|
104
|
+
vout,
|
|
105
|
+
proof,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return transaction_hash;
|
|
109
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Env, getOutputScript } from '@lombard.finance/sdk-common';
|
|
2
|
+
import {
|
|
3
|
+
BtcAddressType,
|
|
4
|
+
getBtcAddressType,
|
|
5
|
+
} from '@lombard.finance/sdk-common/utils/btc-address-type';
|
|
6
|
+
import { toBaseDenomination } from '@lombard.finance/sdk-common/utils/numbers';
|
|
7
|
+
import { ByteArray, CallData, uint256 } from 'starknet';
|
|
8
|
+
|
|
9
|
+
import { getTokenContract, TokenParameters } from '../tokens/lib/tokens';
|
|
10
|
+
import { StarknetChainId } from '../utils/chains';
|
|
11
|
+
import { EnvParameters } from '../utils/env';
|
|
12
|
+
import { ERR_UNEXPECTED_OUTPUT_SCRIPT } from '../utils/err';
|
|
13
|
+
import { getRpcProvider } from '../utils/rpc-providers';
|
|
14
|
+
import { WalletAccountParameters } from '../utils/wallet-account';
|
|
15
|
+
|
|
16
|
+
export type RedeemParameters = TokenParameters &
|
|
17
|
+
WalletAccountParameters &
|
|
18
|
+
EnvParameters & {
|
|
19
|
+
/** The amount, e.g. 1.2 LBTC */
|
|
20
|
+
amount: BigNumber.Value;
|
|
21
|
+
/** The destination BTC address */
|
|
22
|
+
btcAddress: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Redeems (unstakes) specified amount of token and sends equivalent amount of
|
|
27
|
+
* BTC to the provided BTC address.
|
|
28
|
+
*/
|
|
29
|
+
export async function redeem({
|
|
30
|
+
amount,
|
|
31
|
+
btcAddress,
|
|
32
|
+
token,
|
|
33
|
+
walletAccount,
|
|
34
|
+
env,
|
|
35
|
+
}: RedeemParameters) {
|
|
36
|
+
const chainId = (await walletAccount.getChainId()) as StarknetChainId;
|
|
37
|
+
|
|
38
|
+
// Use SDK's RPC provider for read-only operations to avoid wallet RPC rate limits
|
|
39
|
+
// (Wallet extensions like Braavos/ArgentX use OnFinality which has strict rate limits)
|
|
40
|
+
const rpcProvider = getRpcProvider(chainId);
|
|
41
|
+
|
|
42
|
+
const tokenParams = {
|
|
43
|
+
token,
|
|
44
|
+
chainId,
|
|
45
|
+
provider: rpcProvider, // Use SDK RPC for reads, not wallet's RPC
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const tokenContract = getTokenContract({
|
|
49
|
+
...tokenParams,
|
|
50
|
+
contractType: 'token',
|
|
51
|
+
env,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const decimals = Number(await tokenContract.decimals());
|
|
55
|
+
const amountBaseDenom = toBaseDenomination(amount, decimals);
|
|
56
|
+
|
|
57
|
+
const bridgeContract = getTokenContract({
|
|
58
|
+
...tokenParams,
|
|
59
|
+
contractType: 'bridge',
|
|
60
|
+
env,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const script = await getOutputScript(
|
|
64
|
+
btcAddress,
|
|
65
|
+
chainId === StarknetChainId.SN_MAIN ? Env.prod : Env.stage,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const addressType = getBtcAddressType(btcAddress);
|
|
69
|
+
|
|
70
|
+
let script_pub_key: ByteArray | undefined = undefined;
|
|
71
|
+
if (
|
|
72
|
+
addressType === BtcAddressType.p2tr ||
|
|
73
|
+
addressType === BtcAddressType.p2wsh
|
|
74
|
+
) {
|
|
75
|
+
const word = `0x${script.slice(2, 64)}`;
|
|
76
|
+
const pending_word = `0x${script.slice(64) || '0'}`;
|
|
77
|
+
|
|
78
|
+
script_pub_key = {
|
|
79
|
+
data: [word],
|
|
80
|
+
pending_word: pending_word,
|
|
81
|
+
pending_word_len: script.slice(64).length / 2,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (addressType === BtcAddressType.p2wpkh) {
|
|
86
|
+
script_pub_key = {
|
|
87
|
+
data: [],
|
|
88
|
+
pending_word: `0x${script.slice(2)}`,
|
|
89
|
+
pending_word_len: script.slice(2).length / 2,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!script_pub_key) {
|
|
94
|
+
throw ERR_UNEXPECTED_OUTPUT_SCRIPT;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { transaction_hash } = await walletAccount.execute({
|
|
98
|
+
contractAddress: bridgeContract.address,
|
|
99
|
+
entrypoint: 'redeem',
|
|
100
|
+
calldata: CallData.compile({
|
|
101
|
+
script_pub_key,
|
|
102
|
+
amount: uint256.bnToUint256(amountBaseDenom.toNumber()),
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return transaction_hash;
|
|
107
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Starknet tokens */
|
|
2
|
+
|
|
3
|
+
export * from './tokens/lib/tokens';
|
|
4
|
+
|
|
5
|
+
/** Contract functions */
|
|
6
|
+
|
|
7
|
+
export * from './contract-functions/approve';
|
|
8
|
+
export * from './contract-functions/balance-of';
|
|
9
|
+
export * from './contract-functions/mint';
|
|
10
|
+
export * from './contract-functions/redeem';
|
|
11
|
+
|
|
12
|
+
/** Wallet functions */
|
|
13
|
+
|
|
14
|
+
export * from './wallet-functions/sign-message';
|
|
15
|
+
export * from './wallet-functions/sign-terms-of-service';
|
|
16
|
+
|
|
17
|
+
/** Utils */
|
|
18
|
+
|
|
19
|
+
export * from './utils/chains';
|
|
20
|
+
export * from './utils/rpc-providers';
|
|
21
|
+
export * from './utils/typed-data';
|
|
22
|
+
|
|
23
|
+
/** Module */
|
|
24
|
+
|
|
25
|
+
export { starknetModule } from './module/createStarknetModule';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starknet Module
|
|
3
|
+
*
|
|
4
|
+
* Provides Starknet chain service for LBTC operations.
|
|
5
|
+
* Uses Service-First pattern: module is a thin factory that instantiates the service.
|
|
6
|
+
*
|
|
7
|
+
* @module module/createStarknetModule
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ChainModule, StarknetService } from '@lombard.finance/sdk-common';
|
|
11
|
+
|
|
12
|
+
import { StarknetServiceImpl } from '../services/StarknetServiceImpl';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create Starknet module
|
|
16
|
+
*
|
|
17
|
+
* Optional module that provides StarknetService for Starknet operations.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { starknetModule } from '@lombard.finance/sdk-starknet';
|
|
22
|
+
*
|
|
23
|
+
* const config = createConfig({
|
|
24
|
+
* modules: [starknetModule()],
|
|
25
|
+
* providers: { starknet: () => starknetWallet },
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function starknetModule(): ChainModule<'starknet', StarknetService> {
|
|
30
|
+
return {
|
|
31
|
+
id: 'starknet',
|
|
32
|
+
chain: 'starknet',
|
|
33
|
+
requiresProviders: ['starknet'],
|
|
34
|
+
register(ctx) {
|
|
35
|
+
return new StarknetServiceImpl(() => ctx.getProvider('starknet'));
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starknet Service Implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides Starknet-specific operations for LBTC destination signing and unstaking.
|
|
5
|
+
*
|
|
6
|
+
* @module services/StarknetServiceImpl
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Env, StarknetService } from '@lombard.finance/sdk-common';
|
|
10
|
+
import type { WalletAccount } from 'starknet';
|
|
11
|
+
|
|
12
|
+
import { redeem } from '../contract-functions/redeem';
|
|
13
|
+
import { Token } from '../tokens/lib/tokens';
|
|
14
|
+
import { StarknetChainId } from '../utils/chains';
|
|
15
|
+
import { signLbtcDestinationAddrStarknet } from '../wallet-functions/sign-message';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Provider resolver function type
|
|
19
|
+
*/
|
|
20
|
+
type ProviderResolver = () => Promise<unknown>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Starknet wallet provider interface
|
|
24
|
+
*/
|
|
25
|
+
interface StarknetWalletProvider {
|
|
26
|
+
getProvider: () => WalletAccount;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Starknet Service Implementation
|
|
31
|
+
*
|
|
32
|
+
* Wraps low-level Starknet functions into a clean service interface.
|
|
33
|
+
* Instantiated by starknetModule().
|
|
34
|
+
*/
|
|
35
|
+
export class StarknetServiceImpl implements StarknetService {
|
|
36
|
+
constructor(private readonly getProvider: ProviderResolver) {}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Sign LBTC destination address for Starknet minting
|
|
40
|
+
*/
|
|
41
|
+
async signLbtcDestination(args: {
|
|
42
|
+
chainId: string;
|
|
43
|
+
}): Promise<{ signature: string; pubKey: string }> {
|
|
44
|
+
const provider = await this.getProvider();
|
|
45
|
+
const walletProvider = provider as StarknetWalletProvider;
|
|
46
|
+
const walletAccount = walletProvider.getProvider();
|
|
47
|
+
|
|
48
|
+
const result = await signLbtcDestinationAddrStarknet({
|
|
49
|
+
walletAccount,
|
|
50
|
+
chainId: args.chainId as StarknetChainId,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
signature: result.signatureHex,
|
|
55
|
+
pubKey: result.pubKey,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Unstake LBTC on Starknet to receive BTC
|
|
61
|
+
*
|
|
62
|
+
* Burns LBTC on Starknet and releases BTC to the provided Bitcoin address.
|
|
63
|
+
*/
|
|
64
|
+
async unstake(args: {
|
|
65
|
+
amount: string;
|
|
66
|
+
btcAddress: string;
|
|
67
|
+
env: string;
|
|
68
|
+
}): Promise<{ txHash: string }> {
|
|
69
|
+
const provider = await this.getProvider();
|
|
70
|
+
const walletProvider = provider as StarknetWalletProvider;
|
|
71
|
+
const walletAccount = walletProvider.getProvider();
|
|
72
|
+
|
|
73
|
+
const txHash = await redeem({
|
|
74
|
+
amount: args.amount,
|
|
75
|
+
btcAddress: args.btcAddress,
|
|
76
|
+
token: Token.LBTC,
|
|
77
|
+
walletAccount,
|
|
78
|
+
env: args.env as Env,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return { txHash };
|
|
82
|
+
}
|
|
83
|
+
}
|