starknet.js icon indicating copy to clipboard operation
starknet.js copied to clipboard

Consider integrating features used by wallet's into the repo

Open tabaktoni opened this issue 2 years ago • 13 comments

Reported by Axel please consider including some of this code in starknet.js as we end up redefining it in all of our projects

import {
  CallData,
  Contract,
  hash,
  Signer,
  encode,
  ec,
  type RawArgs,
  SignerInterface,
  type Signature,
  typedData,
  transaction,
  type Abi,
  type Call,
  type DeclareSignerDetails,
  type DeployAccountSignerDetails,
  type InvocationsSignerDetails,
} from "starknet";
import { SequencerProvider } from "starknet";

const devnetBaseUrl = "http://127.0.0.1:5050";

export const provider = new SequencerProvider({ baseUrl: devnetBaseUrl });

export async function loadContract(contractAddress: string) {
  const { abi } = await provider.getClassAt(contractAddress);
  if (!abi) {
    throw new Error("Error while getting ABI");
  }
  return new Contract(abi, contractAddress, provider);
}

export class KeyPair extends Signer {
  constructor(pk?: string | bigint) {
    super(pk ? `${pk}` : `0x${encode.buf2hex(ec.starkCurve.utils.randomPrivateKey())}`);
  }

  public get privateKey() {
    return BigInt(this.pk as string);
  }

  public get publicKey() {
    return BigInt(ec.starkCurve.getStarkKey(this.pk));
  }

  public signHash(messageHash: string) {
    const { r, s } = ec.starkCurve.sign(messageHash, this.pk);
    return [r.toString(), s.toString()];
  }
}

export const randomKeyPair = () => new KeyPair();

/**
 * This class allows to easily implement custom signers by overriding the `signRaw` method.
 * This is based on Starknet.js implementation of Signer, but it delegates the actual signing to an abstract function
 */
export abstract class RawSigner implements SignerInterface {
  abstract signRaw(messageHash: string): Promise<Signature>;

  public async getPubKey(): Promise<string> {
    throw Error("This signer allows multiple public keys");
  }

  public async signMessage(typedDataArgument: typedData.TypedData, accountAddress: string): Promise<Signature> {
    const messageHash = typedData.getMessageHash(typedDataArgument, accountAddress);
    return this.signRaw(messageHash);
  }

  public async signTransaction(
    transactions: Call[],
    transactionsDetail: InvocationsSignerDetails,
    abis?: Abi[],
  ): Promise<Signature> {
    if (abis && abis.length !== transactions.length) {
      throw new Error("ABI must be provided for each transaction or no transaction");
    }
    // now use abi to display decoded data somewhere, but as this signer is headless, we can't do that
    const calldata = transaction.getExecuteCalldata(transactions, transactionsDetail.cairoVersion);

    const messageHash = hash.calculateTransactionHash(
      transactionsDetail.walletAddress,
      transactionsDetail.version,
      calldata,
      transactionsDetail.maxFee,
      transactionsDetail.chainId,
      transactionsDetail.nonce,
    );
    return this.signRaw(messageHash);
  }

  public async signDeployAccountTransaction({
    classHash,
    contractAddress,
    constructorCalldata,
    addressSalt,
    maxFee,
    version,
    chainId,
    nonce,
  }: DeployAccountSignerDetails) {
    const messageHash = hash.calculateDeployAccountTransactionHash(
      contractAddress,
      classHash,
      CallData.compile(constructorCalldata),
      addressSalt,
      version,
      maxFee,
      chainId,
      nonce,
    );

    return this.signRaw(messageHash);
  }

  public async signDeclareTransaction(
    // contractClass: ContractClass,  // Should be used once class hash is present in ContractClass
    { classHash, senderAddress, chainId, maxFee, version, nonce, compiledClassHash }: DeclareSignerDetails,
  ) {
    const messageHash = hash.calculateDeclareTransactionHash(
      classHash,
      senderAddress,
      version,
      maxFee,
      chainId,
      nonce,
      compiledClassHash,
    );

    return this.signRaw(messageHash);
  }
}

export async function mintEth(address: string) {
  await handlePost("mint", { address, amount: 1e18, lite: true });
}

async function handlePost(path: string, payload?: RawArgs) {
  const response = await fetch(`${provider.baseUrl}/${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  if (!response.ok) {
    throw new Error(`HTTP error! Status: ${response.status} Message: ${await response.text()}`);
  }
}

tabaktoni avatar Oct 13 '23 10:10 tabaktoni

Hello! Can I take this?

edisontim avatar Nov 17 '23 17:11 edisontim

@edisontim sure! thanks!

ivpavici avatar Nov 20 '23 09:11 ivpavici

Do you know where I can find any RawSigner actual implementation that I could use to test this with?

edisontim avatar Nov 27 '23 11:11 edisontim

@edisontim how far, are you still working on this?

WiseMrMusa avatar Feb 19 '24 19:02 WiseMrMusa

Hey! I have a branch with everything implemented but needed a RawSigner implementation to test it. I left the issue on the side for a while but just sent a message to Alex from Argent to get it. Will start working on it again when I get an answer :)

edisontim avatar Feb 26 '24 14:02 edisontim

Hi! Are you still working on it?

WiseMrMusa avatar Mar 07 '24 18:03 WiseMrMusa

Hey! No you can take this :)

edisontim avatar Mar 22 '24 17:03 edisontim

@WiseMrMusa good luck! :)

ivpavici avatar Mar 26 '24 11:03 ivpavici

I am just seeing this now, I will add it to my schedule 😄

WiseMrMusa avatar Mar 30 '24 09:03 WiseMrMusa

unassigned due to being stale and will be offered to participants of the ODHack event https://onlydust.notion.site/ODHack-Common-Guidelines-b9c6b6a4ac4146d087185568aca38a3f

ivpavici avatar Apr 19 '24 13:04 ivpavici

Hello @ivpavici can I work on this?

IjayAbby avatar Apr 23 '24 18:04 IjayAbby

@ivpavici what are some of the methods that I need to implement?

IjayAbby avatar Apr 30 '24 01:04 IjayAbby

@tabaktoni can you please point out a bit more details?

ivpavici avatar Apr 30 '24 09:04 ivpavici