Skip to content

Instantly share code, notes, and snippets.

@cordt-sei
Forked from besated/associate.ts
Last active December 12, 2024 14:22
Show Gist options
  • Save cordt-sei/e263b908f086d67e580666014609041c to your computer and use it in GitHub Desktop.
Save cordt-sei/e263b908f086d67e580666014609041c to your computer and use it in GitHub Desktop.
// 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