> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Build, sign, and broadcast Metal transactions with @metalntwx/metal-ts

@metalntwx/metal-ts is the TypeScript library for submitting transactions to Metal.
It works with viem and adds support for choosing which MIP20 token pays the
transaction fee. The chosen token is recorded in Metal's `0x7f` transaction envelope.
The SDK prepares this envelope, requests the account's signature, and broadcasts the
signed transaction.

The package also provides a standalone transaction codec and ERC-3009
authorization helpers.

## Installation

```bash theme={null}
npm install @metalntwx/metal-ts viem
```

`viem` is an optional peer dependency. Install it for the wallet helpers, or
import `@metalntwx/metal-ts/codec` when using another signer.

Call `initMetalCodec()` before using the codec directly. The high-level transaction
functions load the codec automatically. ERC-3009 helpers require no initialization.

## Choose an API

| API | Purpose |
| - | - |
| `writeMip20Contract` | Call a contract and broadcast the transaction |
| `signMip20Contract` | Sign and encode a contract call without broadcasting it |
| `sendMip20Call` | Broadcast existing ABI-encoded calldata |
| `@metalntwx/metal-ts/codec` | Use another signer or encode transactions directly |
| `@metalntwx/metal-ts/erc3009` or `/erc3009-viem` | Create or sign an ERC-3009 transfer authorization |

## Build, sign, and send transactions

### `writeMip20Contract`

Build, sign, and broadcast a contract call, paying gas in a MIP20 token. The
returned hash confirms broadcast; the helper does not wait for a receipt or
finality. Verify the successful receipt and finalized block before crediting a
payment.

```typescript theme={null}
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { writeMip20Contract } from "@metalntwx/metal-ts";

const publicClient = createPublicClient({ transport: http(RPC_URL) });
const walletClient = createWalletClient({
  account: privateKeyToAccount(PRIVATE_KEY),
  transport: http(RPC_URL),
});

const txHash = await writeMip20Contract({
  walletClient, // signs via signTypedData
  publicClient, // reads chainId, nonce, estimates gas
  abi: ERC20_ABI,
  address: tokenAddress,
  functionName: "transfer",
  args: [recipient, parseUnits("10", 18)],
  gasToken: tokenAddress, // the MIP20 token that pays for gas
  gasFallback: 200_000n, // gas limit used if estimation fails
});
```

In the browser, build `walletClient` from the injected provider
(`custom(window.ethereum)`); the same call covers a Node private-key signer as
shown above.

### `signMip20Contract`

Build, sign, and encode a contract call without broadcasting it. It accepts the
same arguments as `writeMip20Contract` and returns:

```typescript theme={null}
interface SignedMip20Tx {
  txHash: Hash;
  raw: Hex;
  send(): Promise<Hash>;
}
```

`txHash` is computed from the signed `0x7f` envelope before submission. Call
`send()` to broadcast `raw`. Persist `txHash` and `raw` before broadcasting so
you can check the same transaction if the RPC response is lost.

Both signing and writing fetch the account's pending nonce. Signing does not
reserve it: serialize preparation for an account, or coordinate nonces through
the codec when preparing several transactions before broadcasting.

### `sendMip20Call`

Use this lower-level helper when you already have ABI-encoded calldata.

```typescript theme={null}
import { sendMip20Call, type Mip20Authorization } from "@metalntwx/metal-ts";
import type { Address, Hash, Hex, PublicClient, WalletClient } from "viem";

interface Mip20CallArgs {
  walletClient: WalletClient;
  publicClient: PublicClient;
  to: Address; // target contract
  data: Hex; // ABI-encoded calldata
  gasToken: Address; // MIP20 token for gas payment
  gasFallback: bigint; // gas limit if estimation fails
  maxFeePerGas?: bigint; // gas-token base units per gas; defaults to 1
  gasLimit?: bigint; // hard override; skips estimation
  authorizationList?: Mip20Authorization[]; // optional EIP-7702 SetCode entries
}

const txHash: Hash = await sendMip20Call(args);
```

**Gas estimation:** `estimateGas` is called with 20% headroom + 10,000. On
failure it falls back to `gasFallback`. When `authorizationList` is non-empty,
`gasFallback` is the minimum, because pre-authorization estimation cannot see
the newly installed account code.

