@atomiqlabs/chain-starknet 8.4.5 → 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:
|
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";
|
|
@@ -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
|
*/
|