Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/repositories/contract-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { SecretsProvider } from '../providers/secrets'
import { convertAvlTree as convertAvlTrees } from './helpers/contract'
import { AvlClient } from './helpers/avl-client'
import { getRequest, promiseRetry } from './helpers/client'
import { createTransactionFromLedgerClient, createTransactionFromMetaMaskClient, createTransactionFromPartisiaClient, createTransactionFromPrivateKey } from '../transactions'


/**
Expand Down Expand Up @@ -116,18 +115,29 @@ export class ContractRepository implements IContractRepository {
// Remove contract cache as the state will change
this.cleanCache(contractAddress)

// Loaded on demand. Signing pulls in the crypto stack -- several hundred
// kilobytes -- and reading contract state, which is what most consumers do,
// never reaches this method.
switch (this.secrets.strategy) {
case 'privateKey':
case 'privateKey': {
const { createTransactionFromPrivateKey } = await import('../transactions')
return createTransactionFromPrivateKey(this.rpc, contractAddress, this.secrets.privateKey, payload, isMainnet, gas)
}

case 'partisiaSdk':
case 'partisiaSdk': {
const { createTransactionFromPartisiaClient } = await import('../transactions')
return createTransactionFromPartisiaClient(this.rpc, this.secrets.partisiaSdk, contractAddress, payload, gas)
}

case 'MetaMask':
case 'MetaMask': {
const { createTransactionFromMetaMaskClient } = await import('../transactions')
return createTransactionFromMetaMaskClient(this.rpc, this.secrets.metaMask, contractAddress, payload, isMainnet, gas)
}

case 'Ledger':
case 'Ledger': {
const { createTransactionFromLedgerClient } = await import('../transactions')
return createTransactionFromLedgerClient(this.rpc, this.secrets.ledger, contractAddress, payload, isMainnet, gas)
}

default:
throw new Error('Signing strategy not found')
Expand Down
12 changes: 8 additions & 4 deletions src/transactions/helper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { RpcContractBuilder } from "@partisiablockchain/abi-client"
import { serializedTransaction } from "partisia-blockchain-applications-crypto/lib/main/transaction"
import { PartisiaAccountClass } from "partisia-blockchain-applications-rpc/lib/main/accountInfo"
import { PartisiaRpcClass } from "partisia-blockchain-applications-rpc/lib/main/rpc"
import type { RpcContractBuilder } from "@partisiablockchain/abi-client"
import type { PartisiaAccountClass } from "partisia-blockchain-applications-rpc/lib/main/accountInfo"
import type { PartisiaRpcClass } from "partisia-blockchain-applications-rpc/lib/main/rpc"

export const builderToBytesBe = (rpc: RpcContractBuilder) => {
return rpc.getBytes()
Expand All @@ -17,6 +16,11 @@ export const serializeTransaction = async (
cost: number | string,
validityInMillis: number = 120_000
) => {
// `builderToBytesBe` is imported from this module by the record and domain
// actions, which are on the read path. Keeping the crypto import dynamic means
// reading state never loads it.
const { serializedTransaction } = await import("partisia-blockchain-applications-crypto/lib/main/transaction")

const shardId = rpc.deriveShardId(walletAddress)
const nonce = await rpc.getNonce(walletAddress, shardId)
// Need to pass a number otherwise the internal library will throw an error
Expand Down
36 changes: 30 additions & 6 deletions src/transactions/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { PartisiaAccountClass } from "partisia-blockchain-applications-rpc/lib/main/accountInfo"
import type { PartisiaAccountClass } from "partisia-blockchain-applications-rpc/lib/main/accountInfo"
import type LedgerTransport from "@ledgerhq/hw-transport"
import { ITransactionIntent, MetaMaskSdk } from "../interface"
import { PartisiaLedgerClient, signatureToBuffer } from "./ledger"
import type { ITransactionIntent, MetaMaskSdk } from "../interface"
import { buildTransactionResult, getChainId, serializeTransaction } from "./helper"
import { deriveDigest, getTransactionPayloadData, getTrxHash } from "partisia-blockchain-applications-crypto/lib/main/transaction"
import { PartisiaRpc } from "partisia-blockchain-applications-rpc"
import assert from "assert"
import PartisiaSdk from "partisia-blockchain-applications-sdk"
import { privateKeyToAccountAddress, signTransaction } from "partisia-blockchain-applications-crypto/lib/main/wallet"
import type PartisiaSdk from "partisia-blockchain-applications-sdk"

/**
* The signing backends are loaded on demand.
*
* `partisia-blockchain-applications-crypto` pulls in bip39, elliptic and
* tiny-secp256k1 (~500 KB); the Ledger client pulls in `bip32-path` and the
* `@ledgerhq` transport. All three stay regular dependencies, so nothing extra
* has to be installed, but a bundler splits them out of the entry chunk: a
* consumer that only reads contract state never downloads them, one that signs
* with MetaMask does not pay for the Ledger transport, and one that signs with
* a Ledger does not pay for the BIP-39 wordlists.
*/
const loadTransactionCrypto = () => import("partisia-blockchain-applications-crypto/lib/main/transaction")
const loadWalletCrypto = () => import("partisia-blockchain-applications-crypto/lib/main/wallet")
const loadLedgerClient = () => import("./ledger")

export const createTransactionFromLedgerClient = async (
rpc: PartisiaAccountClass,
Expand All @@ -17,6 +29,11 @@ export const createTransactionFromLedgerClient = async (
isMainnet = false,
cost: number | string = 10490
): Promise<ITransactionIntent> => {
const [{ PartisiaLedgerClient, signatureToBuffer }, { deriveDigest, getTrxHash }] = await Promise.all([
loadLedgerClient(),
loadTransactionCrypto()
])

const client = new PartisiaLedgerClient(transport)
const walletAddress: string = await client.getAddress()
const shardId = rpc.deriveShardId(walletAddress)
Expand Down Expand Up @@ -48,6 +65,8 @@ export const createTransactionFromMetaMaskClient = async (
isMainnet = false,
cost: number | string = 10490
): Promise<ITransactionIntent> => {
const { deriveDigest, getTrxHash } = await loadTransactionCrypto()

const snapId = "npm:@partisiablockchain/snap"
const walletAddress: string = await client.request({
method: "wallet_invokeSnap",
Expand Down Expand Up @@ -132,6 +151,11 @@ export const createTransactionFromPrivateKey = async (
isMainnet = false,
cost: number | string = 8490
): Promise<ITransactionIntent> => {
const [{ deriveDigest, getTransactionPayloadData, getTrxHash }, { privateKeyToAccountAddress, signTransaction }] = await Promise.all([
loadTransactionCrypto(),
loadWalletCrypto()
])

const walletAddress = privateKeyToAccountAddress(privateKey)
const shardId = rpc.deriveShardId(walletAddress)
const url = rpc.getShardUrl(shardId)
Expand Down
Loading