@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 +63 -0
- package/dist/starknet/btcrelay/StarknetBtcRelay.js +3 -3
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/starknet/btcrelay/StarknetBtcRelay.ts +3 -3
- package/src/starknet/wallet/accounts/DeployedStarkCurveWallet.ts +125 -0
- package/src/starknet/wallet/accounts/StarknetKeypairWallet.ts +8 -1
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
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
|
-
*
|
|
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
|
*/
|