@dynamic-labs-wallet/evm 0.0.28
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/index.cjs.d.ts +1 -0
- package/index.cjs.js +296 -0
- package/index.esm.d.ts +1 -0
- package/index.esm.js +294 -0
- package/package.json +31 -0
- package/src/client/client.d.ts +69 -0
- package/src/client/client.d.ts.map +1 -0
- package/src/client/constants.d.ts +7 -0
- package/src/client/constants.d.ts.map +1 -0
- package/src/client/index.d.ts +2 -0
- package/src/client/index.d.ts.map +1 -0
- package/src/index.d.ts +2 -0
- package/src/index.d.ts.map +1 -0
- package/src/utils.d.ts +4 -0
- package/src/utils.d.ts.map +1 -0
package/index.cjs.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/index.cjs.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var browser = require('@dynamic-labs-wallet/browser');
|
|
4
|
+
var viem = require('viem');
|
|
5
|
+
var chains = require('viem/chains');
|
|
6
|
+
|
|
7
|
+
function _extends() {
|
|
8
|
+
_extends = Object.assign || function assign(target) {
|
|
9
|
+
for(var i = 1; i < arguments.length; i++){
|
|
10
|
+
var source = arguments[i];
|
|
11
|
+
for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
|
|
12
|
+
}
|
|
13
|
+
return target;
|
|
14
|
+
};
|
|
15
|
+
return _extends.apply(this, arguments);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const EVM_SIGN_MESSAGE_PREFIX = `\x19Ethereum Signed Message:\n`;
|
|
19
|
+
// Error messages
|
|
20
|
+
const ERROR_KEYGEN_FAILED = 'Error with keygen';
|
|
21
|
+
const ERROR_CREATE_WALLET_ACCOUNT = 'Error creating evm wallet account';
|
|
22
|
+
const ERROR_SIGN_MESSAGE = 'Error signing message';
|
|
23
|
+
const ERROR_ACCOUNT_ADDRESS_REQUIRED = 'Account address is required';
|
|
24
|
+
const ERROR_VERIFY_MESSAGE_SIGNATURE = 'Error verifying message signature';
|
|
25
|
+
|
|
26
|
+
const formatEVMMessage = (message)=>{
|
|
27
|
+
return `${EVM_SIGN_MESSAGE_PREFIX}${message.length}${message}`;
|
|
28
|
+
};
|
|
29
|
+
const serializeECDSASignature = (signature)=>{
|
|
30
|
+
return viem.serializeSignature({
|
|
31
|
+
r: `0x${Buffer.from(signature.r).toString('hex')}`,
|
|
32
|
+
s: `0x${Buffer.from(signature.s).toString('hex')}`,
|
|
33
|
+
v: BigInt(signature.v)
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
class DynamicEvmWalletClient extends browser.DynamicWalletClient {
|
|
38
|
+
createViemPublicClient({ chain, rpcUrl }) {
|
|
39
|
+
return viem.createPublicClient({
|
|
40
|
+
chain,
|
|
41
|
+
transport: viem.http(rpcUrl)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async createWalletAccount({ thresholdSignatureScheme }) {
|
|
45
|
+
try {
|
|
46
|
+
// Generate key shares for given threshold signature scheme (TSS)
|
|
47
|
+
const { rawPublicKey, clientKeyShares } = await this.keyGen({
|
|
48
|
+
chainName: this.chainName,
|
|
49
|
+
thresholdSignatureScheme
|
|
50
|
+
});
|
|
51
|
+
if (!rawPublicKey || !clientKeyShares) {
|
|
52
|
+
throw new Error(ERROR_KEYGEN_FAILED);
|
|
53
|
+
}
|
|
54
|
+
// Get EVM address from public key
|
|
55
|
+
const { accountAddress, publicKeyHex } = await this.deriveAccountAddress({
|
|
56
|
+
rawPublicKey: rawPublicKey
|
|
57
|
+
});
|
|
58
|
+
// Refresh user to get the latest user data
|
|
59
|
+
const refreshedUser = await this.apiClient.refreshUser();
|
|
60
|
+
// Find the new wallet in the user's verified credentials
|
|
61
|
+
const newWallet = refreshedUser.user.verifiedCredentials.find((wallet)=>wallet.address.toLowerCase() === accountAddress.toLowerCase());
|
|
62
|
+
const newWalletId = newWallet.id;
|
|
63
|
+
// Store the new wallet in the wallet map
|
|
64
|
+
this.walletMap[accountAddress] = {
|
|
65
|
+
accountAddress,
|
|
66
|
+
walletId: newWalletId,
|
|
67
|
+
chainName: this.chainName,
|
|
68
|
+
clientKeyShares: clientKeyShares,
|
|
69
|
+
thresholdSignatureScheme
|
|
70
|
+
};
|
|
71
|
+
// Backup the new wallet
|
|
72
|
+
await this.storeEncryptedBackupByWallet({
|
|
73
|
+
accountAddress,
|
|
74
|
+
password: undefined
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
accountAddress,
|
|
78
|
+
rawPublicKey,
|
|
79
|
+
publicKeyHex,
|
|
80
|
+
clientKeyShares
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.logger.error(ERROR_CREATE_WALLET_ACCOUNT, error);
|
|
84
|
+
throw new Error(ERROR_CREATE_WALLET_ACCOUNT);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async signMessage({ message, accountAddress }) {
|
|
88
|
+
try {
|
|
89
|
+
if (!accountAddress) {
|
|
90
|
+
throw new Error(ERROR_ACCOUNT_ADDRESS_REQUIRED);
|
|
91
|
+
}
|
|
92
|
+
// Format the message for EVM signing
|
|
93
|
+
const formattedMessage = formatEVMMessage(message);
|
|
94
|
+
// Sign the message using MPC
|
|
95
|
+
const signatureEcdsa = await this.sign({
|
|
96
|
+
message: formattedMessage,
|
|
97
|
+
accountAddress: accountAddress,
|
|
98
|
+
chainName: this.chainName
|
|
99
|
+
});
|
|
100
|
+
// Serialize the signature
|
|
101
|
+
const serializedSignature = serializeECDSASignature(signatureEcdsa);
|
|
102
|
+
return serializedSignature;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
this.logger.error(ERROR_SIGN_MESSAGE, error);
|
|
105
|
+
throw new Error(ERROR_SIGN_MESSAGE);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async verifyMessageSignature({ accountAddress, message, signature }) {
|
|
109
|
+
try {
|
|
110
|
+
// Verify the signature using the public client
|
|
111
|
+
const publicClient = this.createViemPublicClient({
|
|
112
|
+
chain: chains.mainnet
|
|
113
|
+
});
|
|
114
|
+
const verified = await publicClient.verifyMessage({
|
|
115
|
+
address: accountAddress,
|
|
116
|
+
message,
|
|
117
|
+
signature: signature
|
|
118
|
+
});
|
|
119
|
+
return verified;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
this.logger.error(ERROR_VERIFY_MESSAGE_SIGNATURE, error);
|
|
122
|
+
throw new Error(ERROR_VERIFY_MESSAGE_SIGNATURE);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async signTransaction({ chain, senderAddress, toAddress, value, gas, maxFeePerGas, maxPriorityFeePerGas, rpcUrl, broadcastTxn = false }) {
|
|
126
|
+
const publicClient = this.createViemPublicClient({
|
|
127
|
+
chain,
|
|
128
|
+
rpcUrl
|
|
129
|
+
});
|
|
130
|
+
// Get the balance of the sender address
|
|
131
|
+
const balance = await publicClient.getBalance({
|
|
132
|
+
address: senderAddress
|
|
133
|
+
});
|
|
134
|
+
// Calculate total cost (gas * maxFeePerGas + value)
|
|
135
|
+
const totalCost = gas * maxFeePerGas + value;
|
|
136
|
+
if (balance < totalCost) {
|
|
137
|
+
throw new Error(`Insufficient funds. Required: ${totalCost}, Available: ${balance}`);
|
|
138
|
+
}
|
|
139
|
+
const walletClient = viem.createWalletClient({
|
|
140
|
+
chain,
|
|
141
|
+
transport: viem.http(rpcUrl),
|
|
142
|
+
account: senderAddress
|
|
143
|
+
});
|
|
144
|
+
const nonce = await publicClient.getTransactionCount({
|
|
145
|
+
address: senderAddress
|
|
146
|
+
});
|
|
147
|
+
const transactionRequest = {
|
|
148
|
+
to: toAddress,
|
|
149
|
+
value,
|
|
150
|
+
chainId: chain.id,
|
|
151
|
+
type: 'eip1559',
|
|
152
|
+
gas,
|
|
153
|
+
maxFeePerGas,
|
|
154
|
+
maxPriorityFeePerGas,
|
|
155
|
+
nonce,
|
|
156
|
+
from: senderAddress,
|
|
157
|
+
accessList: []
|
|
158
|
+
};
|
|
159
|
+
const preparedTx = await walletClient.prepareTransactionRequest(transactionRequest);
|
|
160
|
+
// Get the transaction hash that needs to be signed
|
|
161
|
+
const unsignedTx = _extends({}, preparedTx, {
|
|
162
|
+
type: 'eip1559'
|
|
163
|
+
});
|
|
164
|
+
// Get the keccak256 hash of the transaction
|
|
165
|
+
const serializedTx = viem.serializeTransaction(unsignedTx);
|
|
166
|
+
const serializedTxBytes = Uint8Array.from(Buffer.from(serializedTx.slice(2), 'hex'));
|
|
167
|
+
if (!(serializedTxBytes instanceof Uint8Array)) {
|
|
168
|
+
throw new Error('Invalid serializedTxBytes');
|
|
169
|
+
}
|
|
170
|
+
// Get signature using MPC (this will coordinate with server party)
|
|
171
|
+
const signatureEcdsa = await this.sign({
|
|
172
|
+
message: serializedTxBytes,
|
|
173
|
+
accountAddress: senderAddress,
|
|
174
|
+
chainName: this.chainName
|
|
175
|
+
});
|
|
176
|
+
if (!('r' in signatureEcdsa && 's' in signatureEcdsa && 'v' in signatureEcdsa)) {
|
|
177
|
+
throw new Error('Invalid signature format returned from MPC signing');
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const r = `0x${Buffer.from(signatureEcdsa.r).toString('hex')}`;
|
|
181
|
+
const s = `0x${Buffer.from(signatureEcdsa.s).toString('hex')}`;
|
|
182
|
+
const v = BigInt(signatureEcdsa.v);
|
|
183
|
+
const signedTx = {
|
|
184
|
+
to: unsignedTx.to,
|
|
185
|
+
value: unsignedTx.value,
|
|
186
|
+
chainId: chain.id,
|
|
187
|
+
type: 'eip1559',
|
|
188
|
+
gas: unsignedTx.gas,
|
|
189
|
+
maxFeePerGas: unsignedTx.maxFeePerGas,
|
|
190
|
+
maxPriorityFeePerGas: unsignedTx.maxPriorityFeePerGas,
|
|
191
|
+
nonce: unsignedTx.nonce,
|
|
192
|
+
r: r,
|
|
193
|
+
s: s,
|
|
194
|
+
v: v
|
|
195
|
+
};
|
|
196
|
+
// Serialize the signed transaction
|
|
197
|
+
const serializedSignedTx = viem.serializeTransaction(signedTx);
|
|
198
|
+
let txHash;
|
|
199
|
+
// Send the raw transaction
|
|
200
|
+
if (broadcastTxn) {
|
|
201
|
+
const sentTxHash = await walletClient.sendRawTransaction({
|
|
202
|
+
serializedTransaction: serializedSignedTx
|
|
203
|
+
});
|
|
204
|
+
this.logger.info('Transaction broadcasted! Hash:', sentTxHash);
|
|
205
|
+
txHash = sentTxHash;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
txHash,
|
|
209
|
+
signedTx: serializedSignedTx
|
|
210
|
+
};
|
|
211
|
+
} catch (error) {
|
|
212
|
+
this.logger.error('Error signing transaction:', error);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async deriveAccountAddress({ rawPublicKey }) {
|
|
217
|
+
const serializedUncompressed = rawPublicKey.serializeUncompressed();
|
|
218
|
+
const firstByteRemoved = serializedUncompressed.slice(1);
|
|
219
|
+
const hashed = browser.MessageHash.keccak256(firstByteRemoved).bytes;
|
|
220
|
+
const lastTwentyBytes = hashed.slice(-20);
|
|
221
|
+
const accountAddress = '0x' + Buffer.from(lastTwentyBytes).toString('hex');
|
|
222
|
+
const publicKeyHex = rawPublicKey.pubKeyAsHex();
|
|
223
|
+
return {
|
|
224
|
+
accountAddress,
|
|
225
|
+
publicKeyHex
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async exportPrivateKey({ accountAddress }) {
|
|
229
|
+
const { derivedPrivateKey } = await this.exportKey({
|
|
230
|
+
accountAddress,
|
|
231
|
+
chainName: this.chainName
|
|
232
|
+
});
|
|
233
|
+
return {
|
|
234
|
+
derivedPrivateKey
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async offlineExportPrivateKey({ keyShares }) {
|
|
238
|
+
const { derivedPrivateKey } = await this.offlineExportKey({
|
|
239
|
+
chainName: this.chainName,
|
|
240
|
+
keyShares
|
|
241
|
+
});
|
|
242
|
+
return {
|
|
243
|
+
derivedPrivateKey
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
async importPrivateKey({ privateKey, chainName, thresholdSignatureScheme }) {
|
|
247
|
+
// TODO: validate private key for EVM
|
|
248
|
+
const { rawPublicKey, clientKeyShares } = await this.importRawPrivateKey({
|
|
249
|
+
chainName,
|
|
250
|
+
privateKey,
|
|
251
|
+
thresholdSignatureScheme
|
|
252
|
+
});
|
|
253
|
+
if (!rawPublicKey || !clientKeyShares) {
|
|
254
|
+
throw new Error('Error creating wallet account');
|
|
255
|
+
}
|
|
256
|
+
const { accountAddress, publicKeyHex } = await this.deriveAccountAddress({
|
|
257
|
+
rawPublicKey: rawPublicKey
|
|
258
|
+
});
|
|
259
|
+
const refreshedUser = await this.apiClient.refreshUser();
|
|
260
|
+
const newWallet = refreshedUser.user.verifiedCredentials.find((wallet)=>wallet.address.toLowerCase() === accountAddress.toLowerCase());
|
|
261
|
+
const newWalletId = newWallet.id;
|
|
262
|
+
this.walletMap[accountAddress] = {
|
|
263
|
+
accountAddress,
|
|
264
|
+
walletId: newWalletId,
|
|
265
|
+
chainName: this.chainName,
|
|
266
|
+
clientKeyShares,
|
|
267
|
+
thresholdSignatureScheme
|
|
268
|
+
};
|
|
269
|
+
await this.storeEncryptedBackupByWallet({
|
|
270
|
+
accountAddress
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
accountAddress,
|
|
274
|
+
rawPublicKey,
|
|
275
|
+
publicKeyHex,
|
|
276
|
+
clientKeyShares
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
async getEvmWallets() {
|
|
280
|
+
const wallets = await this.getWallets();
|
|
281
|
+
const evmWallets = wallets.filter((wallet)=>wallet.chainName === 'eip155');
|
|
282
|
+
return evmWallets;
|
|
283
|
+
}
|
|
284
|
+
constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug }){
|
|
285
|
+
super({
|
|
286
|
+
environmentId,
|
|
287
|
+
authToken,
|
|
288
|
+
baseApiUrl,
|
|
289
|
+
baseMPCRelayApiUrl,
|
|
290
|
+
storageKey,
|
|
291
|
+
debug
|
|
292
|
+
}), this.chainName = 'EVM';
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
exports.DynamicEvmWalletClient = DynamicEvmWalletClient;
|
package/index.esm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/index.esm.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { DynamicWalletClient, MessageHash } from '@dynamic-labs-wallet/browser';
|
|
2
|
+
import { serializeSignature, createPublicClient, http, createWalletClient, serializeTransaction } from 'viem';
|
|
3
|
+
import { mainnet } from 'viem/chains';
|
|
4
|
+
|
|
5
|
+
function _extends() {
|
|
6
|
+
_extends = Object.assign || function assign(target) {
|
|
7
|
+
for(var i = 1; i < arguments.length; i++){
|
|
8
|
+
var source = arguments[i];
|
|
9
|
+
for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
|
|
10
|
+
}
|
|
11
|
+
return target;
|
|
12
|
+
};
|
|
13
|
+
return _extends.apply(this, arguments);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const EVM_SIGN_MESSAGE_PREFIX = `\x19Ethereum Signed Message:\n`;
|
|
17
|
+
// Error messages
|
|
18
|
+
const ERROR_KEYGEN_FAILED = 'Error with keygen';
|
|
19
|
+
const ERROR_CREATE_WALLET_ACCOUNT = 'Error creating evm wallet account';
|
|
20
|
+
const ERROR_SIGN_MESSAGE = 'Error signing message';
|
|
21
|
+
const ERROR_ACCOUNT_ADDRESS_REQUIRED = 'Account address is required';
|
|
22
|
+
const ERROR_VERIFY_MESSAGE_SIGNATURE = 'Error verifying message signature';
|
|
23
|
+
|
|
24
|
+
const formatEVMMessage = (message)=>{
|
|
25
|
+
return `${EVM_SIGN_MESSAGE_PREFIX}${message.length}${message}`;
|
|
26
|
+
};
|
|
27
|
+
const serializeECDSASignature = (signature)=>{
|
|
28
|
+
return serializeSignature({
|
|
29
|
+
r: `0x${Buffer.from(signature.r).toString('hex')}`,
|
|
30
|
+
s: `0x${Buffer.from(signature.s).toString('hex')}`,
|
|
31
|
+
v: BigInt(signature.v)
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
class DynamicEvmWalletClient extends DynamicWalletClient {
|
|
36
|
+
createViemPublicClient({ chain, rpcUrl }) {
|
|
37
|
+
return createPublicClient({
|
|
38
|
+
chain,
|
|
39
|
+
transport: http(rpcUrl)
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async createWalletAccount({ thresholdSignatureScheme }) {
|
|
43
|
+
try {
|
|
44
|
+
// Generate key shares for given threshold signature scheme (TSS)
|
|
45
|
+
const { rawPublicKey, clientKeyShares } = await this.keyGen({
|
|
46
|
+
chainName: this.chainName,
|
|
47
|
+
thresholdSignatureScheme
|
|
48
|
+
});
|
|
49
|
+
if (!rawPublicKey || !clientKeyShares) {
|
|
50
|
+
throw new Error(ERROR_KEYGEN_FAILED);
|
|
51
|
+
}
|
|
52
|
+
// Get EVM address from public key
|
|
53
|
+
const { accountAddress, publicKeyHex } = await this.deriveAccountAddress({
|
|
54
|
+
rawPublicKey: rawPublicKey
|
|
55
|
+
});
|
|
56
|
+
// Refresh user to get the latest user data
|
|
57
|
+
const refreshedUser = await this.apiClient.refreshUser();
|
|
58
|
+
// Find the new wallet in the user's verified credentials
|
|
59
|
+
const newWallet = refreshedUser.user.verifiedCredentials.find((wallet)=>wallet.address.toLowerCase() === accountAddress.toLowerCase());
|
|
60
|
+
const newWalletId = newWallet.id;
|
|
61
|
+
// Store the new wallet in the wallet map
|
|
62
|
+
this.walletMap[accountAddress] = {
|
|
63
|
+
accountAddress,
|
|
64
|
+
walletId: newWalletId,
|
|
65
|
+
chainName: this.chainName,
|
|
66
|
+
clientKeyShares: clientKeyShares,
|
|
67
|
+
thresholdSignatureScheme
|
|
68
|
+
};
|
|
69
|
+
// Backup the new wallet
|
|
70
|
+
await this.storeEncryptedBackupByWallet({
|
|
71
|
+
accountAddress,
|
|
72
|
+
password: undefined
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
accountAddress,
|
|
76
|
+
rawPublicKey,
|
|
77
|
+
publicKeyHex,
|
|
78
|
+
clientKeyShares
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this.logger.error(ERROR_CREATE_WALLET_ACCOUNT, error);
|
|
82
|
+
throw new Error(ERROR_CREATE_WALLET_ACCOUNT);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async signMessage({ message, accountAddress }) {
|
|
86
|
+
try {
|
|
87
|
+
if (!accountAddress) {
|
|
88
|
+
throw new Error(ERROR_ACCOUNT_ADDRESS_REQUIRED);
|
|
89
|
+
}
|
|
90
|
+
// Format the message for EVM signing
|
|
91
|
+
const formattedMessage = formatEVMMessage(message);
|
|
92
|
+
// Sign the message using MPC
|
|
93
|
+
const signatureEcdsa = await this.sign({
|
|
94
|
+
message: formattedMessage,
|
|
95
|
+
accountAddress: accountAddress,
|
|
96
|
+
chainName: this.chainName
|
|
97
|
+
});
|
|
98
|
+
// Serialize the signature
|
|
99
|
+
const serializedSignature = serializeECDSASignature(signatureEcdsa);
|
|
100
|
+
return serializedSignature;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
this.logger.error(ERROR_SIGN_MESSAGE, error);
|
|
103
|
+
throw new Error(ERROR_SIGN_MESSAGE);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async verifyMessageSignature({ accountAddress, message, signature }) {
|
|
107
|
+
try {
|
|
108
|
+
// Verify the signature using the public client
|
|
109
|
+
const publicClient = this.createViemPublicClient({
|
|
110
|
+
chain: mainnet
|
|
111
|
+
});
|
|
112
|
+
const verified = await publicClient.verifyMessage({
|
|
113
|
+
address: accountAddress,
|
|
114
|
+
message,
|
|
115
|
+
signature: signature
|
|
116
|
+
});
|
|
117
|
+
return verified;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
this.logger.error(ERROR_VERIFY_MESSAGE_SIGNATURE, error);
|
|
120
|
+
throw new Error(ERROR_VERIFY_MESSAGE_SIGNATURE);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async signTransaction({ chain, senderAddress, toAddress, value, gas, maxFeePerGas, maxPriorityFeePerGas, rpcUrl, broadcastTxn = false }) {
|
|
124
|
+
const publicClient = this.createViemPublicClient({
|
|
125
|
+
chain,
|
|
126
|
+
rpcUrl
|
|
127
|
+
});
|
|
128
|
+
// Get the balance of the sender address
|
|
129
|
+
const balance = await publicClient.getBalance({
|
|
130
|
+
address: senderAddress
|
|
131
|
+
});
|
|
132
|
+
// Calculate total cost (gas * maxFeePerGas + value)
|
|
133
|
+
const totalCost = gas * maxFeePerGas + value;
|
|
134
|
+
if (balance < totalCost) {
|
|
135
|
+
throw new Error(`Insufficient funds. Required: ${totalCost}, Available: ${balance}`);
|
|
136
|
+
}
|
|
137
|
+
const walletClient = createWalletClient({
|
|
138
|
+
chain,
|
|
139
|
+
transport: http(rpcUrl),
|
|
140
|
+
account: senderAddress
|
|
141
|
+
});
|
|
142
|
+
const nonce = await publicClient.getTransactionCount({
|
|
143
|
+
address: senderAddress
|
|
144
|
+
});
|
|
145
|
+
const transactionRequest = {
|
|
146
|
+
to: toAddress,
|
|
147
|
+
value,
|
|
148
|
+
chainId: chain.id,
|
|
149
|
+
type: 'eip1559',
|
|
150
|
+
gas,
|
|
151
|
+
maxFeePerGas,
|
|
152
|
+
maxPriorityFeePerGas,
|
|
153
|
+
nonce,
|
|
154
|
+
from: senderAddress,
|
|
155
|
+
accessList: []
|
|
156
|
+
};
|
|
157
|
+
const preparedTx = await walletClient.prepareTransactionRequest(transactionRequest);
|
|
158
|
+
// Get the transaction hash that needs to be signed
|
|
159
|
+
const unsignedTx = _extends({}, preparedTx, {
|
|
160
|
+
type: 'eip1559'
|
|
161
|
+
});
|
|
162
|
+
// Get the keccak256 hash of the transaction
|
|
163
|
+
const serializedTx = serializeTransaction(unsignedTx);
|
|
164
|
+
const serializedTxBytes = Uint8Array.from(Buffer.from(serializedTx.slice(2), 'hex'));
|
|
165
|
+
if (!(serializedTxBytes instanceof Uint8Array)) {
|
|
166
|
+
throw new Error('Invalid serializedTxBytes');
|
|
167
|
+
}
|
|
168
|
+
// Get signature using MPC (this will coordinate with server party)
|
|
169
|
+
const signatureEcdsa = await this.sign({
|
|
170
|
+
message: serializedTxBytes,
|
|
171
|
+
accountAddress: senderAddress,
|
|
172
|
+
chainName: this.chainName
|
|
173
|
+
});
|
|
174
|
+
if (!('r' in signatureEcdsa && 's' in signatureEcdsa && 'v' in signatureEcdsa)) {
|
|
175
|
+
throw new Error('Invalid signature format returned from MPC signing');
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const r = `0x${Buffer.from(signatureEcdsa.r).toString('hex')}`;
|
|
179
|
+
const s = `0x${Buffer.from(signatureEcdsa.s).toString('hex')}`;
|
|
180
|
+
const v = BigInt(signatureEcdsa.v);
|
|
181
|
+
const signedTx = {
|
|
182
|
+
to: unsignedTx.to,
|
|
183
|
+
value: unsignedTx.value,
|
|
184
|
+
chainId: chain.id,
|
|
185
|
+
type: 'eip1559',
|
|
186
|
+
gas: unsignedTx.gas,
|
|
187
|
+
maxFeePerGas: unsignedTx.maxFeePerGas,
|
|
188
|
+
maxPriorityFeePerGas: unsignedTx.maxPriorityFeePerGas,
|
|
189
|
+
nonce: unsignedTx.nonce,
|
|
190
|
+
r: r,
|
|
191
|
+
s: s,
|
|
192
|
+
v: v
|
|
193
|
+
};
|
|
194
|
+
// Serialize the signed transaction
|
|
195
|
+
const serializedSignedTx = serializeTransaction(signedTx);
|
|
196
|
+
let txHash;
|
|
197
|
+
// Send the raw transaction
|
|
198
|
+
if (broadcastTxn) {
|
|
199
|
+
const sentTxHash = await walletClient.sendRawTransaction({
|
|
200
|
+
serializedTransaction: serializedSignedTx
|
|
201
|
+
});
|
|
202
|
+
this.logger.info('Transaction broadcasted! Hash:', sentTxHash);
|
|
203
|
+
txHash = sentTxHash;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
txHash,
|
|
207
|
+
signedTx: serializedSignedTx
|
|
208
|
+
};
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.logger.error('Error signing transaction:', error);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async deriveAccountAddress({ rawPublicKey }) {
|
|
215
|
+
const serializedUncompressed = rawPublicKey.serializeUncompressed();
|
|
216
|
+
const firstByteRemoved = serializedUncompressed.slice(1);
|
|
217
|
+
const hashed = MessageHash.keccak256(firstByteRemoved).bytes;
|
|
218
|
+
const lastTwentyBytes = hashed.slice(-20);
|
|
219
|
+
const accountAddress = '0x' + Buffer.from(lastTwentyBytes).toString('hex');
|
|
220
|
+
const publicKeyHex = rawPublicKey.pubKeyAsHex();
|
|
221
|
+
return {
|
|
222
|
+
accountAddress,
|
|
223
|
+
publicKeyHex
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async exportPrivateKey({ accountAddress }) {
|
|
227
|
+
const { derivedPrivateKey } = await this.exportKey({
|
|
228
|
+
accountAddress,
|
|
229
|
+
chainName: this.chainName
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
derivedPrivateKey
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
async offlineExportPrivateKey({ keyShares }) {
|
|
236
|
+
const { derivedPrivateKey } = await this.offlineExportKey({
|
|
237
|
+
chainName: this.chainName,
|
|
238
|
+
keyShares
|
|
239
|
+
});
|
|
240
|
+
return {
|
|
241
|
+
derivedPrivateKey
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
async importPrivateKey({ privateKey, chainName, thresholdSignatureScheme }) {
|
|
245
|
+
// TODO: validate private key for EVM
|
|
246
|
+
const { rawPublicKey, clientKeyShares } = await this.importRawPrivateKey({
|
|
247
|
+
chainName,
|
|
248
|
+
privateKey,
|
|
249
|
+
thresholdSignatureScheme
|
|
250
|
+
});
|
|
251
|
+
if (!rawPublicKey || !clientKeyShares) {
|
|
252
|
+
throw new Error('Error creating wallet account');
|
|
253
|
+
}
|
|
254
|
+
const { accountAddress, publicKeyHex } = await this.deriveAccountAddress({
|
|
255
|
+
rawPublicKey: rawPublicKey
|
|
256
|
+
});
|
|
257
|
+
const refreshedUser = await this.apiClient.refreshUser();
|
|
258
|
+
const newWallet = refreshedUser.user.verifiedCredentials.find((wallet)=>wallet.address.toLowerCase() === accountAddress.toLowerCase());
|
|
259
|
+
const newWalletId = newWallet.id;
|
|
260
|
+
this.walletMap[accountAddress] = {
|
|
261
|
+
accountAddress,
|
|
262
|
+
walletId: newWalletId,
|
|
263
|
+
chainName: this.chainName,
|
|
264
|
+
clientKeyShares,
|
|
265
|
+
thresholdSignatureScheme
|
|
266
|
+
};
|
|
267
|
+
await this.storeEncryptedBackupByWallet({
|
|
268
|
+
accountAddress
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
accountAddress,
|
|
272
|
+
rawPublicKey,
|
|
273
|
+
publicKeyHex,
|
|
274
|
+
clientKeyShares
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
async getEvmWallets() {
|
|
278
|
+
const wallets = await this.getWallets();
|
|
279
|
+
const evmWallets = wallets.filter((wallet)=>wallet.chainName === 'eip155');
|
|
280
|
+
return evmWallets;
|
|
281
|
+
}
|
|
282
|
+
constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug }){
|
|
283
|
+
super({
|
|
284
|
+
environmentId,
|
|
285
|
+
authToken,
|
|
286
|
+
baseApiUrl,
|
|
287
|
+
baseMPCRelayApiUrl,
|
|
288
|
+
storageKey,
|
|
289
|
+
debug
|
|
290
|
+
}), this.chainName = 'EVM';
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export { DynamicEvmWalletClient };
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dynamic-labs-wallet/evm",
|
|
3
|
+
"version": "0.0.28",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"@dynamic-labs-wallet/browser": "0.0.28"
|
|
6
|
+
},
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"viem": "^2.22.1"
|
|
9
|
+
},
|
|
10
|
+
"nx": {
|
|
11
|
+
"sourceRoot": "packages/evm/src",
|
|
12
|
+
"projectType": "library",
|
|
13
|
+
"name": "evm",
|
|
14
|
+
"targets": {
|
|
15
|
+
"build": {}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./index.cjs.js",
|
|
20
|
+
"module": "./index.esm.js",
|
|
21
|
+
"types": "./index.esm.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
"./package.json": "./package.json",
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./index.esm.d.ts",
|
|
26
|
+
"import": "./index.esm.js",
|
|
27
|
+
"require": "./index.cjs.js",
|
|
28
|
+
"default": "./index.cjs.js"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ClientKeyShare, DynamicWalletClient, EcdsaKeygenResult, EcdsaPublicKey, Ed25519KeygenResult, ThresholdSignatureScheme, DynamicWalletClientProps } from '@dynamic-labs-wallet/browser';
|
|
2
|
+
import { type PublicClient, type Chain, type SignableMessage } from 'viem';
|
|
3
|
+
export declare class DynamicEvmWalletClient extends DynamicWalletClient {
|
|
4
|
+
readonly chainName = "EVM";
|
|
5
|
+
constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug, }: DynamicWalletClientProps);
|
|
6
|
+
createViemPublicClient({ chain, rpcUrl, }: {
|
|
7
|
+
chain: Chain;
|
|
8
|
+
rpcUrl?: string;
|
|
9
|
+
}): PublicClient;
|
|
10
|
+
createWalletAccount({ thresholdSignatureScheme, }: {
|
|
11
|
+
thresholdSignatureScheme: ThresholdSignatureScheme;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
accountAddress: string;
|
|
14
|
+
publicKeyHex: string;
|
|
15
|
+
rawPublicKey: EcdsaPublicKey | Uint8Array | undefined;
|
|
16
|
+
clientKeyShares: ClientKeyShare[];
|
|
17
|
+
}>;
|
|
18
|
+
signMessage({ message, accountAddress, }: {
|
|
19
|
+
message: string;
|
|
20
|
+
accountAddress: string;
|
|
21
|
+
}): Promise<`0x${string}`>;
|
|
22
|
+
verifyMessageSignature({ accountAddress, message, signature, }: {
|
|
23
|
+
accountAddress: string;
|
|
24
|
+
message: SignableMessage;
|
|
25
|
+
signature: any;
|
|
26
|
+
}): Promise<boolean>;
|
|
27
|
+
signTransaction({ chain, senderAddress, toAddress, value, gas, maxFeePerGas, maxPriorityFeePerGas, rpcUrl, broadcastTxn, }: {
|
|
28
|
+
chain: Chain;
|
|
29
|
+
senderAddress: string;
|
|
30
|
+
toAddress: string;
|
|
31
|
+
value: bigint;
|
|
32
|
+
gas: bigint;
|
|
33
|
+
maxFeePerGas: bigint;
|
|
34
|
+
maxPriorityFeePerGas: bigint;
|
|
35
|
+
rpcUrl?: string;
|
|
36
|
+
broadcastTxn?: boolean;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
txHash?: string;
|
|
39
|
+
signedTx: string;
|
|
40
|
+
}>;
|
|
41
|
+
deriveAccountAddress({ rawPublicKey, }: {
|
|
42
|
+
rawPublicKey: EcdsaPublicKey;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
accountAddress: string;
|
|
45
|
+
publicKeyHex: any;
|
|
46
|
+
}>;
|
|
47
|
+
exportPrivateKey({ accountAddress }: {
|
|
48
|
+
accountAddress: string;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
derivedPrivateKey: string | undefined;
|
|
51
|
+
}>;
|
|
52
|
+
offlineExportPrivateKey({ keyShares, }: {
|
|
53
|
+
keyShares: (EcdsaKeygenResult | Ed25519KeygenResult)[];
|
|
54
|
+
}): Promise<{
|
|
55
|
+
derivedPrivateKey: string | undefined;
|
|
56
|
+
}>;
|
|
57
|
+
importPrivateKey({ privateKey, chainName, thresholdSignatureScheme, }: {
|
|
58
|
+
privateKey: string;
|
|
59
|
+
chainName: string;
|
|
60
|
+
thresholdSignatureScheme: ThresholdSignatureScheme;
|
|
61
|
+
}): Promise<{
|
|
62
|
+
accountAddress: string;
|
|
63
|
+
publicKeyHex: string;
|
|
64
|
+
rawPublicKey: EcdsaPublicKey | Uint8Array | undefined;
|
|
65
|
+
clientKeyShares: ClientKeyShare[];
|
|
66
|
+
}>;
|
|
67
|
+
getEvmWallets(): Promise<any>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EAEd,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EAEzB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,KAAK,EAEV,KAAK,eAAe,EACrB,MAAM,MAAM,CAAC;AAWd,qBAAa,sBAAuB,SAAQ,mBAAmB;IAC7D,QAAQ,CAAC,SAAS,SAAS;gBAEf,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,UAAU,EACV,KAAK,GACN,EAAE,wBAAwB;IAW3B,sBAAsB,CAAC,EACrB,KAAK,EACL,MAAM,GACP,EAAE;QACD,KAAK,EAAE,KAAK,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,YAAY;IAOV,mBAAmB,CAAC,EACxB,wBAAwB,GACzB,EAAE;QACD,wBAAwB,EAAE,wBAAwB,CAAC;KACpD,GAAG,OAAO,CAAC;QACV,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,cAAc,GAAG,UAAU,GAAG,SAAS,CAAC;QACtD,eAAe,EAAE,cAAc,EAAE,CAAC;KACnC,CAAC;IAqDI,WAAW,CAAC,EAChB,OAAO,EACP,cAAc,GACf,EAAE;QACD,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;KACxB;IA0BK,sBAAsB,CAAC,EAC3B,cAAc,EACd,OAAO,EACP,SAAS,GACV,EAAE;QACD,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,eAAe,CAAC;QACzB,SAAS,EAAE,GAAG,CAAC;KAChB;IAmBK,eAAe,CAAC,EACpB,KAAK,EACL,aAAa,EACb,SAAS,EACT,KAAK,EACL,GAAG,EACH,YAAY,EACZ,oBAAoB,EACpB,MAAM,EACN,YAAoB,GACrB,EAAE;QACD,KAAK,EAAE,KAAK,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAoHI,oBAAoB,CAAC,EACzB,YAAY,GACb,EAAE;QACD,YAAY,EAAE,cAAc,CAAC;KAC9B;;;;IAUK,gBAAgB,CAAC,EAAE,cAAc,EAAE,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE;;;IAQ/D,uBAAuB,CAAC,EAC5B,SAAS,GACV,EAAE;QACD,SAAS,EAAE,CAAC,iBAAiB,GAAG,mBAAmB,CAAC,EAAE,CAAC;KACxD;;;IAQK,gBAAgB,CAAC,EACrB,UAAU,EACV,SAAS,EACT,wBAAwB,GACzB,EAAE;QACD,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,wBAAwB,EAAE,wBAAwB,CAAC;KACpD,GAAG,OAAO,CAAC;QACV,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,cAAc,GAAG,UAAU,GAAG,SAAS,CAAC;QACtD,eAAe,EAAE,cAAc,EAAE,CAAC;KACnC,CAAC;IAyCI,aAAa;CAOpB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const EVM_SIGN_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n";
|
|
2
|
+
export declare const ERROR_KEYGEN_FAILED = "Error with keygen";
|
|
3
|
+
export declare const ERROR_CREATE_WALLET_ACCOUNT = "Error creating evm wallet account";
|
|
4
|
+
export declare const ERROR_SIGN_MESSAGE = "Error signing message";
|
|
5
|
+
export declare const ERROR_ACCOUNT_ADDRESS_REQUIRED = "Account address is required";
|
|
6
|
+
export declare const ERROR_VERIFY_MESSAGE_SIGNATURE = "Error verifying message signature";
|
|
7
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/client/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,qCAAmC,CAAC;AAGxE,eAAO,MAAM,mBAAmB,sBAAsB,CAAC;AAEvD,eAAO,MAAM,2BAA2B,sCAAsC,CAAC;AAE/E,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAE1D,eAAO,MAAM,8BAA8B,gCAAgC,CAAC;AAE5E,eAAO,MAAM,8BAA8B,sCAAsC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC"}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../packages/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC"}
|
package/src/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../packages/src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,eAAO,MAAM,gBAAgB,YAAa,MAAM,WAE/C,CAAC;AAEF,eAAO,MAAM,uBAAuB,cAAe,cAAc,kBAMhE,CAAC"}
|