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
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
]
},
"dependencies": {
"borsh": "^2.0.0",
"ky": "^1.12.0",
"viem": "~2.45.1"
},
Expand Down
35 changes: 35 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,38 @@ export const MULTICHAIN_INPUT_SETTLER_ESCROW =
"0xb912b4c38ab54b94D45Ac001484dEBcbb519Bc2B" as const;
export const MULTICHAIN_INPUT_SETTLER_COMPACT =
"0x1fccC0807F25A58eB531a0B5b4bf3dCE88808Ed7" as const;


// Solana config

export const SOLANA_MAINNET_CHAIN_ID = 1151111081099710n;
export const SOLANA_TESTNET_CHAIN_ID = 1151111081099711n;
export const SOLANA_DEVNET_CHAIN_ID = 1151111081099712n;
Comment on lines +22 to +24
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to consider renaming these to just SOLANA_MAINNET, SOLANA_TESTNET, etc?


// TODO: fill in mainnet and testnet input settler programs once deployed.
// Do NOT use these constants directly — always go through
// `inputSettlerForSolana(chainId)` which throws for undeployed networks.
const SOLANA_MAINNET_INPUT_SETTLER_ESCROW = undefined;
const SOLANA_TESTNET_INPUT_SETTLER_ESCROW = undefined;
export const SOLANA_DEVNET_INPUT_SETTLER_ESCROW =
"0x4186c46d62fb033aace3a262def7efbbef0591b8e98732bcd62edbbc0916da57" as const;

export const SOLANA_INPUT_SETTLER_PROGRAMS: Record<string, `0x${string}` | undefined> = {
[SOLANA_MAINNET_CHAIN_ID.toString()]: SOLANA_MAINNET_INPUT_SETTLER_ESCROW,
[SOLANA_TESTNET_CHAIN_ID.toString()]: SOLANA_TESTNET_INPUT_SETTLER_ESCROW,
[SOLANA_DEVNET_CHAIN_ID.toString()]: SOLANA_DEVNET_INPUT_SETTLER_ESCROW,
};

// TODO: fill in mainnet and testnet output settler PDAs once deployed.
// Do NOT use these constants directly — always go through
// `SOLANA_OUTPUT_SETTLER_PDAS[chainId]` which is guarded in buildMandateOutputs.
const SOLANA_MAINNET_OUTPUT_SETTLER_PDA = undefined;
const SOLANA_TESTNET_OUTPUT_SETTLER_PDA = undefined;
export const SOLANA_DEVNET_OUTPUT_SETTLER_PDA =
"0xabb04f05c412a4892f8c93efa4eda9f360ba8b5c8342bed51207c7a4fdd036d6" as const;

export const SOLANA_OUTPUT_SETTLER_PDAS: Record<string, `0x${string}` | undefined> = {
[SOLANA_MAINNET_CHAIN_ID.toString()]: SOLANA_MAINNET_OUTPUT_SETTLER_PDA,
[SOLANA_TESTNET_CHAIN_ID.toString()]: SOLANA_TESTNET_OUTPUT_SETTLER_PDA,
[SOLANA_DEVNET_CHAIN_ID.toString()]: SOLANA_DEVNET_OUTPUT_SETTLER_PDA,
};
6 changes: 6 additions & 0 deletions src/helpers/convert.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ describe("convert helpers", () => {
expect(bytes32ToAddress(bytes)).toBe(address);
});

it("passes through an already-padded bytes32 address unchanged", () => {
const bytes32 =
"0x0000000000000000000000001111111111111111111111111111111111111111" as const;
expect(addressToBytes32(bytes32)).toBe(bytes32);
});

