Skip to content

Commit

Permalink
feat: Add native NEP-141 bridge to Ethereum. (#96)
Browse files Browse the repository at this point in the history
* fix: Poll getTransaction.

Fixes error when sending tx from different provider (WalletConnect).

* feat: Add native NEP-141 bridge to Ethereum.

* chore: Fix lint.

* docs: Add mainnet deployment.

* fix: Increase finalization on NEAR gas limit.

* feat: Pay storage balance before NEP-141 unlock.

* fix: Use new contract abi.

* fix: Handle any NEP-141 metadata log receipt order.
  • Loading branch information
paouvrard authored Jun 12, 2024
1 parent 810fe31 commit 6292803
Show file tree
Hide file tree
Showing 19 changed files with 2,188 additions and 74 deletions.
10 changes: 10 additions & 0 deletions .yarn/versions/cf0fe5ae.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
releases:
"@near-eth/aurora-erc20": patch
"@near-eth/aurora-ether": patch
"@near-eth/aurora-nep141": patch
"@near-eth/client": minor
"@near-eth/near-ether": patch
"@near-eth/nep141-erc20": minor
"@near-eth/rainbow": minor
"@near-eth/utils": minor
rainbow-bridge-client-monorepo: minor
10 changes: 9 additions & 1 deletion packages/aurora-ether/src/bridged-ether/sendToEthereum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,15 @@ export async function burn (
})
txHash = transaction!.hash
}
const pendingBurnTx = await provider.getTransaction(txHash)
let pendingBurnTx = null
while (!pendingBurnTx) {
try {
await new Promise(resolve => setTimeout(resolve, 1000))
pendingBurnTx = await provider.getTransaction(txHash)
} catch (error) {
console.log(error)
}
}

return {
...transfer,
Expand Down
10 changes: 9 additions & 1 deletion packages/aurora-nep141/src/bridged-ether/sendToNear/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,15 @@ export async function burn (
data: exitToNearData,
gas: ethers.BigNumber.from(121000).toHexString()
}])
const tx = await provider.getTransaction(txHash)
let tx = null
while (!tx) {
try {
await new Promise(resolve => setTimeout(resolve, 1000))
tx = await provider.getTransaction(txHash)
} catch (error) {
console.log(error)
}
}

