UNPKG

iso-filecoin

Version:

Isomorphic filecoin abstractions for RPC, signatures, address, token and wallet

208 lines (182 loc) 4.75 kB
import { encode } from '@ipld/dag-cbor' import { base64pad } from 'iso-base/rfc4648' import { z } from 'zod' import * as Address from './address.js' import { Token } from './token.js' import { lotusCid } from './utils.js' /** * @typedef {import('./types.js').MessageObj} MessageObj * @typedef {import('./types.js').PartialMessageObj} PartialMessageObj */ /** * Message validation schema */ export const MessageSchema = z.object({ version: z.literal(0).default(0), nonce: z.int().nonnegative().default(0), gasLimit: z.int().nonnegative().default(0), gasFeeCap: z.string().default('0'), gasPremium: z.string().default('0'), method: z.int().nonnegative().default(0), to: z.string(), from: z.string(), /** * Value in attoFIL */ value: z .string() .min(1) .refine((v) => !v.startsWith('-'), { message: 'value must not be negative', }), /** * Params encoded as base64pad */ params: z.base64().default(''), }) const MessageSchemaPartial = MessageSchema.partial({ version: true, nonce: true, gasLimit: true, gasFeeCap: true, gasPremium: true, method: true, params: true, }) export const Schemas = { message: MessageSchema, messagePartial: MessageSchemaPartial, } /** * Filecoin Message class */ export class Message { /** @type {Uint8Array | undefined} */ #bytes /** @type {Uint8Array | undefined} */ #cidBytes /** * * @param {PartialMessageObj} msg */ constructor(msg) { const _msg = MessageSchema.parse(msg) this.version = _msg.version this.to = _msg.to this.from = _msg.from this.nonce = _msg.nonce this.value = _msg.value this.gasLimit = _msg.gasLimit this.gasFeeCap = _msg.gasFeeCap this.gasPremium = _msg.gasPremium this.method = _msg.method this.params = _msg.params } /** * Convert message to Lotus message */ toLotus() { return { Version: this.version, To: this.to, From: this.from, Nonce: this.nonce, Value: this.value, GasLimit: this.gasLimit, GasFeeCap: this.gasFeeCap, GasPremium: this.gasPremium, Method: this.method, Params: this.params, } } /** * Create message from Lotus message * * @param {import('./types').LotusMessage} json */ static fromLotus(json) { /** @type {MessageObj} */ const obj = { version: json.Version, to: json.To, from: json.From, nonce: json.Nonce, value: json.Value, gasLimit: json.GasLimit, gasFeeCap: json.GasFeeCap, gasPremium: json.GasPremium, method: json.Method, params: json.Params, } return new Message(obj) } /** * Prepare message for signing with nonce and gas estimation * * @param {import('./rpc.js').RPC} rpc */ async prepare(rpc) { if (Address.isIdMaskAddress(this.to)) { throw new Error('ID mask addresses are not supported for recipient') } const parsedTo = Address.from(this.to, rpc.network) // Change to method to InvokeEVM for Bare value sends to 0x address to avoid losing funds // @see https://docs.filecoin.io/smart-contracts/filecoin-evm-runtime/difference-with-ethereum#bare-value-sends if (Address.isAddressDelegated(parsedTo) && this.method === 0) { this.method = 3844450837 } this.to = parsedTo.toString() if (this.nonce === 0) { const nonce = await rpc.nonce(this.from) if (nonce.error) { throw new Error(nonce.error.message) } this.nonce = nonce.result } if ( (this.gasLimit === 0 && this.gasFeeCap === '0') || this.gasPremium === '0' ) { const gas = await rpc.gasEstimate({ msg: this }) if (gas.error) { throw new Error(gas.error.message) } this.gasLimit = gas.result.GasLimit this.gasFeeCap = gas.result.GasFeeCap this.gasPremium = gas.result.GasPremium } return this } /** * Serialize message using dag-cbor */ serialize() { if (this.#bytes) { return this.#bytes } const msg = [ this.version, Address.fromString(this.to).toBytes(), Address.fromString(this.from).toBytes(), this.nonce, Token.fromAttoFIL(this.value).toBytes(), this.gasLimit, Token.fromAttoFIL(this.gasFeeCap).toBytes(), Token.fromAttoFIL(this.gasPremium).toBytes(), this.method, base64pad.decode(this.params), ] this.#bytes = /** @type {Uint8Array}*/ (encode(msg)) return this.#bytes } /** * CID bytes of the filecoin message */ cidBytes() { if (this.#cidBytes) { return this.#cidBytes } this.#cidBytes = lotusCid(this.serialize()) return this.#cidBytes } }