it("throws for invalid address and bytes lengths", () => {
expect(() => addressToBytes32("0x1234" as `0x${string}`)).toThrow(
"Invalid address length",
Expand Down
4 changes: 3 additions & 1 deletion src/helpers/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export function toBigIntWithDecimals(value: number, decimals: number): bigint {
}

export function addressToBytes32(address: `0x${string}`): `0x${string}` {
if (address.length !== 42 && address.length !== 40) {
if (address.length === 66) return address;
// Accept a 42-char EVM address or an already-padded 66-char bytes32 value.
if (address.length !== 42) {
throw new Error(`Invalid address length: ${address.length}`);
}
return `0x${address.replace("0x", "").padStart(64, "0")}`;
Expand Down
4 changes: 2 additions & 2 deletions src/intent/compact/conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
Lock,
MultichainCompact,
MultichainOrder,
StandardOrder,
StandardEVM,
} from "../../types";

export function tokenIdToLock(tokenId: bigint, amount: bigint): Lock {
Expand All @@ -23,7 +23,7 @@ export function inputsToLocks(inputs: [bigint, bigint][]): Lock[] {
}

export function toStandardBatchCompact(
order: StandardOrder,
order: StandardEVM,
arbiter: `0x${string}`,
): BatchCompact {
const mandate: CompactMandate = {
Expand Down
71 changes: 68 additions & 3 deletions src/intent/create.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
COIN_FILLER,
INPUT_SETTLER_ESCROW_LIFI,
MULTICHAIN_INPUT_SETTLER_ESCROW,
SOLANA_DEVNET_CHAIN_ID,
SOLANA_DEVNET_INPUT_SETTLER_ESCROW,
} from "../constants";
import type { IntentDeps } from "../deps";
import {
Expand All @@ -20,7 +22,8 @@ import type {
} from "../types";
import { Intent } from "./create";
import { MultichainOrderIntent } from "./multichain";
import { StandardOrderIntent } from "./standard";
import { StandardEVMIntent } from "./standard";
import { StandardSolanaIntent } from "./solanaStandard";

const originalDateNow = Date.now;
const originalMathRandom = Math.random;
Expand All @@ -41,27 +44,31 @@ const ETH_USDC: CoreToken = {
name: "usdc",
chainId: CHAIN_ID_ETHEREUM,
decimals: 6,
chainNamespace: "eip155",
};

const ETH_WETH: CoreToken = {
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
name: "weth",
chainId: CHAIN_ID_ETHEREUM,
decimals: 18,
chainNamespace: "eip155",
};

const ARB_USDC: CoreToken = {
address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
name: "usdc",
chainId: CHAIN_ID_ARBITRUM,
decimals: 6,
chainNamespace: "eip155",
};

const BASE_USDC: CoreToken = {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
name: "usdc",
chainId: CHAIN_ID_BASE,
decimals: 6,
chainNamespace: "eip155",
};

function ctx(token: CoreToken, amount: bigint): TokenContext {
Expand Down Expand Up @@ -124,7 +131,7 @@ describe("Intent", () => {
const single = intent.singlechain();
const order = single.asOrder();

expect(single).toBeInstanceOf(StandardOrderIntent);
expect(single).toBeInstanceOf(StandardEVMIntent);
expect(single.inputSettler).toBe(INPUT_SETTLER_ESCROW_LIFI);
expect(order.inputOracle).toBe(COIN_FILLER);
expect(order.fillDeadline).toBe(TEST_NOW_SECONDS + 2 * 60 * 60);
Expand Down Expand Up @@ -206,7 +213,65 @@ describe("Intent", () => {
intentDeps,
).order();

expect(single).toBeInstanceOf(StandardOrderIntent);
expect(single).toBeInstanceOf(StandardEVMIntent);
expect(multi).toBeInstanceOf(MultichainOrderIntent);
});

describe("Solana singlechain", () => {
const SOLANA_DEVNET_ORACLE =
"0x0000003E06000007A224AeE90052fA6bb46d43C9" as const;

const solanaIntentDeps: IntentDeps = {
getOracle(verifier, chainId) {
if (verifier !== "polymer") return undefined;
if (chainId === SOLANA_DEVNET_CHAIN_ID) return SOLANA_DEVNET_ORACLE;
return [CHAIN_ID_ETHEREUM, CHAIN_ID_ARBITRUM, CHAIN_ID_BASE].includes(chainId)
? TEST_POLYMER_ORACLE
: undefined;
},
};

const SOLANA_USDC: CoreToken = {
address: "0xab11111111111111111111111111111111111111111111111111111111111111",
name: "USDC",
chainId: SOLANA_DEVNET_CHAIN_ID,
decimals: 6,
chainNamespace: "solana",
};

it("returns SolanaStandardEVMIntent for a solana input token", () => {
const intent = new Intent(
makeEscrowOptions([ctx(SOLANA_USDC, 1_000_000n)], [ctx(ARB_USDC, 1_000_000n)]),
solanaIntentDeps,
);
const result = intent.singlechain();

expect(result).toBeInstanceOf(StandardSolanaIntent);
expect(result.inputSettler).toBe(SOLANA_DEVNET_INPUT_SETTLER_ESCROW);
});

it("sets inputOracle from the oracle resolver", () => {
const intent = new Intent(
makeEscrowOptions([ctx(SOLANA_USDC, 1_000_000n)], [ctx(ARB_USDC, 1_000_000n)]),
solanaIntentDeps,
);
const order = intent.singlechain().asOrder();

expect(order.inputOracle).toBe(SOLANA_DEVNET_ORACLE);
});

it("throws when more than one solana input is provided", () => {
const intent = new Intent(
makeEscrowOptions(
[ctx(SOLANA_USDC, 1n), ctx(SOLANA_USDC, 2n)],
[ctx(ARB_USDC, 1n)],
),
solanaIntentDeps,
);

expect(() => intent.singlechain()).toThrow(
"SolanaStandardOrder only supports a single input",
);
});
});
});
Loading