Skip to content

Commit 5a4e22d

Browse files
authored
feat(infra): use turnkey signing for svm igp updates (#7233)
1 parent f98479e commit 5a4e22d

File tree

13 files changed

+1426
-295
lines changed

13 files changed

+1426
-295
lines changed

.changeset/moody-baboons-hunt.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@hyperlane-xyz/widgets": patch
3+
"@hyperlane-xyz/utils": patch
4+
"@hyperlane-xyz/sdk": patch
5+
---
6+
7+
Bump @solana/web3.js dependency explicitly from ^1.95.4 to ^1.98.4.

.changeset/smart-snakes-refuse.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperlane-xyz/sdk": minor
3+
---
4+
5+
Introduced new SvmTransactionSigner interface, rewrite SvmMultiprotocolSignerAdapter to leverage this interface. Add more robust tx sending and handling to SvmMultiprotocolSignerAdapter. Implement KeypairSvmTransactionSigner to handle the general PK/keypair-based tx signing.

typescript/infra/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
"@safe-global/api-kit": "1.3.0",
2424
"@safe-global/protocol-kit": "1.3.0",
2525
"@safe-global/safe-core-sdk-types": "2.3.0",
26-
"@solana/web3.js": "^1.95.4",
26+
"@solana/web3.js": "^1.98.4",
27+
"@turnkey/api-key-stamper": "^0.5.0",
28+
"@turnkey/sdk-server": "^4.10.4",
29+
"@turnkey/solana": "^1.1.9",
2730
"asn1.js": "^5.4.1",
2831
"aws-kms-ethers-signer": "^0.1.3",
2932
"deep-object-diff": "^1.1.9",

typescript/infra/scripts/sealevel-helpers/update-gas-oracles.ts

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
SealevelOverheadIgpData,
2020
SealevelOverheadIgpDataSchema,
2121
SealevelRemoteGasData,
22+
SvmMultiProtocolSignerAdapter,
2223
} from '@hyperlane-xyz/sdk';
2324
import { Domain, ProtocolType, rootLogger } from '@hyperlane-xyz/utils';
2425

@@ -32,13 +33,13 @@ import {
3233
} from '../../src/config/gas-oracle.js';
3334
import {
3435
batchAndSendTransactions,
35-
calculatePercentDifference,
3636
formatRemoteGasData,
3737
loadCoreProgramIds,
3838
serializeGasOracleDifference,
3939
svmGasOracleConfigPath,
4040
} from '../../src/utils/sealevel.js';
41-
import { chainIsProtocol, readJSONAtPath } from '../../src/utils/utils.js';
41+
import { getTurnkeySealevelDeployerSigner } from '../../src/utils/turnkey.js';
42+
import { chainIsProtocol } from '../../src/utils/utils.js';
4243
import { getArgs, withChains } from '../agent-utils.js';
4344
import { getEnvironmentConfig } from '../core-utils.js';
4445

