@atomiqlabs/chain-starknet 8.4.4 → 8.5.0

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 CHANGED
@@ -30,6 +30,69 @@ Import backend-only utilities from the dedicated `node` subpath:
30
30
  import {StarknetChainEvents, StarknetPersistentSigner} from "@atomiqlabs/chain-starknet/node";
31
31
  ```
32
32
 
33
+ ## Signers
34
+
35
+ ### Browser wallets
36
+
37
+ For connecting to browser wallets (Braavos, Xverse, Argent/Ready...) from within browser environments,
38
+ use `StarknetSigner` with a get-starknet `WalletAccount` — the extension handles signing.
39
+
40
+ ```ts
41
+ import { connect } from "@starknet-io/get-starknet";
42
+ import { WalletAccount } from "starknet";
43
+ import { StarknetBrowserSigner } from "@atomiqlabs/chain-starknet";
44
+
45
+ // Connect the starknet wallet
46
+ const swo = await connect();
47
+
48
+ const provider = new RpcProvider({nodeUrl: starknetRpc});
49
+ const walletAccount = await WalletAccount.connect(provider, swo);
50
+ const wallet = new StarknetBrowserSigner(walletAccount);
51
+ ```
52
+
53
+ ### New keypair based wallets
54
+
55
+ For a programmatic integration driven by a raw private key using a simple single-key controlled OpenZeppelin
56
+ account. Use this only for newly created accounts, if you are bringing in your existing key from an already
57
+ deployed wallet account (Braavos, Xverse and Argent/Ready accounts), use the `DeployedStarkCurveWallet` described
58
+ in [Already deployed wallets](#already-deployed-wallets).
59
+
60
+ ```ts
61
+ import {StarknetSigner, StarknetKeypairWallet} from "@atomiqlabs/chain-starknet";
62
+ import {RpcProvider} from "starknet";
63
+
64
+ // Generate random private key
65
+ const starknetKey: string = StarknetKeypairWallet.generateRandomPrivateKey();
66
+
67
+ const provider = new RpcProvider({nodeUrl: starknetRpc});
68
+ const wallet = new StarknetKeypairWallet(provider, starknetKey);
69
+ const starknetSigner = new StarknetSigner(wallet);
70
+ ```
71
+
72
+ The account contract does not need to be deployed up front: the SDK automatically deploys it with the first
73
+ swap transaction (paying the deployment fee from the account's STRK balance).
74
+
75
+ ### Already deployed wallets
76
+
77
+ For a programmatic integration driven by a raw private key controlling an **already-deployed**
78
+ account (like existing Braavos, Xverse and Argent/Ready accounts), use `DeployedStarkCurveWallet`.
79
+ A Starknet address depends on the account's class hash, so the address is passed explicitly rather
80
+ than derived. Prefer `createAndVerify` over using the constructor, which confirms — via the account's
81
+ own on-chain signature validation — that the key controls the address:
82
+
83
+ ```ts
84
+ import {StarknetSigner, DeployedStarkCurveWallet} from "@atomiqlabs/chain-starknet";
85
+ import {RpcProvider} from "starknet";
86
+
87
+ const provider = new RpcProvider({nodeUrl: starknetRpc});
88
+ const wallet = await DeployedStarkCurveWallet.createAndVerify(provider, privateKey, address);
89
+ const signer = new StarknetSigner(wallet);
90
+ ```
91
+
92
+ It throws `AccountNotDeployedError` if the account isn't deployed, or `WalletVerificationError`
93
+ if the key doesn't control it (or the account needs a scheme a single Stark-curve key can't
94
+ satisfy, e.g. multisig). The bare constructor performs no such check — call `verifyWallet()` first.
95
+
33
96
  ## Supported Chains
34
97
 
35
98
  This package exports a single Starknet initializer:
@@ -220,7 +220,7 @@ class StarknetBtcRelay extends StarknetContractBase_1.StarknetContractBase {
220
220
  return null;
221
221
  }
222
222
  catch (e) {
223
- if (e.baseError?.code === 40 && e.baseError?.revert_error != null)
223
+ if (e.baseError?.code === 40 && e.baseError?.data?.revert_error != null)
224
224
  return null;
225
225
  throw e;
226
226
  }
@@ -243,7 +243,7 @@ class StarknetBtcRelay extends StarknetContractBase_1.StarknetContractBase {
243
243
  return null;
244
244
  }
245
245
  catch (e) {
246
- if (e.baseError?.code === 40 && e.baseError?.revert_error != null)
246
+ if (e.baseError?.code === 40 && e.baseError?.data?.revert_error != null)
247
247
  return null;
248
248
  throw e;
249
249
  }
@@ -262,7 +262,7 @@ class StarknetBtcRelay extends StarknetContractBase_1.StarknetContractBase {
262
262
  const [isInBtcMainChain, btcRelayCommitHash] = await Promise.all([
263
263
  this._bitcoinRpc.isInMainChain(blockHashHex),
264
264
  this.contract.get_commit_hash(storedHeader.getBlockheight()).catch(e => {
265
- if (e.baseError?.code === 40 && e.baseError?.revert_error != null)
265
+ if (e.baseError?.code === 40 && e.baseError?.data?.revert_error != null)
266
266
  return 0n;
267
267
  throw e;
268
268
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/chain-starknet",
3
- "version": "8.4.4",
3
+ "version": "8.5.0",
4
4
  "description": "Starknet specific base implementation",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -99,6 +99,7 @@ export {StarknetSuccessAction, StarknetSwapData} from "./starknet/swaps/Starknet
99
99
  export * from "./starknet/wallet/StarknetSigner";
100
100
  export * from "./starknet/wallet/StarknetBrowserSigner";
101
101
  export * from "./starknet/wallet/accounts/StarknetKeypairWallet";
102
+ export * from "./starknet/wallet/accounts/DeployedStarkCurveWallet";
102
103
 
103
104
  export * from "./starknet/StarknetChainType";
104
105
  export * from "./starknet/StarknetInitializer";
@@ -286,7 +286,7 @@ export class StarknetBtcRelay<B extends BtcBlock>
286
286
  const chainCommitment = await this.contract.get_commit_hash(storedBlockHeader.getBlockheight());
287
287
  if(BigInt(chainCommitment)!==BigInt(commitHash)) return null;
288
288
  } catch (e: any) {
289
- if(e.baseError?.code===40 && e.baseError?.revert_error!=null) return null;
289
+ if(e.baseError?.code===40 && e.baseError?.data?.revert_error!=null) return null;
290
290
  throw e;
291
291
  }
292
292
 
@@ -310,7 +310,7 @@ export class StarknetBtcRelay<B extends BtcBlock>
310
310
  const chainCommitment = await this.contract.get_commit_hash(storedBlockHeader.getBlockheight());
311
311
  if(BigInt(chainCommitment)!==BigInt(commitHash)) return null;
312
312
  } catch (e: any) {
313
- if(e.baseError?.code===40 && e.baseError?.revert_error!=null) return null;
313
+ if(e.baseError?.code===40 && e.baseError?.data?.revert_error!=null) return null;
314
314
  throw e;
315
315
  }
316
316
 
@@ -339,7 +339,7 @@ export class StarknetBtcRelay<B extends BtcBlock>
339
339
  const [isInBtcMainChain, btcRelayCommitHash] = await Promise.all([
340
340
  this._bitcoinRpc.isInMainChain(blockHashHex),
341
341
  this.contract.get_commit_hash(storedHeader.getBlockheight()).catch(e => {
342
- if(e.baseError?.code===40 && e.baseError?.revert_error!=null) return 0n;
342
+ if(e.baseError?.code===40 && e.baseError?.data?.revert_error!=null) return 0n;
343
343
  throw e;
344
344
  })
345
345
  ]);
@@ -0,0 +1,125 @@
1
+ import {Account, DeployAccountContractPayload, ec, Provider, TypedData} from "starknet";
2
+ import {toHex} from "../../../utils/Utils";
3
+
4
+ /**
5
+ * Thrown when the target account is not deployed on-chain, so its signature rules can't be checked.
6
+ */
7
+ export class AccountNotDeployedError extends Error {
8
+ constructor(readonly address: string) {
9
+ super("Account "+address+" is not deployed on-chain");
10
+ this.name = "AccountNotDeployedError";
11
+ Object.setPrototypeOf(this, AccountNotDeployedError.prototype);
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Thrown when the deployed account rejects a signature from the key (wrong key, or a scheme a single Stark-curve key can't satisfy, e.g. multisig/guardian/secp256r1).
17
+ */
18
+ export class WalletVerificationError extends Error {
19
+ constructor(readonly address: string) {
20
+ super("Account "+address+" did not accept a signature from the provided private key");
21
+ this.name = "WalletVerificationError";
22
+ Object.setPrototypeOf(this, WalletVerificationError.prototype);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Wallet for an already-deployed Starknet account controlled by a single Stark-curve private key,
28
+ * bound to an explicit `address` (Starknet addresses depend on the account class hash, so a key
29
+ * maps to a different address per wallet vendor). Never self-deploys.
30
+ *
31
+ * Use this when bringing in the key from an already deployed wallet account (Braavos, Xverse or Argent/Ready wallets).
32
+ *
33
+ * If you are creating a new wallet consider using {@link StarknetKeypairWallet}.
34
+ *
35
+ * @remarks Prefer {@link createAndVerify}, or call {@link verifyWallet} before use after using the constructor,
36
+ * these perform an actual check on whether a given key actually controls the wallet account at a given address.
37
+ *
38
+ * @category Wallets
39
+ */
40
+ export class DeployedStarkCurveWallet extends Account {
41
+
42
+ public readonly publicKey: string;
43
+
44
+ /**
45
+ * The constructor does not validate anything. Prefer {@link createAndVerify}, or call
46
+ * {@link verifyWallet} before use — until then the key is not checked to control `address`.
47
+ *
48
+ * @param provider Starknet RPC provider
49
+ * @param privateKey Stark-curve private key of the account, in the 0x... format
50
+ * @param address Address of the **already deployed account**
51
+ */
52
+ constructor(provider: Provider, privateKey: string, address: string) {
53
+ super({provider, address, signer: privateKey, cairoVersion: "1"});
54
+ this.publicKey = ec.starkCurve.getStarkKey(toHex(privateKey));
55
+ }
56
+
57
+ /**
58
+ * Always null — wraps an already-deployed account and never self-deploys.
59
+ */
60
+ public getDeploymentData(): DeployAccountContractPayload | null {
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * @throws {@link AccountNotDeployedError} on CONTRACT_NOT_FOUND; other errors (rate limit, network) propagate.
66
+ */
67
+ protected async assertDeployed(): Promise<void> {
68
+ try {
69
+ await this.getClassHashAt(this.address);
70
+ } catch (e: any) {
71
+ const message = ((e?.message ?? "") + (e?.baseError?.message ?? "")).toLowerCase();
72
+ if (message.includes("contract not found") || e?.code === 20 || e?.baseError?.code === 20)
73
+ throw new AccountNotDeployedError(this.address);
74
+ throw e;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * A fixed SNIP-12 message — the contents are irrelevant, it only needs to be signed and validated.
80
+ */
81
+ protected async getOwnershipMessage(): Promise<TypedData> {
82
+ return {
83
+ types: {
84
+ StarknetDomain: [
85
+ {name: 'name', type: 'shortstring'},
86
+ {name: 'version', type: 'shortstring'},
87
+ {name: 'chainId', type: 'shortstring'},
88
+ {name: 'revision', type: 'shortstring'},
89
+ ],
90
+ Verification: [{name: 'action', type: 'shortstring'}],
91
+ },
92
+ primaryType: 'Verification',
93
+ domain: {name: 'DeployedStarkCurveWallet', version: '1', chainId: await this.getChainId(), revision: '1'},
94
+ message: {action: 'Verify wallet ownership'},
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Verifies the private key controls the bound account by signing a SNIP-12 message and validating it
100
+ * against the account's own on-chain `is_valid_signature` — the canonical check, independent of the
101
+ * account's class hash or signature scheme.
102
+ *
103
+ * @throws {@link AccountNotDeployedError} if the account is not deployed
104
+ * @throws {@link WalletVerificationError} if the account does not accept the key's signature
105
+ */
106
+ public async verifyWallet(): Promise<void> {
107
+ await this.assertDeployed();
108
+ const message = await this.getOwnershipMessage();
109
+ const signature = await this.signMessage(message);
110
+ const valid = await this.verifyMessageInStarknet(message, signature, this.address);
111
+ if (!valid) throw new WalletVerificationError(this.address);
112
+ }
113
+
114
+ /**
115
+ * Use this to create a new wallet instance, prefer it over just using the constructor!
116
+ *
117
+ * Constructs the wallet and verifies wallet account ownership (by running {@link verifyWallet}).
118
+ */
119
+ public static async createAndVerify(provider: Provider, privateKey: string, address: string): Promise<DeployedStarkCurveWallet> {
120
+ const wallet = new DeployedStarkCurveWallet(provider, privateKey, address);
121
+ await wallet.verifyWallet();
122
+ return wallet;
123
+ }
124
+
125
+ }
@@ -5,7 +5,14 @@ import {Buffer} from "buffer";
5
5
  const OZaccountClassHash = '0x00261c293c8084cd79086214176b33e5911677cec55104fddc8d25b0b736dcad';
6
6
 
7
7
  /**
8
- * Keypair-based wallet implementation using OpenZeppelin Account
8
+ * A simple keypair-based wallet implementation with a single key and not-upgradable, uses OpenZeppelin Account.
9
+ * Use this only for new keypair-based wallets which don't require additional features such as guardians, multisigs
10
+ * or upgradability.
11
+ *
12
+ * @remarks Don't use this for already deployed existing wallet account (Braavos, Xverse, Argent/Ready), as it will
13
+ * result in a different address even when used with the same key (this is because Starknet has native account
14
+ * abstraction capabilities and you need a correct combination of account type + key). For already deployed wallet
15
+ * accounts consider using {@link DeployedStarkCurveWallet} instead!
9
16
  *
10
17
  * @category Wallets
11
18
  */