@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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { DEFAULT_ENV, Env } from '@lombard.finance/sdk-common';
|
|
2
|
+
import { Contract, TypedContractV2 } from 'starknet';
|
|
3
|
+
|
|
4
|
+
import { ChainParameters, StarknetChainId } from '../../utils/chains';
|
|
5
|
+
import { EnvParameters } from '../../utils/env';
|
|
6
|
+
import { ERR_CHAIN_MISMATCH, ERR_NO_PROVIDER, ERR_NO_TOKEN } from '../../utils/err';
|
|
7
|
+
import { getRpcProvider, ProviderParameters } from '../../utils/rpc-providers';
|
|
8
|
+
import ERC20_ABI from '../abi/ERC20_ABI';
|
|
9
|
+
import LBTC_ABI from '../abi/LBTC_ABI';
|
|
10
|
+
import LBTC_BASCULE_ABI from '../abi/LBTC_BASCULE_ABI';
|
|
11
|
+
import LBTC_BRIDGE_ABI from '../abi/LBTC_BRIDGE_ABI';
|
|
12
|
+
|
|
13
|
+
export enum Token {
|
|
14
|
+
ETH = 'ETH',
|
|
15
|
+
LBTC = 'LBTC',
|
|
16
|
+
STRK = 'STRK',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ETH_TOKEN_CONFIG = {
|
|
20
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
21
|
+
address:
|
|
22
|
+
'0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7',
|
|
23
|
+
classHash:
|
|
24
|
+
'0x076791ef97c042f81fbf352ad95f39a22554ee8d7927b2ce3c681f3418b5206a',
|
|
25
|
+
abi: ERC20_ABI,
|
|
26
|
+
},
|
|
27
|
+
[StarknetChainId.SN_MAIN]: {
|
|
28
|
+
address:
|
|
29
|
+
'0x049D36570D4e46f48e99674bd3fcc84644DdD6b96F7C741B1562B82f9e004dC7',
|
|
30
|
+
classHash: '',
|
|
31
|
+
abi: ERC20_ABI,
|
|
32
|
+
},
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
const LBTC_TOKEN_CONFIG = {
|
|
36
|
+
[Env.stage]: {
|
|
37
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
38
|
+
address:
|
|
39
|
+
'0x00b442f5420860e937a99659326e81a97e07bfd538b3cee65b90029c9da38a5f',
|
|
40
|
+
classHash:
|
|
41
|
+
'0x01f12942315b6dd05a21392ced7890a380affa81066741de6ac31af4dcb0d468',
|
|
42
|
+
abi: LBTC_ABI,
|
|
43
|
+
},
|
|
44
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
45
|
+
},
|
|
46
|
+
[Env.testnet]: {
|
|
47
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
48
|
+
address:
|
|
49
|
+
'0x00456a829ab75ba5e97534dc70d7fc617cfda244f8dcda47b11624de67c6e70c',
|
|
50
|
+
classHash:
|
|
51
|
+
'0x01f12942315b6dd05a21392ced7890a380affa81066741de6ac31af4dcb0d468',
|
|
52
|
+
abi: LBTC_ABI,
|
|
53
|
+
},
|
|
54
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
55
|
+
},
|
|
56
|
+
[Env.dev]: {
|
|
57
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
58
|
+
address:
|
|
59
|
+
'0x0723de0c550b7bfbb5051dade72966d71a08bef952c0197462a5244497eb57c1',
|
|
60
|
+
classHash:
|
|
61
|
+
'0x01f12942315b6dd05a21392ced7890a380affa81066741de6ac31af4dcb0d468',
|
|
62
|
+
abi: LBTC_ABI,
|
|
63
|
+
},
|
|
64
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
65
|
+
},
|
|
66
|
+
[Env.prod]: {
|
|
67
|
+
[StarknetChainId.SN_SEPOLIA]: undefined,
|
|
68
|
+
[StarknetChainId.SN_MAIN]: {
|
|
69
|
+
address:
|
|
70
|
+
'0x036834a40984312f7f7de8d31e3f6305b325389eaeea5b1c0664b2fb936461a4',
|
|
71
|
+
classHash: '',
|
|
72
|
+
abi: LBTC_ABI,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
[Env.ibc]: undefined,
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
const LBTC_BRIDGE_CONFIG = {
|
|
79
|
+
[Env.stage]: {
|
|
80
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
81
|
+
address:
|
|
82
|
+
'0x01d27f156092746d0d7cd9d9deec5e937f27c3c7c1c28365e9e5efac459880b3',
|
|
83
|
+
classHash:
|
|
84
|
+
'0x0524d80f999f690bbb302f37a36abdf82ea2135391c3d1a5090e7665c20e2159',
|
|
85
|
+
abi: LBTC_BRIDGE_ABI,
|
|
86
|
+
},
|
|
87
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
88
|
+
},
|
|
89
|
+
[Env.testnet]: {
|
|
90
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
91
|
+
address:
|
|
92
|
+
'0x063b7b5c8b114ebd5b9602fbd5d0ffd2cc3a598f1d91c6904cc0997cd8fea760',
|
|
93
|
+
classHash:
|
|
94
|
+
'0x0524d80f999f690bbb302f37a36abdf82ea2135391c3d1a5090e7665c20e2159',
|
|
95
|
+
abi: LBTC_BRIDGE_ABI,
|
|
96
|
+
},
|
|
97
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
98
|
+
},
|
|
99
|
+
[Env.dev]: {
|
|
100
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
101
|
+
address:
|
|
102
|
+
'0x04838a05c798dc57e01e85526979841c84f1f3c732a525cff53adb3e8bee3d24',
|
|
103
|
+
classHash:
|
|
104
|
+
'0x0524d80f999f690bbb302f37a36abdf82ea2135391c3d1a5090e7665c20e2159',
|
|
105
|
+
abi: LBTC_BRIDGE_ABI,
|
|
106
|
+
},
|
|
107
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
108
|
+
},
|
|
109
|
+
[Env.prod]: {
|
|
110
|
+
[StarknetChainId.SN_SEPOLIA]: undefined,
|
|
111
|
+
[StarknetChainId.SN_MAIN]: {
|
|
112
|
+
address:
|
|
113
|
+
'0x05b1886d0f844ab930fc0ee066f1655a873437f15a5d2c41ee3e884fd5299976',
|
|
114
|
+
classHash: '',
|
|
115
|
+
abi: LBTC_BRIDGE_ABI,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
[Env.ibc]: undefined,
|
|
119
|
+
} as const;
|
|
120
|
+
|
|
121
|
+
const LBTC_BASCULE_CONFIG = {
|
|
122
|
+
[Env.stage]: {
|
|
123
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
124
|
+
address:
|
|
125
|
+
'0x001d16d02a4ed6f5673635b1ad3b27ead190904b83fafde7b4c55282ef57492a',
|
|
126
|
+
classHash:
|
|
127
|
+
'0x0083965992f591e5979068d8a12a09a1f13949e1d1f65f495e7a5503d2f1e519',
|
|
128
|
+
abi: LBTC_BASCULE_ABI,
|
|
129
|
+
},
|
|
130
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
131
|
+
},
|
|
132
|
+
[Env.testnet]: {
|
|
133
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
134
|
+
address:
|
|
135
|
+
'0x01757fb630a6cc4e3607fc1d92e0f91c5b3eedd71420f1fa7dc6d1764827b012',
|
|
136
|
+
classHash:
|
|
137
|
+
'0x0083965992f591e5979068d8a12a09a1f13949e1d1f65f495e7a5503d2f1e519',
|
|
138
|
+
abi: LBTC_BASCULE_ABI,
|
|
139
|
+
},
|
|
140
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
141
|
+
},
|
|
142
|
+
[Env.dev]: {
|
|
143
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
144
|
+
address:
|
|
145
|
+
'0x01757fb630a6cc4e3607fc1d92e0f91c5b3eedd71420f1fa7dc6d1764827b012',
|
|
146
|
+
classHash:
|
|
147
|
+
'0x0083965992f591e5979068d8a12a09a1f13949e1d1f65f495e7a5503d2f1e519',
|
|
148
|
+
abi: LBTC_BASCULE_ABI,
|
|
149
|
+
},
|
|
150
|
+
[StarknetChainId.SN_MAIN]: undefined,
|
|
151
|
+
},
|
|
152
|
+
[Env.prod]: {
|
|
153
|
+
[StarknetChainId.SN_SEPOLIA]: undefined,
|
|
154
|
+
[StarknetChainId.SN_MAIN]: {
|
|
155
|
+
address:
|
|
156
|
+
'0x76b68231c1bddb7c981c5a19d9c6a28c5cb14161b8fcd3a857aa8a396d31f95',
|
|
157
|
+
classHash: '',
|
|
158
|
+
abi: LBTC_BASCULE_ABI,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
[Env.ibc]: undefined,
|
|
162
|
+
} as const;
|
|
163
|
+
|
|
164
|
+
const STRK_TOKEN_CONFIG = {
|
|
165
|
+
[StarknetChainId.SN_SEPOLIA]: {
|
|
166
|
+
address:
|
|
167
|
+
'0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d',
|
|
168
|
+
classHash:
|
|
169
|
+
'0x009524a94b41c4440a16fd96d7c1ef6ad6f44c1c013e96662734502cd4ee9b1f',
|
|
170
|
+
abi: ERC20_ABI,
|
|
171
|
+
},
|
|
172
|
+
[StarknetChainId.SN_MAIN]: {
|
|
173
|
+
address:
|
|
174
|
+
'0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d',
|
|
175
|
+
classHash:
|
|
176
|
+
'0x009524a94b41c4440a16fd96d7c1ef6ad6f44c1c013e96662734502cd4ee9b1f',
|
|
177
|
+
abi: ERC20_ABI,
|
|
178
|
+
},
|
|
179
|
+
} as const;
|
|
180
|
+
|
|
181
|
+
export const getTokenInfo = (
|
|
182
|
+
token: Token,
|
|
183
|
+
chain: StarknetChainId = StarknetChainId.SN_MAIN,
|
|
184
|
+
contractType: 'token' | 'bridge' | 'bascule' = 'token',
|
|
185
|
+
env = DEFAULT_ENV,
|
|
186
|
+
) => {
|
|
187
|
+
if (contractType === 'bridge') {
|
|
188
|
+
switch (token) {
|
|
189
|
+
case Token.LBTC:
|
|
190
|
+
return LBTC_BRIDGE_CONFIG?.[env]?.[chain];
|
|
191
|
+
default:
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (contractType === 'bascule') {
|
|
197
|
+
switch (token) {
|
|
198
|
+
case Token.LBTC:
|
|
199
|
+
return LBTC_BASCULE_CONFIG?.[env]?.[chain];
|
|
200
|
+
default:
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// token contracts
|
|
206
|
+
|
|
207
|
+
switch (token) {
|
|
208
|
+
case Token.ETH:
|
|
209
|
+
return ETH_TOKEN_CONFIG[chain];
|
|
210
|
+
case Token.LBTC:
|
|
211
|
+
return LBTC_TOKEN_CONFIG?.[env]?.[chain];
|
|
212
|
+
case Token.STRK:
|
|
213
|
+
return STRK_TOKEN_CONFIG[chain];
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export type TokenParameters = {
|
|
218
|
+
/** The token identifier. */
|
|
219
|
+
token: Token;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Gets the token contract
|
|
224
|
+
*
|
|
225
|
+
* @throws ERR_CHAIN_MISMATCH if wallet network doesn't match SDK environment
|
|
226
|
+
* @throws ERR_NO_TOKEN if token is not configured for the given chain/env
|
|
227
|
+
* @throws ERR_NO_PROVIDER if no provider is available
|
|
228
|
+
*/
|
|
229
|
+
export const getTokenContract = ({
|
|
230
|
+
token,
|
|
231
|
+
chainId,
|
|
232
|
+
provider: _provider,
|
|
233
|
+
contractType = 'token',
|
|
234
|
+
env,
|
|
235
|
+
}: TokenParameters &
|
|
236
|
+
ChainParameters &
|
|
237
|
+
ProviderParameters &
|
|
238
|
+
EnvParameters & {
|
|
239
|
+
contractType: 'token' | 'bridge' | 'bascule';
|
|
240
|
+
}) => {
|
|
241
|
+
const tokenInfo = getTokenInfo(token, chainId, contractType, env);
|
|
242
|
+
if (!tokenInfo) {
|
|
243
|
+
// Check for chain/env mismatch and provide a more helpful error message
|
|
244
|
+
// This happens when the wallet is connected to a different network than the SDK expects
|
|
245
|
+
const isProd = env === 'prod';
|
|
246
|
+
const isMainnet = chainId === StarknetChainId.SN_MAIN;
|
|
247
|
+
|
|
248
|
+
// If env is prod but wallet is on testnet, or env is testnet but wallet is on mainnet
|
|
249
|
+
if ((isProd && !isMainnet) || (!isProd && isMainnet)) {
|
|
250
|
+
throw ERR_CHAIN_MISMATCH(chainId ?? 'unknown', env ?? 'unknown');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
throw ERR_NO_TOKEN(token);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const provider = _provider || getRpcProvider(chainId);
|
|
257
|
+
if (!provider) {
|
|
258
|
+
throw ERR_NO_PROVIDER(chainId);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const contract = new Contract(
|
|
262
|
+
tokenInfo.abi,
|
|
263
|
+
tokenInfo.address,
|
|
264
|
+
provider,
|
|
265
|
+
) as TypedContractV2<typeof tokenInfo.abi>;
|
|
266
|
+
return contract;
|
|
267
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { num, RpcProvider, WalletAccount } from 'starknet';
|
|
2
|
+
|
|
3
|
+
import { StarknetChainId } from './chains';
|
|
4
|
+
import { Address } from './common';
|
|
5
|
+
import { ERR_NO_PUBKEY } from './err';
|
|
6
|
+
import { NormalizedSignature } from './signature';
|
|
7
|
+
|
|
8
|
+
/** The list of entrypoints that could be the public key getters */
|
|
9
|
+
const PUBLIC_KEY_GETTERS = [
|
|
10
|
+
/** The default IAccount<T> public key getter */
|
|
11
|
+
'public_key',
|
|
12
|
+
/** The Braavos wallet account contract public key getter */
|
|
13
|
+
'get_public_key',
|
|
14
|
+
/** The Argent wallet account contract public key getter */
|
|
15
|
+
'get_owner',
|
|
16
|
+
/** The OKX wallet account contract public key getters */
|
|
17
|
+
'getSigner',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/** Gets the public key of an account stored in a contract */
|
|
21
|
+
export async function getPublicKey(
|
|
22
|
+
accountAddress: Address,
|
|
23
|
+
provider: RpcProvider | WalletAccount,
|
|
24
|
+
) {
|
|
25
|
+
let pubkey: string | undefined = undefined;
|
|
26
|
+
|
|
27
|
+
const chainId = (await provider.getChainId()) as StarknetChainId;
|
|
28
|
+
|
|
29
|
+
for (const entrypoint of PUBLIC_KEY_GETTERS) {
|
|
30
|
+
try {
|
|
31
|
+
const res = await provider.callContract({
|
|
32
|
+
contractAddress: accountAddress,
|
|
33
|
+
entrypoint,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (num.isHex(res[0])) {
|
|
37
|
+
pubkey = res[0];
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// NOOP
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!pubkey) {
|
|
45
|
+
throw ERR_NO_PUBKEY(accountAddress, chainId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return pubkey;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Recovers the full public key from the given signature and signed message hash
|
|
53
|
+
*/
|
|
54
|
+
export function recoverFullPublicKeys(
|
|
55
|
+
signature: NormalizedSignature,
|
|
56
|
+
hash: string,
|
|
57
|
+
) {
|
|
58
|
+
const recoveryBits = [0, 1];
|
|
59
|
+
|
|
60
|
+
const keys = [];
|
|
61
|
+
try {
|
|
62
|
+
for (const bit of recoveryBits) {
|
|
63
|
+
const rs = signature.addRecoveryBit(bit);
|
|
64
|
+
const recovered = rs
|
|
65
|
+
.recoverPublicKey(feltToUint8Array(hash))
|
|
66
|
+
.toHex(false);
|
|
67
|
+
keys.push(`0x${recovered}`);
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// NOOP
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return keys;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function feltToUint8Array(felt: string | bigint): Uint8Array {
|
|
77
|
+
const value = typeof felt === 'bigint' ? felt : BigInt(felt);
|
|
78
|
+
let hexValue = value.toString(16);
|
|
79
|
+
if (hexValue.length % 2) hexValue = `0${hexValue}`; // Ensure even-length
|
|
80
|
+
return Uint8Array.from(Buffer.from(hexValue, 'hex'));
|
|
81
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
/** Starknet Main (mainnet) chain id */
|
|
4
|
+
const SN_MAIN = '0x534e5f4d41494e' as const;
|
|
5
|
+
/** Starknet Sepolia chain id */
|
|
6
|
+
const SN_SEPOLIA = '0x534e5f5345504f4c4941' as const;
|
|
7
|
+
|
|
8
|
+
/** Prefixes the destination chain with 0x04... */
|
|
9
|
+
export function makeDestinationChainId(chainId: `0x${string}`) {
|
|
10
|
+
const ch = BigInt(chainId);
|
|
11
|
+
const ch64 = `0x04${ch.toString(16).padStart(64, '0').slice(2)}`;
|
|
12
|
+
return BigNumber(ch64).toFixed(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Starknet chain id */
|
|
16
|
+
export const StarknetChainId = {
|
|
17
|
+
SN_MAIN: SN_MAIN,
|
|
18
|
+
SN_SEPOLIA: SN_SEPOLIA,
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export type StarknetChainId =
|
|
22
|
+
(typeof StarknetChainId)[keyof typeof StarknetChainId];
|
|
23
|
+
|
|
24
|
+
/** Starknet chain identifier (human readable). */
|
|
25
|
+
export enum StarknetChain {
|
|
26
|
+
Mainnet = 'starknet:mainnet',
|
|
27
|
+
Sepolia = 'starknet:sepolia',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ChainParameters = {
|
|
31
|
+
/** The starknet chain identifier. */
|
|
32
|
+
chainId?: StarknetChainId;
|
|
33
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Hex = `0x${string}`;
|
|
2
|
+
export type Hash = Hex;
|
|
3
|
+
export type Address = Hash;
|
|
4
|
+
|
|
5
|
+
export function isHex(input: string): input is Hex {
|
|
6
|
+
return input.startsWith('0x');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ensureHex(input: string): Hex {
|
|
10
|
+
return isHex(input) ? input : `0x${input}`;
|
|
11
|
+
}
|
package/src/utils/env.ts
ADDED
package/src/utils/err.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { extractErrorMessage } from '@lombard.finance/sdk-common/utils/err';
|
|
2
|
+
|
|
3
|
+
import { Token } from '../tokens/lib/tokens';
|
|
4
|
+
import { StarknetChainId } from './chains';
|
|
5
|
+
|
|
6
|
+
/** ErrorOptions type for ES2022 compatibility */
|
|
7
|
+
interface ErrorOptions {
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
enum ErrorCode {
|
|
12
|
+
NO_PROVIDER = 'NO_PROVIDER',
|
|
13
|
+
NO_TOKEN = 'NO_TOKEN',
|
|
14
|
+
UNSUPPORTED_TOKEN = 'UNSUPPORTED_TOKEN',
|
|
15
|
+
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
|
|
16
|
+
NO_STARKNET_WINDOW_OBJECT = 'NO_STARKNET_WINDOW_OBJECT',
|
|
17
|
+
NO_PUBKEY = 'NO_PUBKEY',
|
|
18
|
+
UNKNOWN_SIGNATURE_FORMAT = 'UNKNOWN_SIGNATURE_FORMAT',
|
|
19
|
+
UNEXPECTED_OUTPUT_SCRIPT = 'UNEXPECTED_OUTPUT_SCRIPT',
|
|
20
|
+
UNSUPPORTED_WALLET = 'UNSUPPORTED_WALLET',
|
|
21
|
+
CHAIN_MISMATCH = 'CHAIN_MISMATCH',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class StarknetSdkError extends Error {
|
|
25
|
+
/** The cause of the error (for error chaining) */
|
|
26
|
+
cause?: unknown;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
/** The error message */
|
|
30
|
+
message: string,
|
|
31
|
+
/** The error code */
|
|
32
|
+
readonly code: ErrorCode | string,
|
|
33
|
+
/** The optional error options */
|
|
34
|
+
options?: ErrorOptions,
|
|
35
|
+
) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'StarknetSdkError';
|
|
38
|
+
if (options?.cause) {
|
|
39
|
+
this.cause = options.cause;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static wrap(
|
|
44
|
+
error: unknown,
|
|
45
|
+
code = ErrorCode.UNKNOWN_ERROR,
|
|
46
|
+
customMessage = 'Unknown error occurred.',
|
|
47
|
+
) {
|
|
48
|
+
if (error instanceof StarknetSdkError) return error;
|
|
49
|
+
|
|
50
|
+
const message = extractErrorMessage(error);
|
|
51
|
+
const options: ErrorOptions | undefined =
|
|
52
|
+
error instanceof Error ? { cause: error } : undefined;
|
|
53
|
+
|
|
54
|
+
return new StarknetSdkError(message || customMessage, code, options);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const ERR_NO_PROVIDER = (chainId: StarknetChainId | undefined) =>
|
|
59
|
+
new StarknetSdkError(
|
|
60
|
+
`Could not determine the RPC provider for given chain: ${chainId || 'undefined'}`,
|
|
61
|
+
ErrorCode.NO_PROVIDER,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
export const ERR_NO_TOKEN = (token: Token | undefined) =>
|
|
65
|
+
new StarknetSdkError(
|
|
66
|
+
`Could not retrieve the token information for given token: ${token || 'undefined'}`,
|
|
67
|
+
ErrorCode.NO_TOKEN,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
export const ERR_UNSUPPORTED_TOKEN = (token: Token | undefined) =>
|
|
71
|
+
new StarknetSdkError(
|
|
72
|
+
`The token ${token || 'undefined'} is not supported.`,
|
|
73
|
+
ErrorCode.UNSUPPORTED_TOKEN,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
export const ERR_NO_STARKNET_WINDOW_OBJECT = () =>
|
|
77
|
+
new StarknetSdkError(
|
|
78
|
+
'No Starknet window object detected',
|
|
79
|
+
ErrorCode.NO_STARKNET_WINDOW_OBJECT,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
export const ERR_NO_PUBKEY = (account: string, chainId: StarknetChainId) =>
|
|
83
|
+
new StarknetSdkError(
|
|
84
|
+
`Could not retrieve pubKey for ${account} on ${chainId}`,
|
|
85
|
+
ErrorCode.NO_PUBKEY,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
export const ERR_UNKNOWN_SIGNATURE_FORMAT = () =>
|
|
89
|
+
new StarknetSdkError(
|
|
90
|
+
'Unknown signature format, known formats are [r, s], [version, r, s], [version, type, r, s, ...]',
|
|
91
|
+
ErrorCode.UNKNOWN_SIGNATURE_FORMAT,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const ERR_UNEXPECTED_OUTPUT_SCRIPT = () =>
|
|
95
|
+
new StarknetSdkError(
|
|
96
|
+
'The output script for provided BTC address is invalid',
|
|
97
|
+
ErrorCode.UNEXPECTED_OUTPUT_SCRIPT,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
export const ERR_UNSUPPORTED_WALLET = (walletName?: string) =>
|
|
101
|
+
new StarknetSdkError(
|
|
102
|
+
`The wallet ${walletName || ''} is not supported`,
|
|
103
|
+
ErrorCode.UNSUPPORTED_WALLET,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export const ERR_CHAIN_MISMATCH = (
|
|
107
|
+
walletChainId: StarknetChainId | string,
|
|
108
|
+
expectedEnv: string,
|
|
109
|
+
) => {
|
|
110
|
+
const walletNetwork =
|
|
111
|
+
walletChainId === '0x534e5f4d41494e'
|
|
112
|
+
? 'Starknet Mainnet'
|
|
113
|
+
: walletChainId === '0x534e5f5345504f4c4941'
|
|
114
|
+
? 'Starknet Sepolia'
|
|
115
|
+
: `unknown chain (${walletChainId})`;
|
|
116
|
+
const expectedNetwork = expectedEnv === 'prod' ? 'Starknet Mainnet' : 'Starknet Sepolia';
|
|
117
|
+
return new StarknetSdkError(
|
|
118
|
+
`Wallet network mismatch: Your wallet is connected to ${walletNetwork}, but the SDK is configured for ${expectedNetwork}. Please switch your wallet to ${expectedNetwork}.`,
|
|
119
|
+
ErrorCode.CHAIN_MISMATCH,
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RpcProvider, WalletAccount } from 'starknet';
|
|
2
|
+
|
|
3
|
+
import { StarknetChainId } from './chains';
|
|
4
|
+
|
|
5
|
+
const RPC_PROVIDERS = {
|
|
6
|
+
[StarknetChainId.SN_MAIN]: 'https://rpc.starknet.lava.build:443',
|
|
7
|
+
[StarknetChainId.SN_SEPOLIA]: 'https://rpc.starknet-testnet.lava.build:443',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const providers = new Map<StarknetChainId, RpcProvider>();
|
|
11
|
+
export const getRpcProvider = (
|
|
12
|
+
chainId: StarknetChainId = StarknetChainId.SN_MAIN,
|
|
13
|
+
) => {
|
|
14
|
+
let provider = providers.get(chainId);
|
|
15
|
+
if (!provider) {
|
|
16
|
+
provider = new RpcProvider({ nodeUrl: RPC_PROVIDERS[chainId] });
|
|
17
|
+
providers.set(chainId, provider);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return provider;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ProviderParameters = {
|
|
24
|
+
provider: RpcProvider | WalletAccount;
|
|
25
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ArraySignatureType, ec, WeierstrassSignatureType } from 'starknet';
|
|
2
|
+
|
|
3
|
+
import { ERR_UNKNOWN_SIGNATURE_FORMAT, ERR_UNSUPPORTED_WALLET } from './err';
|
|
4
|
+
import { WalletName } from './wallet-account';
|
|
5
|
+
|
|
6
|
+
export type NormalizedSignature = WeierstrassSignatureType;
|
|
7
|
+
|
|
8
|
+
export function normalizeSignature(
|
|
9
|
+
signature: ArraySignatureType,
|
|
10
|
+
walletName?: WalletName,
|
|
11
|
+
): NormalizedSignature {
|
|
12
|
+
let sigR = undefined;
|
|
13
|
+
let sigS = undefined;
|
|
14
|
+
|
|
15
|
+
if (signature.length === 2) {
|
|
16
|
+
// [r, s] (raw OpenZeppelin or starknet.js)
|
|
17
|
+
const [r, s] = signature;
|
|
18
|
+
sigR = r;
|
|
19
|
+
sigS = s;
|
|
20
|
+
} else if (
|
|
21
|
+
walletName === 'Braavos' ||
|
|
22
|
+
(signature.length === 3 && signature[0] === '1')
|
|
23
|
+
) {
|
|
24
|
+
// Braavos: [version, r, s]
|
|
25
|
+
const [_version, r, s] = signature;
|
|
26
|
+
sigR = r;
|
|
27
|
+
sigS = s;
|
|
28
|
+
} else if (
|
|
29
|
+
walletName === 'Keplr' ||
|
|
30
|
+
(signature.length === 5 &&
|
|
31
|
+
signature.filter(x => x.startsWith('0x')).length === 5)
|
|
32
|
+
) {
|
|
33
|
+
// Probably Keplr: [r low, r high, s low, s high, version]
|
|
34
|
+
|
|
35
|
+
throw ERR_UNSUPPORTED_WALLET(walletName);
|
|
36
|
+
} else {
|
|
37
|
+
// Argent X: [number of signers, type, pubkey, r, s, ...]
|
|
38
|
+
// https://docs.argent.xyz/aa-use-cases/verifying-signatures-and-cosigners#verifying-multi-signatures
|
|
39
|
+
const [
|
|
40
|
+
_signers,
|
|
41
|
+
_signerType1,
|
|
42
|
+
_pubkey1,
|
|
43
|
+
r1,
|
|
44
|
+
s1,
|
|
45
|
+
_signerType2,
|
|
46
|
+
_pubkey2,
|
|
47
|
+
_r2,
|
|
48
|
+
_s2,
|
|
49
|
+
] = signature;
|
|
50
|
+
sigR = r1;
|
|
51
|
+
sigS = s1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (sigR && sigS) {
|
|
55
|
+
const sig = new ec.starkCurve.Signature(BigInt(sigR), BigInt(sigS));
|
|
56
|
+
return sig;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw ERR_UNKNOWN_SIGNATURE_FORMAT;
|
|
60
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { uint256 } from 'starknet';
|
|
2
|
+
|
|
3
|
+
// Example of the proof hex:
|
|
4
|
+
// 0x
|
|
5
|
+
// 0000000000000000000000000000000000000000000000000000000000000020 //index: 0; offset 0x20 -> 32/32 -> 1
|
|
6
|
+
// 0000000000000000000000000000000000000000000000000000000000000004 //index: 1; len 0x4 -> 4
|
|
7
|
+
// 0000000000000000000000000000000000000000000000000000000000000080 //index: 2; el 0: 0x80 -> 128/32 -> 4 +3
|
|
8
|
+
// 00000000000000000000000000000000000000000000000000000000000000e0 //index: 3; el 1: 0xe0 -> 224/32 -> 7 +3
|
|
9
|
+
// 0000000000000000000000000000000000000000000000000000000000000140 //index: 4; el 2: 0x140 -> 320/32 -> 10 +3
|
|
10
|
+
// 00000000000000000000000000000000000000000000000000000000000001a0 //index: 5; el 3: 0x1a0 -> 416/32 -> 13 +3
|
|
11
|
+
// 0000000000000000000000000000000000000000000000000000000000000040 //index: 6;
|
|
12
|
+
// 195a8c89950230b7f0e204dad96026e7fbc3e871c5e36179d3398e0b71d93a6b //index: 7; tuple 1
|
|
13
|
+
// 12f0bc183214345ec0931a02f1798230da7ba0735176cb203342e34086ecd9c7 //index: 8;
|
|
14
|
+
// 0000000000000000000000000000000000000000000000000000000000000040 //index: 9;
|
|
15
|
+
// e57eb39fad14be8803853f9dce2a1aeb13e8e6a7e4b4762601469c4cefee05e6 //index: 10; tuple 2
|
|
16
|
+
// 1ad93c8fb90e282f6e41aaaca23d0ddc088fdd2da26188d0f89c0049c16f6fa4 //index: 11;
|
|
17
|
+
// 0000000000000000000000000000000000000000000000000000000000000040 //index: 10;
|
|
18
|
+
// 3b8cc5c7440647ee1345785a4167d8b119ecd7a0315ec4e112ae27e7cd7f8e36 //index: 13; tuple 3
|
|
19
|
+
// 6093f2edd4a8b54d28ecc26b595402f63cfd90b76f329e37d8d48eb0c64a353e //index: 14;
|
|
20
|
+
// 0000000000000000000000000000000000000000000000000000000000000040 //index: 15;
|
|
21
|
+
// 65b8ea7c620f8b206e94e4b0415cb29ae049c13147593895ff33fcf1ab871345 //index: 16; tuple 4
|
|
22
|
+
// 3bd0794b51ae3db9c8c90a89c7843150c812c119f7faa9997d0a942253e7cb4c //index: 17;
|
|
23
|
+
|
|
24
|
+
export function parseProofHexToU256Tuples(proofHex: string) {
|
|
25
|
+
let calldataHex = proofHex;
|
|
26
|
+
if (calldataHex.startsWith('0x')) calldataHex = calldataHex.slice(2);
|
|
27
|
+
const words = calldataHex.match(/.{64}/g); // each word = 32 bytes = 64 hex chars
|
|
28
|
+
if (!words) throw new Error('Invalid hex string');
|
|
29
|
+
|
|
30
|
+
const offsetToArray = Number.parseInt(words[0], 16) / 32;
|
|
31
|
+
const arrayLength = Number.parseInt(words[offsetToArray], 16);
|
|
32
|
+
const offsetBase = offsetToArray + 1; // where offsets to elements start
|
|
33
|
+
|
|
34
|
+
const result = [];
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
37
|
+
const tupleByteOffset = Number.parseInt(words[offsetBase + i], 16); // in bytes
|
|
38
|
+
|
|
39
|
+
// Dynamic tuple layout:
|
|
40
|
+
// [0]: skip
|
|
41
|
+
// [1]: u256 low
|
|
42
|
+
// [2]: u256 high
|
|
43
|
+
const skip = 1;
|
|
44
|
+
const tupleWordIndex = tupleByteOffset / 32 + offsetBase + skip;
|
|
45
|
+
|
|
46
|
+
const r = `0x${words[tupleWordIndex]}`;
|
|
47
|
+
const s = `0x${words[tupleWordIndex + 1]}`;
|
|
48
|
+
|
|
49
|
+
const uint256r = uint256.bnToUint256(r);
|
|
50
|
+
const uint256s = uint256.bnToUint256(s);
|
|
51
|
+
|
|
52
|
+
result.push({ 0: uint256r, 1: uint256s });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { TypedData, TypedDataRevision } from 'starknet';
|
|
2
|
+
|
|
3
|
+
import { StarknetChainId } from './chains';
|
|
4
|
+
|
|
5
|
+
export const SIGN_MESSAGE_TYPED_DATA = (
|
|
6
|
+
chainId: StarknetChainId,
|
|
7
|
+
message: string,
|
|
8
|
+
): TypedData => ({
|
|
9
|
+
types: {
|
|
10
|
+
PersonalMessage: [{ name: 'message', type: 'string' }],
|
|
11
|
+
StarknetDomain: [
|
|
12
|
+
{
|
|
13
|
+
name: 'name',
|
|
14
|
+
type: 'shortstring',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'chainId',
|
|
18
|
+
type: 'shortstring',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'version',
|
|
22
|
+
type: 'shortstring',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
primaryType: 'PersonalMessage',
|
|
27
|
+
domain: {
|
|
28
|
+
name: 'Lombard Staked Bitcoin',
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
revision: TypedDataRevision.ACTIVE,
|
|
31
|
+
chainId,
|
|
32
|
+
},
|
|
33
|
+
message: {
|
|
34
|
+
message: message,
|
|
35
|
+
},
|
|
36
|
+
});
|