**Nonce:** fetched from `getTransactionCount` with `blockTag: "pending"`, so
in-flight transactions are counted.

**Returns:** the transaction hash after broadcast. Await
`publicClient.waitForTransactionReceipt({ hash })` for execution, check the
receipt's status, then verify [finality](https://docs.metalntwx.com/build/payments/verify-and-reconcile-payments.md).

### Error handling

`sendMip20Call` and `writeMip20Contract` throw on a missing wallet account, an
invalid signature `yParity`, or an `eth_sendRawTransaction` rejection (for
example, `insufficient gas-token balance` when the account can't cover gas).

```typescript theme={null}
try {
  const txHash = await writeMip20Contract({ ... });
} catch (err) {
  if (String(err).includes("insufficient gas-token balance")) {
    // not enough of gasToken to cover gas
  }
  throw err;
}
```

## Codec API

The codec implements the same MIP20 transaction encoding used by the chain. It
has no wallet-library dependency, so indexers, edge runtimes, and applications
using another signer can import it directly.

### `initMetalCodec`

```typescript theme={null}
initMetalCodec(wasmSource?: InitInput): Promise<unknown>
```

Loads the WASM module. Repeated calls reuse the loaded instance. Pass
`wasmSource` to load WASM from a non-default location.

### Use the codec without viem

The following example signs with ethers v5 and submits the encoded envelope
through JSON-RPC.

```typescript theme={null}
import {
  initMetalCodec,
  buildMip20TypedDataMessage,
  encodeSignedEnvelope,
  type Mip20Tx,
} from "@metalntwx/metal-ts/codec";

await initMetalCodec();

const tx: Mip20Tx = {
  chainId: 63825n,
  nonce: 0n,
  maxPriorityFeePerGas: 1n,
  maxFeePerGas: 1n,
  gasLimit: 200_000n,
  to: "0x...",
  data: "0x...",
  gasToken: "0x...",
};

const typedData = buildMip20TypedDataMessage(tx, BigInt(tx.chainId));

// Sign with any EIP-712 signer (here: ethers.js)
const sigHex = await ethersSigner._signTypedData(
  typedData.domain,
  { MIP20Tx: typedData.types.MIP20Tx },
  typedData.message,
);

// Marshal to 65-byte r || s || v, then wrap into the 0x7f envelope
const raw = encodeSignedEnvelope(tx, ethers.utils.arrayify(sigHex));
await provider.send("eth_sendRawTransaction", [
  "0x" + Buffer.from(raw).toString("hex"),
]);
```

### `Mip20Tx`

```typescript theme={null}
interface Mip20Tx {
  chainId: number | bigint;
  nonce: number | bigint;
  maxPriorityFeePerGas: bigint;
  maxFeePerGas: bigint;
  gasLimit: number | bigint;
  to: `0x${string}` | null; // null = contract creation
  data: `0x${string}`; // hex calldata
  gasToken: `0x${string}`;
  authorizationList?: Mip20Authorization[];
}

interface Mip20Authorization {
  chainId: bigint | number;
  address: `0x${string}`; // smart-account implementation
  nonce: bigint | number;
  yParity: boolean;
  r: `0x${string}`;
  s: `0x${string}`;
}
```

`value` is always `0` and is injected by the codec; it is not on this shape
because mempool admission rejects `value != 0`.

`authorizationList` carries optional EIP-7702 SetCode authorizations. Omit it
for ordinary transfers. Include a signed authorization when an account opts into
or updates a smart-account implementation; the codec binds the canonical RLP
hash of the list into the transaction signature.

### `buildMip20TypedDataMessage`

Produces the EIP-712 typed data a wallet's `signTypedData` consumes.

```typescript theme={null}
buildMip20TypedDataMessage(tx: Mip20Tx, chainId: bigint): {
  domain:      { name: "Metal"; version: "1"; chainId: bigint; verifyingContract: `0x${string}` };
  types:       typeof MIP20_TX_TYPES;
  primaryType: "MIP20Tx";
  message:     { /* all 10 typehash fields, with value: 0n and authorizationListHash injected */ };
}
```

The `MIP20Tx` typehash has 10 fields. `value: 0n` and `authorizationListHash`
are injected here to match the chain's canonical typehash; `authorizationListHash`
is computed inside WASM; never hand-roll it. When `tx.to` is `null` the helper
substitutes `0x0000…0000`, and all address fields are lowercased so viem's
`signTypedData` accepts them regardless of source casing.

### `encodeSignedEnvelope`

```typescript theme={null}
encodeSignedEnvelope(
  tx:  Mip20Tx,
  sig: Uint8Array, // 65 bytes: r (32) || s (32) || v (1), v = 27 or 28
): Uint8Array      // raw 0x7f-prefixed bytes for eth_sendRawTransaction
```

`signatureBytes(sigHex)` (from the package root) marshals a viem hex signature
into the 65-byte layout this expects.

## ERC-3009 authorizations

ERC-3009 lets a token holder sign a transfer authorization for another account
to submit. The package provides wallet-independent typed-data builders and viem
signing helpers.

The pure helpers are exported from `@metalntwx/metal-ts/erc3009`:

| Export | Purpose |
| - | - |
| `Erc3009Authorization` | `from`, `to`, `value`, `validAfter`, `validBefore`, and 32-byte `nonce` |
| `Erc3009Domain` | Token `name()`, chain ID, and token contract address |
| `TRANSFER_WITH_AUTHORIZATION_TYPES` | EIP-712 types for `transferWithAuthorization` |
| `RECEIVE_WITH_AUTHORIZATION_TYPES` | EIP-712 types for `receiveWithAuthorization` |
| `buildTransferAuthorizationTypedData(domain, authorization)` | Transfer authorization typed data |
| `buildReceiveAuthorizationTypedData(domain, authorization)` | Payee-bound receive authorization typed data |
| `randomAuthorizationNonce()` | Fresh random 32-byte authorization nonce |

The viem helpers are exported from `@metalntwx/metal-ts/erc3009-viem`:

| Export | Purpose |
| - | - |
| `signTransferAuthorization(args)` | Sign a transfer authorization with a `WalletClient` |
| `signReceiveAuthorization(args)` | Sign a receive authorization with a `WalletClient` |
| `TRANSFER_WITH_AUTHORIZATION_ABI` | Minimal transfer ABI fragment |
| `RECEIVE_WITH_AUTHORIZATION_ABI` | Minimal receive ABI fragment |
| `encodeTransferWithAuthorizationCalldata(authorization, signature)` | Encode transfer calldata |
| `encodeReceiveWithAuthorizationCalldata(authorization, signature)` | Encode receive calldata |

## Export reference

### Package entrypoints

| Import | Contents |
| - | - |
| `@metalntwx/metal-ts` | Codec and viem wallet helpers |
| `@metalntwx/metal-ts/codec` | WASM codec and MIP20 transaction typed-data helpers; no viem dependency |
| `@metalntwx/metal-ts/erc3009` | ERC-3009 types, typed-data builders, and nonce generation; no viem dependency |
| `@metalntwx/metal-ts/erc3009-viem` | ERC-3009 wallet signing, ABI fragments, and calldata encoders |

### Other MIP20 exports

| Export | Entry point | Return or purpose | | | | |
| - | - | - | - | - | - | - |
| `estimateGasLimit(publicClient, from, to, data, fallback, hasAuthorization)` | Package root | Estimated gas with 20% headroom plus 10,000, or `fallback` | | | | |
| `signatureBytes(sigHex)` | Package root | A viem signature as 65-byte \`r | | s | | v\` bytes |
| `factoryAddress()` | Package root or `/codec` | Canonical MIP20 factory address used by the transaction EIP-712 domain | | | | |
| `authorizationSigningHash(...)` | Package root or `/codec` | EIP-7702 authorization signing hash | | | | |
| `mip20AuthorizationListHash(tx)` | Package root or `/codec` | Canonical authorization-list hash | | | | |
| `signingHash(tx)` | Package root or `/codec` | Canonical MIP20 transaction signing hash | | | | |
| `txTypeByte()` | Package root or `/codec` | MIP20 envelope type byte | | | | |

## Agent packages

`@metalntwx/metal-pay` adds x402 payments on Metal to a buyer or a seller: the
`exact` scheme, spending-limit mandates, signed orders and pay-per-call
clearing. `@metalntwx/metal-agent` is the CLI and MCP server that holds an
agent's key and pays with it. Both are documented in the
[Agents](https://docs.metalntwx.com/agents/quickstart.md) section.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.