return {
...transfer,
Expand Down
56 changes: 3 additions & 53 deletions packages/aurora-nep141/src/natural-nep141/sendToAurora/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import BN from 'bn.js'
import { ethers } from 'ethers'
import { transactions, Account, utils, providers as najProviders } from 'near-api-js'
import { CodeResult } from 'near-api-js/lib/providers/provider'
import { getBridgeParams, track, untrack } from '@near-eth/client'
import { TransactionInfo, TransferStatus } from '@near-eth/client/dist/types'
import * as status from '@near-eth/client/dist/statuses'
import { getNearWallet, getNearAccountId, getNearProvider } from '@near-eth/client/dist/utils'
import { urlParams, buildIndexerTxQuery } from '@near-eth/utils'
import { urlParams, buildIndexerTxQuery, nep141 } from '@near-eth/utils'
import getMetadata from '../getMetadata'

export const SOURCE_NETWORK = 'near'
Expand Down Expand Up @@ -564,10 +563,10 @@ export async function lockNear (
const auroraEvmAccount = options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount

const actions = []
const minStorageBalance = await getMinStorageBalance({
const minStorageBalance = await nep141.getMinStorageBalance({
nep141Address: wNearNep141, nearProvider
})
const userStorageBalance = await getStorageBalance({
const userStorageBalance = await nep141.getStorageBalance({
nep141Address: wNearNep141,
accountId: transfer.sender,
nearProvider
Expand Down Expand Up @@ -664,53 +663,4 @@ export async function lockNear (
}
}

export async function getMinStorageBalance (
{ nep141Address, nearProvider }: {
nep141Address: string
nearProvider: najProviders.Provider
}
): Promise<string> {
try {
const result = await nearProvider.query<CodeResult>({
request_type: 'call_function',
account_id: nep141Address,
method_name: 'storage_balance_bounds',
args_base64: '',
finality: 'optimistic'
})
return JSON.parse(Buffer.from(result.result).toString()).min
} catch (e) {
const result = await nearProvider.query<CodeResult>({
request_type: 'call_function',
account_id: nep141Address,
method_name: 'storage_minimum_balance',
args_base64: '',
finality: 'optimistic'
})
return JSON.parse(Buffer.from(result.result).toString())
}
}

export async function getStorageBalance (
{ nep141Address, accountId, nearProvider }: {
nep141Address: string
accountId: string
nearProvider: najProviders.Provider
}
): Promise<null | {total: string}> {
try {
const result = await nearProvider.query<CodeResult>({
request_type: 'call_function',
account_id: nep141Address,
method_name: 'storage_balance_of',
args_base64: Buffer.from(JSON.stringify({ account_id: accountId })).toString('base64'),
finality: 'optimistic'
})
return JSON.parse(Buffer.from(result.result).toString())
} catch (e) {
console.warn(e, nep141Address)
return null
}
}

const last = (arr: any[]): any => arr[arr.length - 1]
36 changes: 21 additions & 15 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,32 +441,29 @@ import {
| from Ethereum | to NEAR | to Aurora |
| :------------ | :---------------------------------------------- | :------------------------------------------------------ |
| ERC-20 | nep141Erc20/naturalErc20/sendToNear | auroraErc20/naturalErc20/sendToAurora |
| | nep141Erc20/bridgedErc20/sendToNear (TODO) | auroraErc20/bridgedErc20/sendToAurora (TODO) |
| | nep141Erc20/bridgedErc20/sendToNear | auroraErc20/bridgedErc20/sendToAurora (TODO) |
| ETH | nearEther/naturalETH/sendToNear | auroraEther/naturalEther/sendToAurora |
| NEAR | nearEther.bridgedNEAR.sendToNear | (TODO) |
| ERC-721 | (TODO) | (TODO) |

| from NEAR | to Ethereum | to Aurora |
| :------------ | :---------------------------------------------- | :------------------------------------------------------ |
| NEP-141 | nep141Erc20/bridgedNep141/sendToEthereum | auroraNep141/naturalNep141/sendToAurora |
| | nep141Erc20/naturalNep141/sendToEthereum (TODO) | auroraNep141/bridgedNep141/sendToAurora (TODO) |
| | nep141Erc20/naturalNep141/sendToEthereum | auroraNep141/bridgedNep141/sendToAurora (TODO) |
| ETH | nearEther/bridgedETH/sendToEthereum | auroraNep141/naturalNep141/sendToAurora |
| NEAR | nearEther/naturalNEAR/sendToEthereum | auroraNep141/naturalNep141/wrapAndSendNearToAurora \*\* |
| NEAR | nearEther/naturalNEAR/sendToEthereum | auroraNep141/naturalNep141/wrapAndSendNearToAurora |
| ERC-721 | (TODO) | (TODO) |

| from Aurora | to Ethereum | to NEAR |
| :------------ | :---------------------------------------------- | :------------------------------------------------------ |
| ERC-20 | auroraErc20/bridgedErc20/sendToEthereum | auroraNep141/bridgedErc20/sendToNear \* |
| | auroraErc20/naturalErc20/sendToEthereum (TODO) | auroraNep141/naturalErc20/sendToNear (TODO) \* |
| ETH | auroraEther/bridgedEther/sendToEthereum | auroraNep141/bridgedEther/sendToNear \* |
| NEAR | (TODO) | auroraNep141/bridgedErc20/sendToNear \* \*\* |
| NEAR | auroraErc20/wNEAR/sendToEthereum | auroraNep141/bridgedErc20/sendToNear \* |
| ERC-721 | (TODO) | (TODO) |

\* WARNING: The recipient of transfers from Aurora to NEAR must have paid NEAR storage fees otherwise tokens may be lost.

\*\* Received as wNEAR


Author a custom connector library
=================================

Expand Down Expand Up @@ -517,10 +514,18 @@ setBridgeParams({
nativeNEARLockerAddress: 'e-near.near',
wNearNep141: 'wrap.near',
eventRelayerAccount: 'event-relayer.near',
// https://github.com/Near-One/near-erc20-connector/blob/main/aurora/contracts/NearBridge.sol
wNearBridgeAddress: '0x5D5a9D3fB8BD3959B0C9266f90e126427E83872d',
wNearBridgeAbi: process.env.wNearBridgeAbi,
// https://github.com/Near-One/rainbow-token-connector/tree/master/token-locker
nep141LockerAccount: 'ft-locker.bridge.near',
// https://github.com/Near-One/rainbow-token-connector/tree/master/erc20-bridge-token
erc20FactoryAddress: '0x252e87862A3A720287E7fd527cE6e8d0738427A2',
erc20FactoryAbi: process.env.erc20FactoryAbi,
})
```

Goerli Testnet Bridge addresses and parameters
Sepolia Testnet Bridge addresses and parameters
==============================================
```js
import { setBridgeParams } from '@near-eth/client'
Expand Down Expand Up @@ -561,12 +566,13 @@ setBridgeParams({
nativeNEARLockerAddress: 'enear.goerli.testnet',
wNearNep141: 'wrap.testnet',
eventRelayerAccount: 'event-relayer.goerli.testnet',
// https://github.com/Near-One/near-erc20-connector/blob/main/aurora/contracts/NearBridge.sol
wNearBridgeAddress: '0x329242C003Df320166F5b198dCcb22b0CFF1d91B',
wNearBridgeAbi: process.env.wNearBridgeAbi,
// https://github.com/Near-One/rainbow-token-connector/tree/master/token-locker
nep141LockerAccount: 'ft-locker.sepolia.testnet',
// https://github.com/Near-One/rainbow-token-connector/tree/master/erc20-bridge-token
erc20FactoryAddress: '0xa9108f7F83Fb661e611991116D526fCa1a9585ab',
erc20FactoryAbi: process.env.erc20FactoryAbi,
})
```

Getting testnet tokens:

- https://erc20faucet.com
- https://goerlifaucet.com
- https://goerli-faucet.mudit.blog
- https://usdcfaucet.com
4 changes: 4 additions & 0 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export function getTransferType (transfer: Transfer): ConnectorLib {
return require('@near-eth/nep141-erc20/dist/natural-erc20/sendToNear')
case '@near-eth/nep141-erc20/bridged-nep141/sendToEthereum':
return require('@near-eth/nep141-erc20/dist/bridged-nep141/sendToEthereum')
case '@near-eth/nep141-erc20/natural-nep141/sendToEthereum':
return require('@near-eth/nep141-erc20/dist/natural-nep141/sendToEthereum')
case '@near-eth/nep141-erc20/bridged-erc20/sendToNear':
return require('@near-eth/nep141-erc20/dist/bridged-erc20/sendToNear')
case '@near-eth/near-ether/natural-near/sendToEthereum':
return require('@near-eth/near-ether/dist/natural-near/sendToEthereum')
case '@near-eth/near-ether/bridged-near/sendToNear':
Expand Down
156 changes: 156 additions & 0 deletions packages/nep141-erc20/src/bridged-erc20/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { Account, providers as najProviders } from 'near-api-js'
import { FinalExecutionOutcome } from 'near-api-js/lib/providers'
import { deserialize as deserializeBorsh } from 'near-api-js/lib/utils/serialize'
import {
getNearWallet,
getNearProvider,
getSignerProvider,
getBridgeParams
} from '@near-eth/client/dist/utils'
import { providers, Signer, Contract } from 'ethers'
import {
borshifyOutcomeProof,
nearOnEthSyncHeight,
findNearProof
} from '@near-eth/utils'

export async function logNep141Metadata (
{ nep141Address, options }: {
nep141Address: string
options?: {
nearAccount?: Account
nep141LockerAccount?: string
}
}
): Promise<FinalExecutionOutcome> {
options = options ?? {}
const bridgeParams = getBridgeParams()
const nep141LockerAccount: string = options.nep141LockerAccount ?? bridgeParams.nep141LockerAccount
const nearWallet = options.nearAccount ?? getNearWallet()
const isNajAccount = nearWallet instanceof Account

let tx: FinalExecutionOutcome
if (isNajAccount) {
tx = await nearWallet.functionCall({
contractId: nep141LockerAccount,
methodName: 'log_metadata',
args: { token_id: nep141Address },
gas: '100' + '0'.repeat(12)
})
} else {
tx = await nearWallet.signAndSendTransaction({
receiverId: nep141LockerAccount,
actions: [
{
type: 'FunctionCall',
params: {
methodName: 'log_metadata',
args: { token_id: nep141Address },
gas: '100' + '0'.repeat(12)
}
}
]
})
}
return tx
}

export async function deploy (
{ nep141MetadataLogTx, options }: {
nep141MetadataLogTx: string
options?: {
erc20FactoryAddress?: string
nep141LockerAccount?: string
erc20FactoryAbi?: string
signer?: Signer
provider?: providers.JsonRpcProvider
ethChainId?: number
nearProvider?: najProviders.Provider
ethClientAddress?: string
ethClientAbi?: string
}
}
): Promise<providers.TransactionResponse> {
options = options ?? {}
const bridgeParams = getBridgeParams()
const erc20FactoryAddress: string = options.erc20FactoryAddress ?? bridgeParams.erc20FactoryAddress
const nep141LockerAccount: string = options.nep141LockerAccount ?? bridgeParams.nep141LockerAccount
const provider = options.provider ?? getSignerProvider()
const ethChainId: number = (await provider.getNetwork()).chainId
const expectedChainId: number = options.ethChainId ?? bridgeParams.ethChainId
if (ethChainId !== expectedChainId) {
throw new Error(
`Wrong network for deploying token, expected: ${expectedChainId}, got: ${ethChainId}`
)
}
const nearProvider = options.nearProvider ?? getNearProvider()
const logTx = await nearProvider.txStatus(nep141MetadataLogTx, nep141LockerAccount)
let logReceipt: any
logTx.receipts_outcome.some((receipt) => {
// @ts-expect-error
if (receipt.outcome.executor_id !== nep141LockerAccount) return false
try {
// @ts-expect-error
const successValue = receipt.outcome.status.SuccessValue
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class LogEvent {
constructor (args: any) {
Object.assign(this, args)
}
}
const SCHEMA = new Map([
[LogEvent, {
kind: 'struct',
fields: [
['prefix', [32]],
['token', 'String'],
['name', 'String'],
['symbol', 'String'],
['decimals', 'u8'],
['block_height', 'u64']
]
}]
])
deserializeBorsh(
SCHEMA, LogEvent, Buffer.from(successValue, 'base64')
)
logReceipt = receipt
return true
} catch (error) {
console.log(error)
}
return false
})
if (!logReceipt) {
console.log(logReceipt)
throw new Error('Failed to parse NEP-141 log metadata receipt.')
}
const receiptBlock = await nearProvider.block({ blockId: logReceipt.block_hash })
const logBlockHeight = Number(receiptBlock.header.height)
const nearOnEthClientBlockHeight = await nearOnEthSyncHeight(
provider,
options.ethClientAddress ?? bridgeParams.ethClientAddress,
options.ethClientAbi ?? bridgeParams.ethClientAbi
)
if (logBlockHeight > nearOnEthClientBlockHeight) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Wait for the light client sync: NEP-141 metadata log block height: ${logBlockHeight}, light client block height: ${nearOnEthClientBlockHeight}`)
}
const proof = await findNearProof(
logReceipt.id,
nep141LockerAccount,
nearOnEthClientBlockHeight,
nearProvider,
provider,
options.ethClientAddress ?? bridgeParams.ethClientAddress,
options.ethClientAbi ?? bridgeParams.ethClientAbi
)
const borshProof = borshifyOutcomeProof(proof)
const erc20Factory = new Contract(
erc20FactoryAddress,
options.erc20FactoryAbi ?? bridgeParams.erc20FactoryAbi,
provider.getSigner()
)
const tx = await erc20Factory.newBridgeToken(borshProof, nearOnEthClientBlockHeight)
return tx
}
Loading

0 comments on commit 6292803

Please sign in to comment.