@@ -134,15 +135,13 @@ async function promptForRemoval(
134135
*/
135136
async function manageGasOracles(
136137
mpp: MultiProtocolProvider,
137-
connection: Connection,
138138
chain: ChainName,
139139
igpAccountData: SealevelIgpData,
140140
chainGasOracleConfig: ChainMap<GasOracleConfigWithOverhead>,
141141
allConfigDomainIds: Set<Domain>,
142142
igpAdapter: SealevelIgpAdapter,
143-
programId: PublicKey,
144143
igpAccountPda: PublicKey,
145-
signerKeypair: Keypair,
144+
adapter: SvmMultiProtocolSignerAdapter,
146145
dryRun: boolean,
147146
): Promise<{
148147
oraclesRemoved: number;
@@ -241,15 +240,14 @@ async function manageGasOracles(
241240
);
242241

243242
await batchAndSendTransactions({
244-
connection,
245243
chain,
246-
signerKeypair,
244+
adapter,
247245
operationName: 'gas oracle removals',
248246
items: removalConfigs,
249247
createInstruction: (batch) =>
250248
igpAdapter.createSetGasOracleConfigsInstruction(
251249
igpAccountPda,
252-
signerKeypair.publicKey,
250+
adapter.publicKey(),
253251
batch,
254252
),
255253
formatBatch: (batch) => {
@@ -269,15 +267,14 @@ async function manageGasOracles(
269267
let oraclesUpdated = 0;
270268
if (configsToUpdate.length > 0) {
271269
await batchAndSendTransactions({
272-
connection,
273270
chain,
274-
signerKeypair,
271+
adapter,
275272
operationName: 'gas oracle updates',
276273
items: configsToUpdate.map((item) => item.config),
277274
createInstruction: (batch) =>
278275
igpAdapter.createSetGasOracleConfigsInstruction(
279276
igpAccountPda,
280-
signerKeypair.publicKey,
277+
adapter.publicKey(),
281278
batch,
282279
),
283280
formatBatch: (batch) => {
@@ -302,14 +299,13 @@ async function manageGasOracles(
302299
*/
303300
async function manageGasOverheads(
304301
mpp: MultiProtocolProvider,
305-
connection: Connection,
306302
chain: ChainName,
307303
overheadIgpAccountData: SealevelOverheadIgpData,
308304
chainGasOracleConfig: ChainMap<GasOracleConfigWithOverhead>,
309305
allConfigDomainIds: Set<Domain>,
310306
overheadIgpAdapter: SealevelOverheadIgpAdapter,
311307
overheadIgpAccountPda: PublicKey,
312-
signerKeypair: Keypair,
308+
adapter: SvmMultiProtocolSignerAdapter,
313309
dryRun: boolean,
314310
): Promise<{
315311
overheadsRemoved: number;
@@ -389,15 +385,14 @@ async function manageGasOverheads(
389385
);
390386

391387
await batchAndSendTransactions({
392-
connection,
393388
chain,
394-
signerKeypair,
389+
adapter,
395390
operationName: 'gas overhead removals',
396391
items: removalConfigs,
397392
createInstruction: (batch) =>
398393
overheadIgpAdapter.createSetDestinationGasOverheadsInstruction(
399394
overheadIgpAccountPda,
400-
signerKeypair.publicKey,
395+
adapter.publicKey(),
401396
batch,
402397
),
403398
formatBatch: (batch) => {
@@ -417,15 +412,14 @@ async function manageGasOverheads(
417412
let overheadsUpdated = 0;
418413
if (configsToUpdate.length > 0) {
419414
await batchAndSendTransactions({
420-
connection,
421415
chain,
422-
signerKeypair,
416+
adapter,
423417
operationName: 'gas overhead updates',
424418
items: configsToUpdate.map((item) => item.config),
425419
createInstruction: (batch) =>
426420
overheadIgpAdapter.createSetDestinationGasOverheadsInstruction(
427421
overheadIgpAccountPda,
428-
signerKeypair.publicKey,
422+
adapter.publicKey(),
429423
batch,
430424
),
431425
formatBatch: (batch) => {
@@ -453,7 +447,7 @@ async function processChain(
453447
mpp: MultiProtocolProvider,
454448
chain: ChainName,
455449
chainGasOracleConfig: ChainMap<GasOracleConfigWithOverhead>,
456-
keyPath: string,
450+
adapter: SvmMultiProtocolSignerAdapter,
457451
dryRun: boolean,
458452
): Promise<{
459453
chain: string;
@@ -476,15 +470,7 @@ async function processChain(
476470
rootLogger.debug(`Using IGP program ID: ${programId.toBase58()}`);
477471
rootLogger.debug(`IGP Account: ${igpAccountPda.toBase58()}`);
478472
rootLogger.debug(`Overhead IGP Account: ${overheadIgpAccountPda.toBase58()}`);
479-
480-
// Load keypair
481-
const keypairData = readJSONAtPath(keyPath);
482-
const signerKeypair = Keypair.fromSecretKey(new Uint8Array(keypairData));
483-
rootLogger.debug(`Using signer: ${signerKeypair.publicKey.toBase58()}`);
484-
485-
// Setup connection
486-
const rpcs = await getSecretRpcEndpoints(environment, chain);
487-
const connection = new Connection(rpcs[0], 'confirmed');
473+
rootLogger.debug(`Using signer: ${await adapter.address()}`);
488474

489475
// Create adapters and fetch account states
490476
const igpAdapter = new SealevelIgpAdapter(chain, mpp, {
@@ -497,6 +483,7 @@ async function processChain(
497483
programId: programId.toBase58(),
498484
});
499485

486+
const connection = mpp.getSolanaWeb3Provider(chain);
500487
const { igpAccountData, overheadIgpAccountData } = await fetchAccountStates(
501488
connection,
502489
igpAccountPda,
@@ -514,29 +501,26 @@ async function processChain(
514501
const { oraclesRemoved, oraclesUpdated, oraclesMatched } =
515502
await manageGasOracles(
516503
mpp,
517-
connection,
518504
chain,
519505
igpAccountData,
520506
chainGasOracleConfig,
521507
allConfigDomainIds,
522508
igpAdapter,
523-
programId,
524509
igpAccountPda,
525-
signerKeypair,
510+
adapter,
526511
dryRun,
527512
);
528513

529514
const { overheadsRemoved, overheadsUpdated, overheadsMatched } =
530515
await manageGasOverheads(
531516
mpp,
532-
connection,
533517
chain,
534518
overheadIgpAccountData,
535519
chainGasOracleConfig,
536520
allConfigDomainIds,
537521
overheadIgpAdapter,
538522
overheadIgpAccountPda,
539-
signerKeypair,
523+
adapter,
540524
dryRun,
541525
);
542526

@@ -556,20 +540,12 @@ async function main() {
556540
const {
557541
environment,
558542
chains: chainsArg,
559-
keyPath,
560543
apply,
561-
} = await withChains(getArgs())
562-
.option('keyPath', {
563-
type: 'string',
564-
description: 'Path to Solana keypair JSON file',
565-
demandOption: true,
566-
alias: 'k',
567-
})
568-
.option('apply', {
569-
type: 'boolean',
570-
description: 'Apply changes on-chain (default is dry-run mode)',
571-
default: false,
572-
}).argv;
544+
} = await withChains(getArgs()).option('apply', {
545+
type: 'boolean',
546+
description: 'Apply changes on-chain (default is dry-run mode)',
547+
default: false,
548+
}).argv;
573549

574550
const dryRun = !apply;
575551

@@ -584,6 +560,11 @@ async function main() {
584560
)
585561
: chainsArg;
586562

563+
// Initialize Turnkey signer and wrap in adapter (default and only option)
564+
rootLogger.info('Initializing Turnkey signer from GCP Secret Manager...');
565+
const turnkeySigner = await getTurnkeySealevelDeployerSigner(environment);
566+
rootLogger.info(`Signer public key: ${turnkeySigner.publicKey.toBase58()}`);
567+
587568
rootLogger.info(
588569
`Configuring IGP for chains: ${chains.join(', ')} on ${environment}`,
589570
);
@@ -599,6 +580,13 @@ async function main() {
599580
const mpp = await envConfig.getMultiProtocolProvider();
600581
const results = await Promise.all(
601582
chains.map((chain) => {
583+
// Wrap Turnkey signer in the adapter for this chain
584+
const signerAdapter = new SvmMultiProtocolSignerAdapter(
585+
chain,
586+
turnkeySigner,
587+
mpp,
588+
);
589+
602590
const chainGasOracleConfig = gasOracleConfig[chain];
603591
if (!chainGasOracleConfig) {
604592
// Guard against missing chain configuration
@@ -613,7 +601,7 @@ async function main() {
613601
mpp,
614602
chain,
615603
chainGasOracleConfig,
616-
keyPath,
604+
signerAdapter,
617605
dryRun,
618606
);
619607
}),

0 commit comments

Comments
 (0)