-
-
Save cordt-sei/e263b908f086d67e580666014609041c to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| // Here are detailed **4** different methods with which a new account can be "associated" or "linked" on-chain | |
| // In very clear terms, all this is doing is making the pubkey known to the chain. | |
| // This is required because there is no way to determine either of the wallet addresses from the other. | |
| import secp256k1 from "secp256k1"; | |
| import { createPublicClient, createWalletClient, hexToNumber, http, numberToHex, parseSignature, toHex } from "viem"; | |
| import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; | |
| import { seiTestnet } from "viem/chains"; | |
| import { ADDRESS_PRECOMPILE_ABI, ADDRESS_PRECOMPILE_ADDRESS } from '@sei-js/evm'; | |
| // method 1 | |
| // Replace `pk` | |
| // *** Requires private key to be directly available. *** | |
| // *** High security risk: Private key exposure can compromise the entire wallet. *** | |
| const PRIVATE_KEY = ""; | |
| const publicClient = createPublicClient({ | |
| chain: seiTestnet, | |
| transport: http(), | |
| }); | |
| const client = createWalletClient({ | |
| chain: seiTestnet, | |
| transport: http(), | |
| }); | |
| interface AssociateRequest { | |
| r: string; | |
| s: string; | |
| v: string; | |
| custom_message: string; | |
| } | |
| interface AssociateRequestSchema { | |
| Method: 'sei_associate'; | |
| Parameters: [request: AssociateRequest]; | |
| ReturnType: null; | |
| } | |
| const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | |
| // method 2 | |
| // Associate account using signed message | |
| // *** Medium security risk: Requires private key to sign a message. *** | |
| // *** User action: Must authorize the signing of a specific message. *** | |
| const associate = async () => { | |
| const account = privateKeyToAccount(PRIVATE_KEY); | |
| const newPk = generatePrivateKey(); | |
| const newAccount = privateKeyToAccount(newPk); | |
| // Sign a message | |
| const message = "associate"; | |
| const signature = await newAccount.signMessage({ message }); | |
| const parsedSignature = parseSignature(signature); | |
| const messageLength = Buffer.from(message, 'utf8').length; | |
| const signedMessage = `\x19Ethereum Signed Message:\n${messageLength}${message}`; | |
| const response = await client.writeContract({ | |
| account, | |
| address: ADDRESS_PRECOMPILE_ADDRESS, | |
| abi: ADDRESS_PRECOMPILE_ABI, | |
| functionName: "associate", | |
| args: [numberToHex(Number(parsedSignature.v) - 27), parsedSignature.r, parsedSignature.s, signedMessage], | |
| gasPrice: BigInt(100_000_000_000), // 100 GWEI | |
| }); | |
| console.log(response); | |
| // Get evm address after association | |
| await sleep(3000); // Sometimes it fails if you query it immeditely because RPC nodes are behind | |
| const evmAddress = await publicClient.readContract({ | |
| address: ADDRESS_PRECOMPILE_ADDRESS, | |
| abi: ADDRESS_PRECOMPILE_ABI, | |
| functionName: "getSeiAddr", | |
| args: [newAccount.address], | |
| }); | |
| console.log(evmAddress); | |
| }; | |
| // method 3 | |
| // Associate account using public key | |
| // *** Lower security risk: Public key exposure is less sensitive than private key. *** | |
| // *** User action: Requires a generated key pair to use. *** | |
| const associateViaPubkey = async () => { | |
| const account = privateKeyToAccount(PRIVATE_KEY); | |
| const newPk = generatePrivateKey(); | |
| const newAccount = privateKeyToAccount(newPk); | |
| // Compress public key | |
| const publicKeyBuffer = Buffer.from(newAccount.publicKey.slice(2), "hex"); | |
| const compressedPubKey = secp256k1.publicKeyConvert(publicKeyBuffer, true); | |
| const response = await client.writeContract({ | |
| account, | |
| address: ADDRESS_PRECOMPILE_ADDRESS, | |
| abi: ADDRESS_PRECOMPILE_ABI, | |
| functionName: "associatePubKey", | |
| args: [Buffer.from(compressedPubKey).toString("hex")], | |
| gasPrice: BigInt(100_000_000_000), // 100 GWEI | |
| }); | |
| console.log(response); | |
| // Get evm address after association | |
| await sleep(3000); // Sometimes it fails if you query it immeditely because RPC nodes are behind | |
| const evmAddress = await publicClient.readContract({ | |
| address: ADDRESS_PRECOMPILE_ADDRESS, | |
| abi: ADDRESS_PRECOMPILE_ABI, | |
| functionName: "getSeiAddr", | |
| args: [newAccount.address], | |
| }); | |
| console.log(evmAddress); | |
| }; | |
| // method 4 | |
| // Associate account using a signed message without gas (only allowed if the account already has funds) | |
| // *** Low security risk: Only requires signing a message and no private key exposure. *** | |
| // *** User action: Requires authorization to sign a message. *** | |
| const associateGasless = async (signature: `0x${string}`, message: string) => { | |
| const parsedSignature = parseSignature(signature); | |
| const messageLength = Buffer.from(message, 'utf8').length; | |
| const messageToSign = `\x19Ethereum Signed Message:\n${messageLength}${message}`; | |
| const request: AssociateRequest = { | |
| r: parsedSignature.r, | |
| s: parsedSignature.s, | |
| v: numberToHex(Number(parsedSignature.v) - 27), | |
| custom_message: messageToSign, | |
| }; | |
| const response = await client.request<AssociateRequestSchema>({ | |
| method: 'sei_associate', | |
| params: [request], | |
| }); | |
| console.log(response); | |
| }; | |
| const main = async () => { | |
| associate(); | |
| // associateViaPubkey(); | |
| // associateGasless(signature, message); | |
| }; | |
| main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment