Skip to content

Commit

Permalink
feat: support f1/f2/f3 to ID and back to robust expect for f4
Browse files Browse the repository at this point in the history
  • Loading branch information
hugomrdias committed Oct 21, 2024
1 parent 3508d54 commit f3d5e5f
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 4 deletions.
96 changes: 96 additions & 0 deletions packages/iso-filecoin/src/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
getNetwork,
} from './utils.js'

export { checksumEthAddress } from './utils.js'

/**
* @typedef {import('./types.js').Address} IAddress
* @typedef { string | IAddress | BufferSource} Value
Expand Down Expand Up @@ -37,6 +39,46 @@ export function isAddress(val) {
return Boolean(val?.[symbol])
}

/**
* Check if object is a {@link AddressSecp256k1} instance
*
* @param {any} val
* @returns {val is AddressSecp256k1}
*/
export function isAddressSecp256k1(val) {
return Boolean(val?.[symbol]) && val.protocol === PROTOCOL_INDICATOR.SECP256K1
}

/**
* Check if object is a {@link AddressBLS} instance
*
* @param {any} val
* @returns {val is AddressBLS}
*/
export function isAddressBls(val) {
return Boolean(val?.[symbol]) && val.protocol === PROTOCOL_INDICATOR.BLS
}

/**
* Check if object is a {@link AddressId} instance
*
* @param {any} val
* @returns {val is AddressId}
*/
export function isAddressId(val) {
return Boolean(val?.[symbol]) && val.protocol === PROTOCOL_INDICATOR.ID
}

/**
* Check if object is a {@link AddressDelegated} instance
*
* @param {any} val
* @returns {val is AddressDelegated}
*/
export function isAddressDelegated(val) {
return Boolean(val?.[symbol]) && val.protocol === PROTOCOL_INDICATOR.DELEGATED
}

/**
* Validate checksum
*
Expand Down Expand Up @@ -276,6 +318,41 @@ class Address {
dkLen: 4,
})
}

/**
*
* @param {import('./rpc.js').RPC} rpc
*/
async toID(rpc) {
if (rpc.network !== this.network) {
throw new Error(
`Network mismatch. RPC network: ${rpc.network} Address network: ${this.network}`
)
}

if (this.protocol === 0) {
return AddressId.fromString(this.toString())
}

if (this.protocol === 4) {
throw new Error(
`Cannot convert delegated address to ID: ${this.toString()}`
)
}
const r = await rpc.filecoinAddressToEthAddress({
address: this.toString(),
})

if (r.error) {
throw new Error(r.error.message)
}

if (isIdMaskAddress(r.result)) {
return AddressId.fromEthAddress(r.result, this.network)
}

throw new Error(`Invalid ID address: ${r.result}`)
}
}

/**
Expand Down Expand Up @@ -380,6 +457,25 @@ export class AddressId extends Address {
toString() {
return `${this.networkPrefix}${this.protocol}${this.id}`
}

/**
*
* @param {import('./rpc.js').RPC} rpc
*/
async toRobust(rpc) {
if (rpc.network !== this.network) {
throw new Error(
`Network mismatch. RPC network: ${rpc.network} Address network: ${this.network}`
)
}

const r = await rpc.stateAccountKey({ address: this.toString() })
if (r.error) {
throw new Error(r.error.message)
}

return fromString(r.result)
}
}

/**
Expand Down
113 changes: 111 additions & 2 deletions packages/iso-filecoin/src/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,122 @@ export class RPC {
)
}

/**
* Converts any Filecoin address to an EthAddress.
*
* @see https://github.com/filecoin-project/lotus/blob/471819bf1ef8a4d5c7c0476a38ce9f5e23c59bfc/api/api_full.go#L743-L768
* @param {import('./types.js').FilecoinAddressToEthAddressParams} params
*/
async filecoinAddressToEthAddress(params, fetchOptions = {}) {
return await /** @type {typeof this.call<string>} */ (this.call)(
{
method: 'Filecoin.FilecoinAddressToEthAddress',
params: [params.address, params.blockNumber],
},
fetchOptions
)
}

/**
* Public key address of the given ID address.
*
* @see https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-v0-methods.md#StateAccountKey
*
*
* @param {import('./types.js').StateAccountKeyParams} params
*/
async stateAccountKey(params, fetchOptions = {}) {
const r = await /** @type {typeof this.call<string>} */ (this.call)(
{
method: 'Filecoin.StateAccountKey',
params: [
params.address,
params === undefined ? null : params.tipSetKey,
],
},
fetchOptions
)

if (r.error) {
return r
}

return {
result:
this.network === 'testnet' ? r.result.replace('f', 't') : r.result,
error: undefined,
}
}

