This repository has been archived by the owner on Jan 22, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 925
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds an example to the `examples` folder that show-cases how to create and use various signers. It offers 4 different signer options: 1. Signing using a generated key pair. 2. Signing using a key pair loaded from a local JSON file. 3. Signing using a key pair derived from a seed. (Note that this currently requires a helper method `getPublicKeyFromPrivateKey` that should soon be available in `@solana/keys`). 4. Signing using a no-op signer. Each of these options go through the same 3 signing methods that: - Sign a message using the signer. - Sign a transaction using the signer. - Sign a transaction by automatically extracting the signers in the transaction message. Here is an example of the logs we see when running `pnpm start` on that example. ``` INFO (Signers): [option 1] Using a generated key pair signer address: "EkYtTyNcw9USEjKcQX23puoUUgiEbbiqi32uiSjyTZgs" INFO (Signers): >> Signing the message "Hello, World!" signature: "5hbhCAsFvV1HcQQ3YVDzQJeAbNDJpjwKo1YxR8TvpXTJCA2vAmeGeyVXRCqtyCGuV4vHKpXzoTgn5QxpJ5HjMyq7" INFO (Signers): >> Signing a transfer SOL transaction signature: "ztaVZHbN39DGh52XjXa6xrVL1ect53Qa9pmHsvUbNWEX96oApqMzEJFAxNSGoi2fEg1sY3iAFs8S45xXqddpsuV" INFO (Signers): >> Signing a transfer SOL transaction using its registered signers signature: "ztaVZHbN39DGh52XjXa6xrVL1ect53Qa9pmHsvUbNWEX96oApqMzEJFAxNSGoi2fEg1sY3iAFs8S45xXqddpsuV" INFO (Signers): [option 2] Using a key pair signer from a JSON file address: "BoK4mWeYVU6LdgNfo8QF7zxmTRiHw7VKMhodToZgrRup" INFO (Signers): >> Signing the message "Hello, World!" signature: "5tZ5zqXGcjrt6QHpUbNsdff59CTyQqfY8UYRpB5baxJXvokmwKEau1UknvURjmMTyZvhbYfaXC6A9iuWzU6rsCdS" INFO (Signers): >> Signing a transfer SOL transaction signature: "Jnnos7xeUJnv2BSVqjYb4Vs6AviHdMcFRCbYYRVrsXGsHnZTBBoh3b7BV1APTniWeP2mNG2DvXW4U4G8L8K8Dso" INFO (Signers): >> Signing a transfer SOL transaction using its registered signers signature: "Jnnos7xeUJnv2BSVqjYb4Vs6AviHdMcFRCbYYRVrsXGsHnZTBBoh3b7BV1APTniWeP2mNG2DvXW4U4G8L8K8Dso" INFO (Signers): [option 3] Using a key pair signer from a seed address: "A4fhR7TUeaj8qNS7PnBHdU4BnyZzuegK27AvHbSx5bhB" INFO (Signers): >> Signing the message "Hello, World!" signature: "wL5M1pSx1eyzKhqzSwP4jyTwTQNihFsAGEx52C9PeqNrQpjEQNoyvYkFcPLZWCSJi4bbwgNHR14awse5GrWn9Cu" INFO (Signers): >> Signing a transfer SOL transaction signature: "3EYnSrL7136Rca3YGbSYfHP3w52AZEv8nMBLofhu25r6XNfmduMWhj3vLemZkRNSu9QAzbAV5tCZXWAidZ4oPfis" INFO (Signers): >> Signing a transfer SOL transaction using its registered signers signature: "3EYnSrL7136Rca3YGbSYfHP3w52AZEv8nMBLofhu25r6XNfmduMWhj3vLemZkRNSu9QAzbAV5tCZXWAidZ4oPfis" INFO (Signers): [option 4] Using a no-op signer address: "HpgzmkFMyHxKAZ8ym6prLdxQ8H3GmQaSBcyimW31dQoe" INFO (Signers): >> Signing the message "Hello, World!" signature: null INFO (Signers): >> Signing a transfer SOL transaction signature: null INFO (Signers): >> Signing a transfer SOL transaction using its registered signers signature: null ```
- Loading branch information
1 parent
7ec1ea5
commit 889d0eb
Showing
6 changed files
with
274 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2023 Solana Labs, Inc | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"name": "@solana/example-signers", | ||
"private": true, | ||
"type": "module", | ||
"scripts": { | ||
"prestart": "turbo --output-logs=errors-only compile:js compile:typedefs", | ||
"run:example": "tsx src/example.ts", | ||
"start": "pnpm run:example", | ||
"style:fix": "pnpm eslint --fix src/* && pnpm prettier --log-level warn --ignore-unknown --write ./*", | ||
"test:lint": "TERM_OVERRIDE=\"${TURBO_HASH:+dumb}\" TERM=${TERM_OVERRIDE:-$TERM} jest -c ../../node_modules/@solana/test-config/jest-lint.config.ts --rootDir . --silent --testMatch '<rootDir>src/**/*.{ts,tsx}'", | ||
"test:prettier": "TERM_OVERRIDE=\"${TURBO_HASH:+dumb}\" TERM=${TERM_OVERRIDE:-$TERM} jest -c ../../node_modules/@solana/test-config/jest-prettier.config.ts --rootDir . --silent", | ||
"test:typecheck": "tsc" | ||
}, | ||
"dependencies": { | ||
"@solana-program/system": "^0.5.0", | ||
"@solana/example-utils": "workspace:*", | ||
"@solana/web3.js": "workspace:*" | ||
}, | ||
"devDependencies": { | ||
"start-server-and-test": "^2.0.5", | ||
"tsx": "^4.16.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[ | ||
107, 145, 66, 123, 253, 251, 77, 186, 176, 211, 187, 232, 47, 142, 54, 214, 142, 152, 37, 182, 65, 117, 85, 75, 133, | ||
97, 107, 11, 180, 24, 73, 245, 160, 114, 3, 57, 51, 114, 113, 153, 78, 211, 199, 86, 240, 220, 223, 19, 254, 107, | ||
250, 11, 190, 31, 112, 13, 15, 146, 198, 211, 48, 140, 218, 239 | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
/** | ||
* EXAMPLE | ||
* Create and use signers with @solana/web3.js. | ||
* | ||
* Before running any of the examples in this monorepo, make sure to set up a test validator by | ||
* running `pnpm test:live-with-test-validator:setup` in the root directory. | ||
* | ||
* To run this example, execute `pnpm start` in this directory. | ||
*/ | ||
import { createLogger } from '@solana/example-utils/createLogger.js'; | ||
import { | ||
address, | ||
appendTransactionMessageInstruction, | ||
Blockhash, | ||
CompilableTransactionMessage, | ||
compileTransaction, | ||
createKeyPairSignerFromBytes, | ||
createKeyPairSignerFromPrivateKeyBytes, | ||
createNoopSigner, | ||
createSignableMessage, | ||
createTransactionMessage, | ||
generateKeyPairSigner, | ||
getBase58Decoder, | ||
MessagePartialSigner, | ||
partiallySignTransactionMessageWithSigners, | ||
pipe, | ||
setTransactionMessageFeePayerSigner, | ||
setTransactionMessageLifetimeUsingBlockhash, | ||
TransactionPartialSigner, | ||
TransactionSigner, | ||
} from '@solana/web3.js'; | ||
import { getTransferSolInstruction } from '@solana-program/system'; | ||
import { readFile } from 'fs/promises'; | ||
import path from 'path'; | ||
|
||
const log = createLogger('Signers'); | ||
|
||
/** | ||
* SETUP: CREATE A NEW TRANSACTION MESSAGE | ||
* This helper function creates a new transaction message for us such that: | ||
* - Its lifetime is set to the latest blockhash. | ||
* - Its fee payer is set to the given signer. | ||
* - It contains a single instruction to transfer 1 SOL to some other address. | ||
* | ||
* You can read more about this transaction and how to send it | ||
* in the `examples/transfer-lamports` example. | ||
*/ | ||
function getTransferSolTransactionMessage(signer: TransactionSigner) { | ||
// Create the transfer SOL instruction by passing the signer as the source. | ||
const instruction = getTransferSolInstruction({ | ||
amount: 1n, | ||
destination: address('ED1WqT2hWJLSZtj4TtTdoovmpMrr7zpkUdbfxmcJR1Fq'), | ||
source: signer, // <- We pass the signer here, not just the address. | ||
}); | ||
|
||
// Use a mock blockhash as a transaction lifetime constraint. | ||
const mockBlockhash = { | ||
blockhash: '9fBfi7Q23LHd6gDENDhp25jRnzeGJZesAtCkKuqkga63' as Blockhash, | ||
lastValidBlockHeight: 1119n, | ||
}; | ||
|
||
// Prepare the transaction message. | ||
return pipe( | ||
createTransactionMessage({ version: 0 }), | ||
tx => setTransactionMessageLifetimeUsingBlockhash(mockBlockhash, tx), | ||
tx => setTransactionMessageFeePayerSigner(signer, tx), // <- Here as well, we provide the payer as a signer. | ||
tx => appendTransactionMessageInstruction(instruction, tx), | ||
); | ||
} | ||
|
||
/** | ||
* SETUP: SIGN A MESSAGE | ||
* This helper function signs a message using the given signer. | ||
*/ | ||
async function signMessage(signer: MessagePartialSigner, message: string) { | ||
const [signatureDictionary] = await signer.signMessages([createSignableMessage(message)]); | ||
const signature = signatureDictionary[signer.address]; | ||
log.info( | ||
{ signature: signature ? getBase58Decoder().decode(signature) : null }, | ||
`>> Signing the message "${message}"`, | ||
); | ||
} | ||
|
||
/** | ||
* SETUP: SIGN A TRANSACTION | ||
* This helper function signs a transaction message using the given signer. | ||
*/ | ||
async function signTransaction(signer: TransactionPartialSigner, transactionMessage: CompilableTransactionMessage) { | ||
const transaction = compileTransaction(transactionMessage); | ||
const [signatureDictionary] = await signer.signTransactions([transaction]); | ||
const signature = signatureDictionary[signer.address]; | ||
log.info( | ||
{ signature: signature ? getBase58Decoder().decode(signature) : null }, | ||
'>> Signing a transfer SOL transaction', | ||
); | ||
} | ||
|
||
/** | ||
* SETUP: SIGN A TRANSACTION USING REGISTERED SIGNERS | ||
* This helper function signs a transaction message by retrieving | ||
* the signers registered within the transaction message. | ||
* For instance, in our transfer SOL transaction message, we can | ||
* extract the fee payer and the transfer source as signers. | ||
*/ | ||
async function signTransactionWithSigners(transactionMessage: CompilableTransactionMessage) { | ||
const signedTransaction = await partiallySignTransactionMessageWithSigners(transactionMessage); | ||
const signature = signedTransaction.signatures[transactionMessage.feePayer]; | ||
log.info( | ||
{ signature: signature ? getBase58Decoder().decode(signature) : null }, | ||
'>> Signing a transfer SOL transaction using its registered signers', | ||
); | ||
} | ||
|
||
/** | ||
* OPTION 1: GENERATED KEY PAIR SIGNER | ||
* With this option, we generate a brand new key pair | ||
* and wrap it in a signer object. | ||
*/ | ||
{ | ||
// Generate a new key pair signer. | ||
const signer = await generateKeyPairSigner(); | ||
log.info({ address: signer.address }, '[option 1] Using a generated key pair signer'); | ||
|
||
// Use it to sign messages and transactions. | ||
const transactionMessage = getTransferSolTransactionMessage(signer); | ||
await signMessage(signer, 'Hello, World!'); | ||
await signTransaction(signer, transactionMessage); | ||
await signTransactionWithSigners(transactionMessage); | ||
} | ||
|
||
/** | ||
* OPTION 2: KEY PAIR SIGNER FROM FILE | ||
* With this option, we load the content of the key pair | ||
* from a JSON file and wrap it in a signer object. | ||
*/ | ||
{ | ||
// Load a key pair signer from a JSON file. | ||
const keypairPath = path.join('src', 'example-keypair.json'); | ||
const keypairBytes = new Uint8Array(JSON.parse(await readFile(keypairPath, 'utf-8'))); | ||
const signer = await createKeyPairSignerFromBytes(keypairBytes); | ||
log.info({ address: signer.address }, '[option 2] Using a key pair signer from a JSON file'); | ||
|
||
// Use it to sign messages and transactions. | ||
const transactionMessage = getTransferSolTransactionMessage(signer); | ||
await signMessage(signer, 'Hello, World!'); | ||
await signTransaction(signer, transactionMessage); | ||
await signTransactionWithSigners(transactionMessage); | ||
} | ||
|
||
/** | ||
* OPTION 3: KEY PAIR SIGNER FROM SEED | ||
* With this option, we derive a key pair from a seed — | ||
* i.e. the 32 bytes of its private key — and wrap it | ||
* in a signer object. | ||
*/ | ||
{ | ||
// Access the 32 bytes of the private key. | ||
// Note that this is the first 32 bytes of the `example-keypair.json` file, | ||
// Meaning we will get the same signatures as in the previous example. | ||
const seed = new Uint8Array([ | ||
107, 145, 66, 123, 253, 251, 77, 186, 176, 211, 187, 232, 47, 142, 54, 214, 142, 152, 37, 182, 65, 117, 85, 75, | ||
133, 97, 107, 11, 180, 24, 73, 245, | ||
]); | ||
|
||
// Create a key pair signer using this private key seed. | ||
const signer = await createKeyPairSignerFromPrivateKeyBytes(seed); | ||
log.info({ address: signer.address }, '[option 3] Using a key pair signer from a seed'); | ||
|
||
// Use it to sign messages and transactions. | ||
const transactionMessage = getTransferSolTransactionMessage(signer); | ||
await signMessage(signer, 'Hello, World!'); | ||
await signTransaction(signer, transactionMessage); | ||
await signTransactionWithSigners(transactionMessage); | ||
} | ||
|
||
/** | ||
* OPTION 4: NOOP SIGNER | ||
* With this option, we create a no-operation signer that pretends | ||
* sign messages and transactions without actually doing so. | ||
* This can be useful when a function requires a signer object but | ||
* we don't actually want to sign anything at this point. | ||
* For instance, we can use it to create a transaction with a | ||
* server-side fee payer that will be signed by the server later on. | ||
*/ | ||
{ | ||
// Create a no-op signer. | ||
const signer = createNoopSigner(address('BoK4mWeYVU6LdgNfo8QF7zxmTRiHw7VKMhodToZgrRup')); | ||
log.info({ address: signer.address }, '[option 4] Using a no-op signer'); | ||
|
||
// Use it to sign messages and transactions. | ||
const transactionMessage = getTransferSolTransactionMessage(signer); | ||
await signMessage(signer, 'Hello, World!'); | ||
await signTransaction(signer, transactionMessage); | ||
await signTransactionWithSigners(transactionMessage); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"$schema": "https://json.schemastore.org/tsconfig", | ||
"compilerOptions": { | ||
"module": "NodeNext", | ||
"moduleResolution": "NodeNext", | ||
"noEmit": true, | ||
"target": "ESNext" | ||
}, | ||
"display": "@solana/example-signers", | ||
"extends": "../../packages/tsconfig/base.json", | ||
"include": ["src"] | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.