Skip to content

Commit 605f7e1

Browse files
committed
removed ethers from demoscript
1 parent e6bd1db commit 605f7e1

File tree

3 files changed

+127
-77
lines changed

3 files changed

+127
-77
lines changed

conventions.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,12 @@ We use Foundry as our primary development and testing framework. Foundry provide
8686
- Includes ZKSync-specific deployment scripts
8787
- Update scripts for adding facets to the Diamond
8888
- **demoScripts/**: TypeScript files demonstrating contract usage
89-
- Uses viem and helper functions
89+
- **MUST use viem** for all contract interactions (ethers.js is NOT allowed)
90+
- Uses helper functions from `utils/demoScriptHelpers.ts`
9091
- Shows how to prepare arguments and calculate parameters
9192
- Helps backend team understand contract usage
9293
- Provides end-to-end testing capabilities
94+
- See `demoLidoWrapper.ts`, `demoUnit.ts`, or `demoEco.ts` for examples of proper viem usage
9395
- **mongoDb/**: MongoDB integration for:
9496
- Storing multisig proposals (alternative to Safe Transaction Service)
9597
- Sharing RPC URLs across developers
@@ -457,15 +459,39 @@ All Solidity files must follow the rules defined in `.solhint.json`. This config
457459
- Environment variables should be validated using `getEnvVar()` helper
458460
- Scripts should exit with appropriate exit codes (0 for success, 1 for error)
459461
462+
#### Demo Scripts Requirements
463+
464+
- **MUST use viem for all contract interactions** - ethers.js is NOT allowed
465+
- Use `createPublicClient` and `createWalletClient` from viem
466+
- Use `getContract` from viem to create contract instances
467+
- Use `encodeFunctionData` for encoding function calls
468+
- Use `parseUnits` and `formatUnits` from viem instead of BigNumber
469+
- Use `zeroAddress` from viem instead of `constants.AddressZero`
470+
- Use `randomBytes` from `crypto` instead of `utils.randomBytes`
471+
- See `demoLidoWrapper.ts`, `demoUnit.ts`, or `demoEco.ts` for reference implementations
472+
473+
- **Helper functions from `demoScriptHelpers.ts`:**
474+
- Use `setupEnvironment()` for setting up viem clients and contracts
475+
- Use `ensureBalance()` and `ensureAllowance()` for token checks (viem-based)
476+
- Use `executeTransaction()` for executing transactions with receipt validation
477+
- Use `createContractObject()` for creating ERC20 contract objects
478+
- Use `getEnvVar()` for environment variable access
479+
480+
- **DO NOT use deprecated ethers-based helpers:**
481+
- ❌ `getProvider()` - use viem clients instead
482+
- ❌ `getWalletFromPrivateKeyInDotEnv()` - use `privateKeyToAccount` from viem
483+
- ❌ `sendTransaction()` (ethers version) - use `executeTransaction()` (viem-based)
484+
- ❌ `ensureBalanceAndAllowanceToDiamond()` - use `ensureBalance()` and `ensureAllowance()` separately
485+
460486
#### Helper Function Usage
461487
462488
- **Always use existing helper functions** when available instead of reimplementing functionality
463489
- Common helper functions to check for:
464490
465491
- `getDeployments()` from `script/utils/deploymentHelpers.ts` for loading deployment files
466-
- `getProvider()` and `getWalletFromPrivateKeyInDotEnv()` from `script/demoScripts/utils/demoScriptHelpers.ts`
467-
- `sendTransaction()` for transaction execution
468-
- `ensureBalanceAndAllowanceToDiamond()` for token approvals
492+
- `setupEnvironment()` from `script/demoScripts/utils/demoScriptHelpers.ts` for viem client setup
493+
- `executeTransaction()` for transaction execution (viem-based)
494+
- `ensureBalance()` and `ensureAllowance()` for token approvals (viem-based)
469495
- `getUniswapData*()` functions for swap data generation
470496
471497
- Before implementing new functionality, search the codebase for existing helper functions

script/demoScripts/demoPolymerCCTP.ts

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
#!/usr/bin/env bunx tsx
22

3+
import { randomBytes } from 'crypto'
4+
35
import { consola } from 'consola'
46
import { config } from 'dotenv'
5-
import { BigNumber, constants, utils } from 'ethers'
7+
import {
8+
encodeFunctionData,
9+
formatUnits,
10+
getAddress,
11+
getContract,
12+
parseUnits,
13+
toHex,
14+
zeroAddress,
15+
} from 'viem'
616

717
import {
18+
ERC20__factory,
19+
PolymerCCTPFacet__factory,
820
type ILiFi,
921
type PolymerCCTPFacet,
10-
PolymerCCTPFacet__factory,
1122
} from '../../typechain'
1223

1324
import {
@@ -16,11 +27,13 @@ import {
1627
ADDRESS_USDC_OPT,
1728
ADDRESS_USDC_SOL,
1829
DEV_WALLET_ADDRESS,
19-
ensureBalanceAndAllowanceToDiamond,
20-
getProvider,
21-
getWalletFromPrivateKeyInDotEnv,
30+
NON_EVM_ADDRESS,
31+
createContractObject,
32+
ensureAllowance,
33+
ensureBalance,
34+
executeTransaction,
2235
logBridgeDataStruct,
23-
sendTransaction,
36+
setupEnvironment,
2437
} from './utils/demoScriptHelpers'
2538

2639
config()
@@ -44,37 +57,40 @@ const SRC_CHAIN = 'optimism' as 'arbitrum' | 'optimism'
4457
// these are test deployments by Polymer team
4558
const LIFI_DIAMOND_ADDRESS_ARB = '0xD99A49304227d3fE2c27A1F12Ef66A95b95837b6'
4659
const LIFI_DIAMOND_ADDRESS_OPT = '0x36d7A6e0B2FE968a9558C5AaF5713aC2DAc0DbFc'
47-
const DIAMOND_ADDRESS_SRC =
60+
const DIAMOND_ADDRESS_SRC = getAddress(
4861
SRC_CHAIN === 'arbitrum' ? LIFI_DIAMOND_ADDRESS_ARB : LIFI_DIAMOND_ADDRESS_OPT
62+
)
4963

5064
// Destination chain ID
5165
const LIFI_CHAIN_ID_SOLANA = 1151111081099710
5266
// Mainnet chain IDs
5367
const LIFI_CHAIN_ID_ARBITRUM = 42161
5468
const LIFI_CHAIN_ID_OPTIMISM = 10
55-
const NON_EVM_ADDRESS = '0x11f111f111f111F111f111f111F111f111f111F1'
5669

5770
// For EVM destinations, use Arbitrum if source is Optimism, and vice versa
5871
const DST_CHAIN_ID_EVM =
5972
SRC_CHAIN === 'arbitrum' ? LIFI_CHAIN_ID_OPTIMISM : LIFI_CHAIN_ID_ARBITRUM // Optimism or Arbitrum
6073
const DST_CHAIN_ID = BRIDGE_TO_SOLANA ? LIFI_CHAIN_ID_SOLANA : DST_CHAIN_ID_EVM
6174

6275
// Token addresses
63-
const sendingAssetId =
76+
const sendingAssetId = getAddress(
6477
SRC_CHAIN === 'arbitrum' ? ADDRESS_USDC_ARB : ADDRESS_USDC_OPT
65-
const fromAmount = '1000000' // 1 USDC (6 decimals)
78+
)
79+
const fromAmount = parseUnits('1', 6) // 1 USDC (6 decimals)
6680

6781
// Receiver address
68-
const receiverAddress = BRIDGE_TO_SOLANA
69-
? NON_EVM_ADDRESS // Use NON_EVM_ADDRESS for Solana
70-
: DEV_WALLET_ADDRESS // Use EVM address for EVM destinations
82+
const receiverAddress = getAddress(
83+
BRIDGE_TO_SOLANA
84+
? NON_EVM_ADDRESS // Use NON_EVM_ADDRESS for Solana
85+
: DEV_WALLET_ADDRESS
86+
) // Use EVM address for EVM destinations
7187

7288
// Solana receiver (bytes32 format) - only used when BRIDGE_TO_SOLANA is true
7389
const solanaReceiverBytes32 = ADDRESS_DEV_WALLET_SOLANA_BYTES32
7490

7591
// Polymer CCTP specific parameters
7692
// polymerTokenFee will be extracted from API response
77-
const maxCCTPFee = '0' // Max CCTP fee (0 = no limit)
93+
const maxCCTPFee = 0n // Max CCTP fee (0 = no limit)
7894
const minFinalityThreshold = USE_FAST_MODE ? 1000 : 2000 // 1000 = fast path, 2000 = standard path
7995

8096
const EXPLORER_BASE_URL =
@@ -129,9 +145,9 @@ async function getPolymerQuote(
129145
toChainId: number,
130146
fromToken: string,
131147
toToken: string,
132-
fromAmount: string,
148+
fromAmount: bigint,
133149
toAddress: string
134-
): Promise<{ quote: IQuoteResponse; polymerTokenFee: string }> {
150+
): Promise<{ quote: IQuoteResponse; polymerTokenFee: bigint }> {
135151
const quoteType = USE_FAST_MODE ? 'fast' : 'standard'
136152
consola.info(`\n📡 Fetching ${quoteType} quote from Polymer API...`)
137153

@@ -141,14 +157,14 @@ async function getPolymerQuote(
141157
toChain: toChainId.toString(),
142158
fromToken,
143159
toToken,
144-
fromAmount,
160+
fromAmount: fromAmount.toString(),
145161
toAddress,
146162
})
147163

148164
const fullApiUrl = `${POLYMER_API_URL}/v1/quote/${quoteType}?${queryParams.toString()}`
149165
consola.info(`API URL: ${fullApiUrl}`)
150166
consola.info(
151-
`Request: fromChain=${fromChainId}, toChain=${toChainId}, fromAmount=${fromAmount}`
167+
`Request: fromChain=${fromChainId}, toChain=${toChainId}, fromAmount=${fromAmount.toString()}`
152168
)
153169

154170
const quoteResponse = await fetch(fullApiUrl, {
@@ -175,7 +191,7 @@ async function getPolymerQuote(
175191
// Standard path: polymerTokenFee = 0 (no CCTP service fee)
176192
// Fast path: polymerTokenFee > 0 (CCTP service fee charged by Circle, included in amount)
177193
// The "CCTP service fee" with included: true is the fee deducted from the bridged amount
178-
let polymerTokenFee = '0'
194+
let polymerTokenFee = 0n
179195
if (quoteData.feeCosts) {
180196
// Look for CCTP service fee (fast path) - this is the fee that's included/deducted
181197
const cctpServiceFee = quoteData.feeCosts.find(
@@ -185,7 +201,7 @@ async function getPolymerQuote(
185201
fee.included === true
186202
)
187203
if (cctpServiceFee && cctpServiceFee.amount) {
188-
polymerTokenFee = cctpServiceFee.amount
204+
polymerTokenFee = BigInt(cctpServiceFee.amount)
189205
}
190206
}
191207

@@ -199,17 +215,19 @@ async function getPolymerQuote(
199215
}
200216

201217
async function main() {
202-
const provider = getProvider(SRC_CHAIN)
203-
const wallet = getWalletFromPrivateKeyInDotEnv(provider)
204-
const walletAddress = await wallet.getAddress()
218+
// Setup environment using helper function
219+
const { client, publicClient, walletAccount, walletClient } =
220+
await setupEnvironment(SRC_CHAIN, null)
221+
const walletAddress = walletAccount.address
205222
consola.info('Using wallet address:', walletAddress)
206223

207-
// Get diamond contract
208-
const polymerCCTPFacet = PolymerCCTPFacet__factory.connect(
209-
DIAMOND_ADDRESS_SRC,
210-
wallet
211-
)
212-
consola.info('Diamond/PolymerCCTPFacet connected:', polymerCCTPFacet.address)
224+
// Get diamond contract with custom address
225+
const polymerCCTPFacet = getContract({
226+
address: DIAMOND_ADDRESS_SRC,
227+
abi: PolymerCCTPFacet__factory.abi,
228+
client,
229+
})
230+
consola.info('Diamond/PolymerCCTPFacet connected:', DIAMOND_ADDRESS_SRC)
213231

214232
// Display route details
215233
consola.info('\n🌉 BRIDGE ROUTE DETAILS:')
@@ -227,9 +245,7 @@ async function main() {
227245
consola.info(
228246
`📥 Destination Chain: ${destChainName} (Chain ID: ${DST_CHAIN_ID})`
229247
)
230-
consola.info(
231-
`💰 Amount: ${BigNumber.from(fromAmount).div(1e6).toString()} USDC`
232-
)
248+
consola.info(`💰 Amount: ${formatUnits(fromAmount, 6)} USDC`)
233249
consola.info(`🎯 Sending Asset: ${sendingAssetId}`)
234250
consola.info(
235251
`👤 Receiver: ${
@@ -275,15 +291,15 @@ async function main() {
275291
// Note: The facet transfers minAmount from user, then deducts polymerTokenFee before bridging
276292
// So if minAmount = fromAmount, the bridged amount will be fromAmount - polymerTokenFee
277293
// User must approve minAmount (which equals fromAmount in this case)
278-
const transactionId = utils.randomBytes(32)
294+
const transactionId = toHex(new Uint8Array(randomBytes(32)))
279295
const bridgeData: ILiFi.BridgeDataStruct = {
280296
transactionId,
281297
bridge: 'polymercctp',
282298
integrator: 'demoScript',
283-
referrer: constants.AddressZero,
299+
referrer: zeroAddress,
284300
sendingAssetId,
285301
receiver: receiverAddress,
286-
minAmount: fromAmount, // Total amount to transfer; bridged amount = minAmount - polymerTokenFee
302+
minAmount: fromAmount.toString(), // Total amount to transfer; bridged amount = minAmount - polymerTokenFee
287303
destinationChainId: DST_CHAIN_ID,
288304
hasSourceSwaps: false,
289305
hasDestinationCall: false,
@@ -293,11 +309,11 @@ async function main() {
293309

294310
// Prepare PolymerCCTP data
295311
const polymerData: PolymerCCTPFacet.PolymerCCTPDataStruct = {
296-
polymerTokenFee,
297-
maxCCTPFee,
312+
polymerTokenFee: polymerTokenFee.toString(),
313+
maxCCTPFee: maxCCTPFee.toString(),
298314
nonEVMReceiver: BRIDGE_TO_SOLANA
299-
? solanaReceiverBytes32
300-
: constants.HashZero,
315+
? toHex(solanaReceiverBytes32)
316+
: toHex(0, { size: 32 }),
301317
minFinalityThreshold,
302318
}
303319
consola.info('📋 polymerData prepared:')
@@ -308,54 +324,54 @@ async function main() {
308324

309325
// Ensure balance and allowance
310326
// Contract transfers minAmount, so user must approve minAmount (fromAmount)
311-
const totalAmountNeeded = BigNumber.from(fromAmount)
312327
if (SEND_TX) {
313-
await ensureBalanceAndAllowanceToDiamond(
328+
// Create ERC20 contract for balance/allowance checks
329+
const tokenContract = createContractObject(
314330
sendingAssetId,
315-
wallet,
331+
ERC20__factory.abi,
332+
publicClient,
333+
walletClient
334+
)
335+
336+
await ensureBalance(tokenContract, walletAddress, fromAmount, publicClient)
337+
await ensureAllowance(
338+
tokenContract,
339+
walletAddress,
316340
DIAMOND_ADDRESS_SRC,
317-
totalAmountNeeded,
318-
false
341+
fromAmount,
342+
publicClient
319343
)
320344
consola.info('✅ Balance and allowance verified')
321345
consola.info(
322-
` Approved amount: ${totalAmountNeeded.toString()} (will bridge: ${BigNumber.from(
323-
fromAmount
324-
)
325-
.sub(BigNumber.from(polymerTokenFee))
326-
.toString()}, fee: ${polymerTokenFee})`
346+
` Approved amount: ${fromAmount.toString()} (will bridge: ${(
347+
fromAmount - polymerTokenFee
348+
).toString()}, fee: ${polymerTokenFee.toString()})`
327349
)
328350
}
329351

330352
// Execute transaction
331353
if (SEND_TX) {
332354
consola.info('🚀 Executing bridge transaction...')
333355

334-
const executeTxData = await polymerCCTPFacet.populateTransaction
335-
.startBridgeTokensViaPolymerCCTP(bridgeData, polymerData)
336-
.then((tx) => tx.data)
356+
const hash = await executeTransaction(
357+
() =>
358+
(polymerCCTPFacet.write as any).startBridgeTokensViaPolymerCCTP([
359+
bridgeData,
360+
polymerData,
361+
]),
362+
'Starting bridge via PolymerCCTP',
363+
publicClient,
364+
true
365+
)
337366

338-
if (!executeTxData) {
339-
throw new Error('Failed to populate transaction data')
367+
if (!hash) {
368+
throw new Error('Failed to execute transaction')
340369
}
341370

342-
const transactionResponse = await sendTransaction(
343-
wallet,
344-
polymerCCTPFacet.address,
345-
executeTxData,
346-
BigNumber.from(0)
347-
)
348-
349371
consola.info('\n🎉 BRIDGE TRANSACTION EXECUTED SUCCESSFULLY!')
350-
consola.info(`📤 Transaction Hash: ${transactionResponse.hash}`)
351-
consola.info(
352-
`🔗 Explorer Link: ${EXPLORER_BASE_URL}${transactionResponse.hash}`
353-
)
354-
consola.info(
355-
`💰 Amount Bridged: ${BigNumber.from(fromAmount)
356-
.div(1e6)
357-
.toString()} USDC`
358-
)
372+
consola.info(`📤 Transaction Hash: ${hash}`)
373+
consola.info(`🔗 Explorer Link: ${EXPLORER_BASE_URL}${hash}`)
374+
consola.info(`💰 Amount Bridged: ${formatUnits(fromAmount, 6)} USDC`)
359375
consola.info(
360376
`📥 Destination: ${
361377
BRIDGE_TO_SOLANA
@@ -368,9 +384,11 @@ async function main() {
368384
consola.info('')
369385
} else {
370386
consola.info('🔍 Dry-run mode: Transaction not sent')
371-
const executeTxData = await polymerCCTPFacet.populateTransaction
372-
.startBridgeTokensViaPolymerCCTP(bridgeData, polymerData)
373-
.then((tx) => tx.data)
387+
const executeTxData = encodeFunctionData({
388+
abi: PolymerCCTPFacet__factory.abi,
389+
functionName: 'startBridgeTokensViaPolymerCCTP',
390+
args: [bridgeData, polymerData] as any,
391+
})
374392
consola.info('Calldata:', executeTxData)
375393
}
376394
}

script/demoScripts/utils/demoScriptHelpers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
createWalletClient,
1616
formatEther,
1717
formatUnits,
18+
getAddress,
1819
getContract,
1920
http,
2021
parseAbi,
@@ -38,6 +39,11 @@ config()
3839

3940
export const DEV_WALLET_ADDRESS = globalConfig.devWallet
4041

42+
// NON_EVM_ADDRESS constant from LiFiData.sol - used as receiver address when bridging to non-EVM chains
43+
export const NON_EVM_ADDRESS = getAddress(
44+
'0x11f111f111f111F111f111f111F111f111f111F1'
45+
)
46+
4147
export const DEFAULT_DEST_PAYLOAD_ABI = [
4248
'bytes32', // Transaction Id
4349
'tuple(address callTo, address approveTo, address sendingAssetId, address receivingAssetId, uint256 fromAmount, bytes callData, bool requiresDeposit)[]', // Swap Data

0 commit comments

Comments
 (0)