Skip to content

Commit b621abd

Browse files
nooxxloicttn
authored andcommitted
solana: sign transactions with nonce account
1 parent 04d3014 commit b621abd

File tree

5 files changed

+137
-23
lines changed

5 files changed

+137
-23
lines changed

examples/sol.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const apiSecret = fs.readFileSync(__dirname + '/fireblocks_secret.key', 'utf8');
66
const f = async () => {
77
const k = new Kiln({
88
testnet: true,
9-
apiToken: 'kiln_dTkxUTFRdHBMZm9vNFFycFhDSTZCdlJsbjJZang5VnY6bVE3bUYyUExZeDd3LUM2Ty01THJ2QTlyMmVtUG92NzI5ejRqU19FVzQ3UFdkUFdZTmgyMHJ2VWcxcUdjWXNsMg',
9+
apiToken: 'kiln_V1JKOW55SkZMOFZvMzBJYmw0aGQ3bkM3UFpIM2IzeXA6Zi0zcUU3SHNIZTB6WDg2dkVUbUpWbmhLRFFyYmljdm1aQzFqOGcwOWFtY3U0Yk9fZHh6SkVweDJRcUxOVERWMA',
1010
integrations: [
1111
{
1212
name: 'vault1',
@@ -19,10 +19,9 @@ const f = async () => {
1919
});
2020

2121
try {
22-
const tx = await k.sol.craftStakeTx(
23-
'376acfff-e35d-4b7c-90da-c6acb8ea7197',
22+
const tx = await k.sol.craftDeactivateStakeTx(
23+
'AhymNaMJBxLCWRgrNb7Er4bUQoGWpYV2EG4qrGwqzemY',
2424
'4icse2mPXNgyxxn11tVM7sTnSqDqwJSEzdnaCQnRzvA9',
25-
0.1
2625
);
2726
const signedTx = await k.sol.sign('vault1', tx);
2827
const hash = await k.sol.broadcast(signedTx);

src/globals.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const ADDRESSES = {
1010
},
1111
sol: {
1212
devnet: {
13-
voteAccountAddress: 'CzQumNfTV6DJZRkAFk8FpABgDnoJ7aJnssm6S6toYZD4' // random validator vote account
13+
voteAccountAddress: 'ChrXH3zgYkBYe7c9XXrkrajF1yrEca3EvZe2ErEBJPJf' // random validator vote account
1414
},
1515
mainnet: {
1616
voteAccountAddress: 'DdCNGDpP7qMgoAy6paFzhhak2EeyCZcgjH7ak5u5v28m'

src/kiln.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class Kiln {
2424
api.defaults.headers.common['Content-Type'] = 'application/json';
2525
api.defaults.baseURL =
2626
testnet === true
27-
? 'https://api.testnet.kiln.fi/'
27+
? 'https://api.devnet.kiln.fi/'
2828
: 'https://api.kiln.fi/';
2929

3030
this.eth = new EthService({ testnet, integrations, rpc: rpcs?.ethereum, });

src/services/sol.ts

Lines changed: 122 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
Keypair,
66
PublicKey,
77
sendAndConfirmRawTransaction,
8-
StakeProgram,
8+
StakeProgram, SystemProgram,
99
Transaction,
1010
TransactionInstruction,
1111
} from '@solana/web3.js';
@@ -14,7 +14,7 @@ import { InvalidStakeAmount } from '../errors/sol';
1414
import { ADDRESSES } from '../globals';
1515
import {
1616
ApiCreatedStakes,
17-
InternalSolanaConfig,
17+
InternalSolanaConfig, PublicNonceAccountInfo, PublicSignature,
1818
SolanaStakeOptions,
1919
SolanaTx,
2020
SolNetworkStats,
@@ -48,6 +48,27 @@ export class SolService extends Service {
4848
return connection;
4949
}
5050

51+
/**
52+
* Get Kiln nonce account info
53+
* @private
54+
*/
55+
private async getNonceAccount (): Promise<PublicNonceAccountInfo> {
56+
const { data } = await api.get<PublicNonceAccountInfo>('/v1/sol/nonce-account');
57+
return data;
58+
};
59+
60+
/**
61+
* Partially sign a hex encoded message with kiln nonce account
62+
* @param message
63+
* @private
64+
*/
65+
private async partialSignWithNonceAccount (message: string): Promise<PublicSignature[]> {
66+
const { data } = await api.post<PublicSignature[]>('/v1/sol/nonce-account/partial-sign', {
67+
message,
68+
});
69+
return data;
70+
};
71+
5172
/**
5273
* Craft Solana staking transaction
5374
* @param accountId id of the kiln account to use for the stake transaction
@@ -76,8 +97,20 @@ export class SolService extends Service {
7697
);
7798
const memoProgram = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
7899

100+
// Get nonce account info
101+
const nonceInfo = await this.getNonceAccount();
102+
const nonceAccountPubKey = new PublicKey(nonceInfo.nonce_account);
103+
const connection = await this.getConnection();
104+
const nonceAccount = await connection.getNonce(nonceAccountPubKey);
105+
if (!nonceAccount) {
106+
throw new Error('Could not fetch nonce account');
107+
}
108+
79109
const instructions = [
80-
// memo the transaction with account id
110+
SystemProgram.nonceAdvance({
111+
noncePubkey: nonceAccountPubKey,
112+
authorizedPubkey: nonceAccount.authorizedPubkey,
113+
}),
81114
new TransactionInstruction({
82115
keys: [
83116
{
@@ -120,12 +153,21 @@ export class SolService extends Service {
120153
);
121154
}
122155

123-
const connection = await this.getConnection();
124-
let blockhash = await connection.getLatestBlockhash('finalized');
125-
tx.recentBlockhash = blockhash.blockhash;
156+
tx.recentBlockhash = nonceAccount.nonce;
126157
tx.feePayer = staker;
127158
tx.partialSign(stakeKey);
128159

160+
// Sign with nonce account
161+
const signatures = await this.partialSignWithNonceAccount(tx.serializeMessage().toString('hex'));
162+
signatures.forEach((signature: PublicSignature) => {
163+
if (signature.signature) {
164+
tx.addSignature(
165+
new PublicKey(signature.pubkey),
166+
Buffer.from(signature.signature, 'hex'),
167+
);
168+
}
169+
});
170+
129171
// Tag stake
130172
const stake: TaggedStake = {
131173
stakeAccount: stakeKey.publicKey.toString(),
@@ -155,7 +197,20 @@ export class SolService extends Service {
155197
const stakeAccountPubKey = new PublicKey(stakeAccountAddress);
156198
const walletPubKey = new PublicKey(walletAddress);
157199

200+
// Get nonce account info
201+
const nonceInfo = await this.getNonceAccount();
202+
const nonceAccountPubKey = new PublicKey(nonceInfo.nonce_account);
203+
const connection = await this.getConnection();
204+
const nonceAccount = await connection.getNonce(nonceAccountPubKey);
205+
if (!nonceAccount) {
206+
throw new Error('Could not fetch nonce account');
207+
}
208+
158209
const instructions = [
210+
SystemProgram.nonceAdvance({
211+
noncePubkey: nonceAccountPubKey,
212+
authorizedPubkey: nonceAccount.authorizedPubkey,
213+
}),
159214
StakeProgram.deactivate({
160215
stakePubkey: stakeAccountPubKey,
161216
authorizedPubkey: walletPubKey,
@@ -164,10 +219,20 @@ export class SolService extends Service {
164219
tx.add(...instructions);
165220

166221

167-
const connection = await this.getConnection();
168-
let blockhash = await connection.getLatestBlockhash('finalized');
169-
tx.recentBlockhash = blockhash.blockhash;
222+
tx.recentBlockhash = nonceAccount.nonce;
170223
tx.feePayer = walletPubKey;
224+
225+
// Sign with nonce account
226+
const signatures = await this.partialSignWithNonceAccount(tx.serializeMessage().toString('hex'));
227+
signatures.forEach((signature: PublicSignature) => {
228+
if (signature.signature) {
229+
tx.addSignature(
230+
new PublicKey(signature.pubkey),
231+
Buffer.from(signature.signature, 'hex'),
232+
);
233+
}
234+
});
235+
171236
return tx;
172237
}
173238

@@ -184,8 +249,17 @@ export class SolService extends Service {
184249
): Promise<SolanaTx> {
185250
const stakeAccountPubKey = new PublicKey(stakeAccountAddress);
186251
const walletPubKey = new PublicKey(walletAddress);
187-
let amount;
252+
253+
// Get nonce account info
254+
const nonceInfo = await this.getNonceAccount();
255+
const nonceAccountPubKey = new PublicKey(nonceInfo.nonce_account);
188256
const connection = await this.getConnection();
257+
const nonceAccount = await connection.getNonce(nonceAccountPubKey);
258+
if (!nonceAccount) {
259+
throw new Error('Could not fetch nonce account');
260+
}
261+
262+
let amount;
189263

190264
if (!amountToWithdraw) {
191265
amount = await connection.getBalance(stakeAccountPubKey);
@@ -196,6 +270,10 @@ export class SolService extends Service {
196270
const tx = new Transaction();
197271

198272
const instructions = [
273+
SystemProgram.nonceAdvance({
274+
noncePubkey: nonceAccountPubKey,
275+
authorizedPubkey: nonceAccount.authorizedPubkey,
276+
}),
199277
StakeProgram.withdraw({
200278
stakePubkey: stakeAccountPubKey,
201279
authorizedPubkey: walletPubKey,
@@ -205,10 +283,20 @@ export class SolService extends Service {
205283
];
206284
tx.add(...instructions);
207285

208-
209-
let blockhash = await connection.getLatestBlockhash('finalized');
210-
tx.recentBlockhash = blockhash.blockhash;
286+
tx.recentBlockhash = nonceAccount.nonce;
211287
tx.feePayer = walletPubKey;
288+
289+
// Sign with nonce account
290+
const signatures = await this.partialSignWithNonceAccount(tx.serializeMessage().toString('hex'));
291+
signatures.forEach((signature: PublicSignature) => {
292+
if (signature.signature) {
293+
tx.addSignature(
294+
new PublicKey(signature.pubkey),
295+
Buffer.from(signature.signature, 'hex'),
296+
);
297+
}
298+
});
299+
212300
return tx;
213301
}
214302

@@ -228,6 +316,15 @@ export class SolService extends Service {
228316
const sourcePubKey = new PublicKey(stakeAccountSourceAddress);
229317
const destinationPubKey = new PublicKey(stakeAccountDestinationAddress);
230318

319+
// Get nonce account info
320+
const nonceInfo = await this.getNonceAccount();
321+
const nonceAccountPubKey = new PublicKey(nonceInfo.nonce_account);
322+
const connection = await this.getConnection();
323+
const nonceAccount = await connection.getNonce(nonceAccountPubKey);
324+
if (!nonceAccount) {
325+
throw new Error('Could not fetch nonce account');
326+
}
327+
231328
const instructions = [
232329
StakeProgram.merge({
233330
stakePubkey: destinationPubKey,
@@ -237,11 +334,20 @@ export class SolService extends Service {
237334
];
238335
tx.add(...instructions);
239336

240-
const connection = await this.getConnection();
241-
let blockhash = await connection.getLatestBlockhash('finalized');
242-
tx.recentBlockhash = blockhash.blockhash;
337+
tx.recentBlockhash = nonceAccount.nonce;
243338
tx.feePayer = stakerPubKey;
244339

340+
// Sign with nonce account
341+
const signatures = await this.partialSignWithNonceAccount(tx.serializeMessage().toString('hex'));
342+
signatures.forEach((signature: PublicSignature) => {
343+
if (signature.signature) {
344+
tx.addSignature(
345+
new PublicKey(signature.pubkey),
346+
Buffer.from(signature.signature, 'hex'),
347+
);
348+
}
349+
});
350+
245351
return tx;
246352
}
247353

@@ -270,7 +376,6 @@ export class SolService extends Service {
270376

271377
const signatures = await this.fbSigner.signWithFB(payload, this.testnet ? 'SOL_TEST' : 'SOL', note);
272378
signatures.signedMessages?.forEach((signedMessage: any) => {
273-
console.log(signedMessage);
274379
if (signedMessage.derivationPath[3] == 0 && transaction.feePayer) {
275380
transaction.addSignature(transaction.feePayer, Buffer.from(signedMessage.signature.fullSig, "hex"));
276381
}

src/types/sol.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,14 @@ export type ApiCreatedStakes = {
5050
export type TaggedStake = {
5151
stakeAccount: string;
5252
balance: number;
53+
};
54+
55+
export type PublicNonceAccountInfo = {
56+
nonce_account: string;
57+
nonce_account_authority: string;
58+
};
59+
60+
export type PublicSignature = {
61+
pubkey: string;
62+
signature: string | null;
5363
};

0 commit comments

Comments
 (0)