@@ -13,7 +13,7 @@ import chalk from "chalk";
1313import yargs from "yargs" ;
1414import { $ } from "bun" ;
1515import { hideBin } from "yargs/helpers" ;
16- import { Connection , Keypair , PublicKey } from "@solana/web3.js" ;
16+ import { AddressLookupTableAccount , Connection , Keypair , PublicKey , SendTransactionError , TransactionMessage , VersionedTransaction } from "@solana/web3.js" ;
1717import * as spl from "@solana/spl-token" ;
1818import fs from "fs" ;
1919import readline from "readline" ;
@@ -785,7 +785,8 @@ yargs(hideBin(process.argv))
785785 continue ;
786786 }
787787 const solanaNtt = ntt as SolanaNtt < Network , SolanaChains > ;
788- const tx = solanaNtt . initializeOrUpdateLUT ( { payer : new SolanaAddress ( signer . address . address ) . unwrap ( ) } )
788+ const payer = new SolanaAddress ( signer . address . address ) . unwrap ( ) ;
789+ const tx = solanaNtt . initializeOrUpdateLUT ( { payer, owner : payer } )
789790 try {
790791 await signSendWait ( ctx , tx , signer . signer )
791792 } catch ( e : any ) {
@@ -963,6 +964,314 @@ yargs(hideBin(process.argv))
963964 const ata = spl . getAssociatedTokenAddressSync ( mint , owner , true , tokenProgram ) ;
964965 console . log ( ata . toBase58 ( ) ) ;
965966 } )
967+ . command ( "create-spl-multisig <multisigMemberPubkey...>" ,
968+ "create a valid SPL Multisig (see https://github.com/wormhole-foundation/native-token-transfers/tree/main/solana#spl-multisig-support for more info)" ,
969+ ( yargs ) =>
970+ yargs
971+ . positional ( "multisigMemberPubkey" , {
972+ describe :
973+ "public keys of the members that can independently mint" ,
974+ type : "string" ,
975+ demandOption : true ,
976+ } )
977+ . option ( "path" , options . deploymentPath )
978+ . option ( "payer" , { ...options . payer , demandOption : true } )
979+ . example (
980+ "$0 solana create-spl-multisig Sol1234... --payer <SOLANA_KEYPAIR_PATH>" ,
981+ "Create multisig with Sol1234... having independent mint privilege alongside NTT token-authority"
982+ )
983+ . example (
984+ "$0 solana create-spl-multisig Sol1234... Sol3456... Sol5678... --payer <SOLANA_KEYPAIR_PATH>" ,
985+ "Create multisig with Sol1234..., Sol3456..., and Sol5678... having mint privileges alongside NTT token-authority"
986+ ) ,
987+ async ( argv ) => {
988+ const path = argv [ "path" ] ;
989+ const deployments : Config = loadConfig ( path ) ;
990+ const chain : Chain = "Solana" ;
991+ const network = deployments . network as Network ;
992+
993+ if ( ! fs . existsSync ( argv [ "payer" ] ) ) {
994+ console . error ( "Payer not found. Specify with --payer" ) ;
995+ process . exit ( 1 ) ;
996+ }
997+ const payerKeypair = Keypair . fromSecretKey (
998+ new Uint8Array (
999+ JSON . parse ( fs . readFileSync ( argv [ "payer" ] ) . toString ( ) )
1000+ )
1001+ ) ;
1002+
1003+ if ( ! ( chain in deployments . chains ) ) {
1004+ console . error ( `Chain ${ chain } not found in ${ path } ` ) ;
1005+ process . exit ( 1 ) ;
1006+ }
1007+ const chainConfig = deployments . chains [ chain ] ! ;
1008+ const wh = new Wormhole ( network , [ solana . Platform , evm . Platform ] , overrides ) ;
1009+ const ch = wh . getChain ( chain ) ;
1010+ const connection = await ch . getRpc ( ) ;
1011+ const [ , , ntt ] = await pullChainConfig (
1012+ network ,
1013+ { chain, address : toUniversal ( chain , chainConfig . manager ) } ,
1014+ overrides
1015+ ) ;
1016+ const solanaNtt = ntt as SolanaNtt < typeof network , SolanaChains > ;
1017+ const tokenAuthority = NTT . pdas ( chainConfig . manager ) . tokenAuthority ( ) ;
1018+
1019+ // check if SPL-Multisig is supported for manager version
1020+ const major = Number ( solanaNtt . version . split ( "." ) [ 0 ] ) ;
1021+ if ( major < 3 ) {
1022+ console . error (
1023+ "SPL Multisig token mint authority is only supported for versions >= 3.x.x"
1024+ ) ;
1025+ console . error ( "Use 'ntt upgrade' to upgrade the NTT contract to a specific version." ) ;
1026+ process . exit ( 1 ) ;
1027+ }
1028+
1029+ try {
1030+ // use same tokenProgram as token to create multisig
1031+ const tokenProgram = ( await solanaNtt . getConfig ( ) ) . tokenProgram ;
1032+ const additionalMemberPubkeys = ( argv [ "multisigMemberPubkey" ] as any ) . map (
1033+ ( key : string ) => new PublicKey ( key )
1034+ ) ;
1035+ const multisig = await spl . createMultisig (
1036+ connection ,
1037+ payerKeypair ,
1038+ [
1039+ tokenAuthority ,
1040+ ...additionalMemberPubkeys ,
1041+ ] ,
1042+ 1 ,
1043+ undefined ,
1044+ { commitment : "finalized" } ,
1045+ tokenProgram
1046+ ) ;
1047+ console . log ( `Valid SPL Multisig created: ${ multisig . toBase58 ( ) } ` ) ;
1048+ } catch ( error ) {
1049+ if ( error instanceof Error ) {
1050+ console . error ( error . message ) ;
1051+ } else if ( error instanceof SendTransactionError ) {
1052+ console . error ( error . logs ) ;
1053+ }
1054+ }
1055+ } )
1056+ . command ( "set-mint-authority <newAuthority>" ,
1057+ "set token mint authority to token authority (or valid SPL Multisig if --multisig flag is provided)" ,
1058+ ( yargs ) =>
1059+ yargs
1060+ . positional ( "newAuthority" , {
1061+ describe :
1062+ "token authority address (or valid SPL Multisig address if --multisig flag is provided)" ,
1063+ type : "string" ,
1064+ demandOption : true ,
1065+ } )
1066+ . option ( "path" , options . deploymentPath )
1067+ . option ( "payer" , { ...options . payer , demandOption : true } )
1068+ . option ( "multisig" , {
1069+ describe : "newAuthority is a valid SPL Multisig" ,
1070+ type : "boolean" ,
1071+ default : false ,
1072+ } )
1073+ . example (
1074+ "$0 solana set-mint-authority <TOKEN_AUTHORITY> --payer <SOLANA_KEYPAIR_PATH>" ,
1075+ "Set token mint authority to be the token authority address"
1076+ )
1077+ . example (
1078+ "$0 solana set-mint-authority <VALID_SPL_MULTISIG> --multisig --payer <SOLANA_KEYPAIR_PATH>" ,
1079+ "Set token mint authority to be a valid SPL Multisig"
1080+ ) ,
1081+ async ( argv ) => {
1082+ const path = argv [ "path" ] ;
1083+ const deployments : Config = loadConfig ( path ) ;
1084+ const chain : Chain = "Solana" ;
1085+ const network = deployments . network as Network ;
1086+
1087+ if ( ! fs . existsSync ( argv [ "payer" ] ) ) {
1088+ console . error ( "Payer not found. Specify with --payer" ) ;
1089+ process . exit ( 1 ) ;
1090+ }
1091+ const payerKeypair = Keypair . fromSecretKey (
1092+ new Uint8Array (
1093+ JSON . parse ( fs . readFileSync ( argv [ "payer" ] ) . toString ( ) )
1094+ )
1095+ ) ;
1096+
1097+ if ( ! ( chain in deployments . chains ) ) {
1098+ console . error ( `Chain ${ chain } not found in ${ path } ` ) ;
1099+ process . exit ( 1 ) ;
1100+ }
1101+ const chainConfig = deployments . chains [ chain ] ! ;
1102+ const wh = new Wormhole ( network , [ solana . Platform , evm . Platform ] , overrides ) ;
1103+ const ch = wh . getChain ( chain ) ;
1104+ const connection : Connection = await ch . getRpc ( ) ;
1105+ const [ , , ntt ] = await pullChainConfig (
1106+ network ,
1107+ { chain, address : toUniversal ( chain , chainConfig . manager ) } ,
1108+ overrides
1109+ ) ;
1110+ const solanaNtt = ntt as SolanaNtt < typeof network , SolanaChains > ;
1111+ const major = Number ( solanaNtt . version . split ( "." ) [ 0 ] ) ;
1112+ const config = await solanaNtt . getConfig ( ) ;
1113+ const tokenAuthority = NTT . pdas ( chainConfig . manager ) . tokenAuthority ( ) ;
1114+
1115+ // verify current mint authority is not token authority
1116+ const mintInfo = await spl . getMint (
1117+ connection ,
1118+ config . mint ,
1119+ undefined ,
1120+ config . tokenProgram
1121+ ) ;
1122+ if ( ! mintInfo . mintAuthority ) {
1123+ console . error (
1124+ "Token has fixed supply and no further tokens may be minted"
1125+ ) ;
1126+ process . exit ( 1 ) ;
1127+ }
1128+ if ( mintInfo . mintAuthority . equals ( tokenAuthority ) ) {
1129+ console . error ( "Please use https://github.com/wormhole-foundation/demo-ntt-token-mint-authority-transfer to transfer the token mint authority out of the NTT manager" ) ;
1130+ process . exit ( 1 ) ;
1131+ }
1132+
1133+ // verify current mint authority is not valid SPL Multisig
1134+ let isMultisigTokenAuthority = false ;
1135+ try {
1136+ const multisigInfo = await spl . getMultisig (
1137+ connection ,
1138+ mintInfo . mintAuthority ,
1139+ undefined ,
1140+ config . tokenProgram
1141+ ) ;
1142+ if ( multisigInfo . m === 1 ) {
1143+ const n = multisigInfo . n ;
1144+ for ( let i = 0 ; i < n ; ++ i ) {
1145+ // TODO: not sure if there's an easier way to loop through and check
1146+ if ( ( multisigInfo [ `signer${ i + 1 } ` as keyof spl . Multisig ] as PublicKey ) . equals ( tokenAuthority ) ) {
1147+ isMultisigTokenAuthority = true ;
1148+ break ;
1149+ }
1150+ }
1151+ }
1152+ } catch { }
1153+ if ( isMultisigTokenAuthority ) {
1154+ console . error ( "Please use https://github.com/wormhole-foundation/demo-ntt-token-mint-authority-transfer to transfer the token mint authority out of the NTT manager" ) ;
1155+ process . exit ( 1 ) ;
1156+ }
1157+
1158+ // verify current mint authority is payer
1159+ if ( ! mintInfo . mintAuthority . equals ( payerKeypair . publicKey ) ) {
1160+ console . error (
1161+ `Current mint authority (${ mintInfo . mintAuthority . toBase58 ( ) } ) does not match payer (${ payerKeypair . publicKey . toBase58 ( ) } ). Retry with current authority`
1162+ ) ;
1163+ process . exit ( 1 ) ;
1164+ }
1165+
1166+ const isMultisig = argv [ "multisig" ] ;
1167+ if ( isMultisig ) {
1168+ // check if SPL-Multisig is supported for manager version
1169+ if ( major < 3 ) {
1170+ console . error (
1171+ "SPL Multisig token mint authority only supported for versions >= 3.x.x"
1172+ ) ;
1173+ console . error ( "Use 'ntt upgrade' to upgrade the NTT contract to a specific version." ) ;
1174+ process . exit ( 1 ) ;
1175+ }
1176+ }
1177+
1178+ // verify new authority address is valid
1179+ const newAuthority = new PublicKey ( argv [ "newAuthority" ] ) ;
1180+ if ( isMultisig && newAuthority . equals ( tokenAuthority ) ) {
1181+ console . error (
1182+ `New authority matches token authority (${ newAuthority . toBase58 ( ) } ). To set mint authority as token authority, retry without --multisig`
1183+ ) ;
1184+ process . exit ( 1 ) ;
1185+ }
1186+ if ( ! isMultisig && ! newAuthority . equals ( tokenAuthority ) ) {
1187+ console . error (
1188+ `New authority (${ newAuthority . toBase58 ( ) } ) does not match token authority (${ tokenAuthority . toBase58 ( ) } ). To set mint authority as a valid SPL Multisig, specify with --multisig`
1189+ ) ;
1190+ process . exit ( 1 ) ;
1191+ }
1192+
1193+ // ensure manager is paused
1194+ if ( ! ( await solanaNtt . isPaused ( ) ) ) {
1195+ console . error (
1196+ `Not paused. Set \`paused\` for Solana to \`true\` in ${ path } and run \`ntt push\` to sync the changes on-chain. Then retry this command.`
1197+ ) ;
1198+ process . exit ( 1 ) ;
1199+ }
1200+
1201+ // manager versions < 3.x.x have to call spl setAuthority instruction directly
1202+ if ( major < 3 ) {
1203+ try {
1204+ await spl . setAuthority (
1205+ connection ,
1206+ payerKeypair ,
1207+ config . mint ,
1208+ payerKeypair . publicKey ,
1209+ spl . AuthorityType . MintTokens ,
1210+ newAuthority ,
1211+ [ ] ,
1212+ { commitment : "finalized" } ,
1213+ config . tokenProgram
1214+ ) ;
1215+ console . log (
1216+ `Token mint authority successfully updated to ${ newAuthority . toBase58 ( ) } `
1217+ ) ;
1218+ process . exit ( 0 ) ;
1219+ } catch ( error ) {
1220+ if ( error instanceof Error ) {
1221+ console . error ( error . message ) ;
1222+ } else if ( error instanceof SendTransactionError ) {
1223+ console . error ( error . logs ) ;
1224+ }
1225+ process . exit ( 1 ) ;
1226+ }
1227+ }
1228+
1229+ // use lut if configured
1230+ const luts : AddressLookupTableAccount [ ] = [ ] ;
1231+ try {
1232+ luts . push ( await solanaNtt . getAddressLookupTable ( ) ) ;
1233+ } catch { }
1234+
1235+ // send versioned transaction
1236+ try {
1237+ const latestBlockHash = await connection . getLatestBlockhash ( ) ;
1238+ const messageV0 = new TransactionMessage ( {
1239+ payerKey : payerKeypair . publicKey ,
1240+ instructions : [
1241+ await NTT . createAcceptTokenAuthorityInstruction (
1242+ solanaNtt . program ,
1243+ config ,
1244+ {
1245+ currentAuthority : payerKeypair . publicKey ,
1246+ multisigTokenAuthority : isMultisig
1247+ ? newAuthority
1248+ : undefined ,
1249+ }
1250+ ) ,
1251+ ] ,
1252+ recentBlockhash : latestBlockHash . blockhash ,
1253+ } ) . compileToV0Message ( luts ) ;
1254+ const vtx = new VersionedTransaction ( messageV0 ) ;
1255+ vtx . sign ( [ payerKeypair ] ) ;
1256+ const signature = await connection . sendTransaction ( vtx , { } ) ;
1257+ await connection . confirmTransaction (
1258+ {
1259+ ...latestBlockHash ,
1260+ signature,
1261+ } ,
1262+ "finalized"
1263+ ) ;
1264+ console . log (
1265+ `Token mint authority successfully updated to ${ newAuthority . toBase58 ( ) } `
1266+ ) ;
1267+ } catch ( error ) {
1268+ if ( error instanceof Error ) {
1269+ console . error ( error . message ) ;
1270+ } else if ( error instanceof SendTransactionError ) {
1271+ console . error ( error . logs ) ;
1272+ }
1273+ }
1274+ } )
9661275 . demandCommand ( )
9671276 }
9681277 )
@@ -1415,7 +1724,7 @@ async function deploySolana<N extends Network, C extends SolanaChains>(
14151724 console . error ( `Expected: ${ expectedMintAuthority } ` ) ;
14161725 console . error ( `Actual: ${ actualMintAuthority } ` ) ;
14171726 console . error ( `Set the mint authority to the program's token authority PDA with e.g.:` ) ;
1418- console . error ( `spl-token authorize ${ token } mint ${ expectedMintAuthority } ` ) ;
1727+ console . error ( `ntt solana set- mint-authority ${ expectedMintAuthority } ` ) ;
14191728 process . exit ( 1 ) ;
14201729 }
14211730 }
@@ -1550,7 +1859,7 @@ async function missingConfigs(
15501859 // if it does, it means the LUT is missing or outdated. notice that
15511860 // we're not actually updating the LUT here, just checking if it's
15521861 // missing, so it's ok to use the 0 pubkey as the payer.
1553- const updateLUT = solanaNtt . initializeOrUpdateLUT ( { payer : new PublicKey ( 0 ) } ) ;
1862+ const updateLUT = solanaNtt . initializeOrUpdateLUT ( { payer : new PublicKey ( 0 ) , owner : new PublicKey ( 0 ) } ) ;
15541863 // check if async generator is non-empty
15551864 if ( ! ( await updateLUT . next ( ) ) . done ) {
15561865 count ++ ;
@@ -2149,4 +2458,4 @@ function nttVersion(): { version: string, commit: string, path: string, remote:
21492458 } catch {
21502459 return null ;
21512460 }
2152- }
2461+ }
0 commit comments