/**
* Public key address of the given non-account ID address.
*
* @see https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-v0-methods.md#StateLookupRobustAddress
*
*
* @param {import('./types.js').StateAccountKeyParams} params
*/
async stateLookupRobustAddress(params, fetchOptions = {}) {
const r = await /** @type {typeof this.call<string>} */ (this.call)(
{
method: 'Filecoin.StateLookupRobustAddress',
params: [
params.address,
params === undefined ? null : params.tipSetKey,
],
},
fetchOptions
)

if (r.error) {
return r
}

return {
result:
this.network === 'testnet' ? r.result.replace('f', 't') : r.result,
error: undefined,
}
}

/**
* Retrieves the ID address of the given address
*
* @see https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-v0-methods.md#statelookupid
*
*
* @param {import('./types.js').StateAccountKeyParams} params
*/
async stateLookupID(params, fetchOptions = {}) {
const r = await /** @type {typeof this.call<string>} */ (this.call)(
{
method: 'Filecoin.StateLookupID',
params: [
params.address,
params === undefined ? null : params.tipSetKey,
],
},
fetchOptions
)

if (r.error) {
return r
}

return {
result:
this.network === 'testnet' ? r.result.replace('f', 't') : r.result,
error: undefined,
}
}

/**
* Generic method to call any method on the lotus rpc api.
*
* @template R
* @param {import('./types.js').RpcOptions} rpcOptions
* @param {import('./types.js').FetchOptions} [fetchOptions]
* @returns {Promise<R | import('./types.js').RpcError>}
* @returns {Promise<import('./types.js').LotusResponse<R>>}
*/

async call(rpcOptions, fetchOptions = {}) {
Expand Down Expand Up @@ -208,7 +317,7 @@ export class RPC {
},
})
}
return /** @type {R} */ ({ result: json.result })
return { result: /** @type {R} */ (json.result), error: undefined }
}
const text = await res.text()
let json
Expand Down
22 changes: 21 additions & 1 deletion packages/iso-filecoin/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type BigNumber from 'bignumber.js'
import type { z } from 'zod'
import type { PROTOCOL_INDICATOR } from './address'
import type { AddressId, PROTOCOL_INDICATOR } from './address'
import type { Schemas as MessageSchemas } from './message'
import type { RPC } from './rpc'
import type { SIGNATURE_TYPE, Schemas as SignatureSchemas } from './signature'

export type ProtocolIndicator = typeof PROTOCOL_INDICATOR
Expand All @@ -22,6 +23,7 @@ export interface Address {
toBytes: () => Uint8Array
toContractDestination: () => `0x${string}`
checksum: () => Uint8Array
toID: (rpc: RPC) => Promise<AddressId>
}

export interface DerivationPathComponents {
Expand Down Expand Up @@ -168,6 +170,24 @@ export interface waitMsgParams {
lookback?: number
}

export interface StateAccountKeyParams {
address: string
tipSetKey?: CID[] | null
}
export type BlockNumber = '0x${string}'
export interface FilecoinAddressToEthAddressParams {
/**
* The Filecoin address to convert.
*/
address: string
/**
* The block number or state for the conversion.
* Defaults to "finalized" for maximum safety.
* Possible values: "pending", "latest", "finalized", "safe", or a specific block number represented as hex.
*/
blockNumber?: 'pending' | 'latest' | 'finalized' | 'safe' | BlockNumber
}

// Token types
export type FormatOptions = BigNumber.Format & {
/**
Expand Down
28 changes: 27 additions & 1 deletion packages/iso-filecoin/src/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export function mnemonicToSeed(mnemonic, password) {
}

/**
* Get account from mnemonic
*
* @param {string} mnemonic
* @param {import('./types.js').SignatureType} type
* @param {string} path
Expand All @@ -40,6 +42,7 @@ export function accountFromMnemonic(mnemonic, type, path, password, network) {
}

/**
* Get account from seed
*
* @param {Uint8Array} seed
* @param {import('./types.js').SignatureType} type
Expand Down Expand Up @@ -70,7 +73,7 @@ export function accountFromSeed(seed, type, path, network) {
}

/**
* Account from private key
* Get account from private key
*
* @param {Uint8Array} privateKey
* @param {import('./types.js').SignatureType} type
Expand All @@ -93,6 +96,29 @@ export function accountFromPrivateKey(privateKey, type, network, path) {
}
}

/**
* Create account
*
* @param {import('./types.js').SignatureType} type
* @param {import('./types.js').Network} network
*/
export function create(type, network) {
switch (type) {
case 'SECP256K1': {
return accountFromPrivateKey(secp.utils.randomPrivateKey(), type, network)
}

case 'BLS': {
return accountFromPrivateKey(bls.utils.randomPrivateKey(), type, network)
}
default: {
throw new Error(
`Create does not support "${type}" type. Use SECP256K1 or BLS.`
)
}
}
}

/**
* Get public key from private key
*
Expand Down
Loading

0 comments on commit f3d5e5f

Please sign in to comment.