Skip to content

Commit 3e504fa

Browse files
feat: implement swaps FROM NEAR and token balance fetching
- Add getUnsignedNearTransaction/getNearTransactionFees to NearIntentsSwapper - Add execNearTransaction to TradeExecution class - Update useTradeExecution hook to handle NEAR chain namespace - Implement token balance fetching via nearblocks API in NearChainAdapter - Add VITE_NEAR_NODE_URL and VITE_NEARBLOCKS_API_URL to environment - Add nearblocks to CSP configuration - Export NEAR CoinGecko adapter (fixes market data) - Add GetNearTradeQuoteInput types to swapper types - Update tests to include NEAR USDC 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent b2aeea6 commit 3e504fa

File tree

16 files changed

+248
-16
lines changed

16 files changed

+248
-16
lines changed

.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ VITE_THORCHAIN_NODE_URL=https://api.thorchain.shapeshift.com/lcd
159159
VITE_MAYACHAIN_NODE_URL=https://api.mayachain.shapeshift.com/lcd
160160
VITE_SOLANA_NODE_URL=https://api.solana.shapeshift.com/api/v1/jsonrpc
161161
VITE_TRON_NODE_URL=https://api.trongrid.io
162+
VITE_NEAR_NODE_URL=https://rpc.mainnet.near.org
163+
VITE_NEARBLOCKS_API_URL=https://api.nearblocks.io/v1
162164
VITE_ALCHEMY_POLYGON_URL=https://polygon-mainnet.g.alchemy.com/v2/anoTMcIc2hbPUxri37h4DeuUwg2p5_xZ
163165

164166
# midgard

headers/csps/chains/near.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ const mode = process.env.MODE ?? process.env.NODE_ENV ?? 'development'
66
const env = loadEnv(mode, process.cwd(), '')
77

88
export const csp: Csp = {
9-
'connect-src': [env.VITE_NEAR_NODE_URL],
9+
'connect-src': [env.VITE_NEAR_NODE_URL, env.VITE_NEARBLOCKS_API_URL],
1010
}

packages/caip/src/adapters/coingecko/generated/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import solana from './solana_5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/adapter.json'
2121
import sui from './sui_35834a8a/adapter.json'
2222
import tron from './tron_0x2b6653dc/adapter.json'
2323
import zcash from './bip122_00040fe8ec8471911baa1db1266ea15d/adapter.json'
24+
import near from './near_mainnet/adapter.json'
2425

2526
export {
2627
bitcoin,
@@ -45,5 +46,6 @@ export {
4546
solana,
4647
sui,
4748
tron,
48-
zcash
49+
zcash,
50+
near
4951
}

packages/caip/src/adapters/coingecko/index.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ describe('adapters:coingecko', () => {
194194
assetNamespace: 'erc20',
195195
assetReference: '0xb88339cb7199b77e23db6e890353e22632ba630f',
196196
})
197+
const usdcOnNear = toAssetId({
198+
chainNamespace: CHAIN_NAMESPACE.Near,
199+
chainReference: CHAIN_REFERENCE.NearMainnet,
200+
assetNamespace: 'nep141',
201+
assetReference: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
202+
})
197203
expect(coingeckoToAssetIds('usd-coin')).toEqual([
198204
usdcOnEthereum,
199205
usdcOnAvalanche,
@@ -206,6 +212,7 @@ describe('adapters:coingecko', () => {
206212
usdcOnSolana,
207213
usdcOnSui,
208214
usdcOnTron,
215+
usdcOnNear,
209216
])
210217
})
211218
it('can get AssetIds for bridged USD Coin on EVM Chains', () => {

packages/chain-adapters/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@mysten/sui": "1.45.0",
3333
"@near-js/crypto": "^2.5.1",
3434
"@near-js/transactions": "^2.5.1",
35+
"@near-js/utils": "^2.5.1",
3536
"@shapeshiftoss/caip": "workspace:^",
3637
"@shapeshiftoss/hdwallet-core": "1.62.35-near-chain.1",
3738
"@shapeshiftoss/hdwallet-ledger": "1.62.35-near-chain.1",

packages/chain-adapters/src/near/NearChainAdapter.ts

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@ import {
55
Signature,
66
SignedTransaction,
77
} from '@near-js/transactions'
8+
import { baseDecode, baseEncode } from '@near-js/utils'
89
import type { AssetId, ChainId } from '@shapeshiftoss/caip'
9-
import { ASSET_REFERENCE, nearAssetId, nearChainId } from '@shapeshiftoss/caip'
10+
import {
11+
ASSET_NAMESPACE,
12+
ASSET_REFERENCE,
13+
nearAssetId,
14+
nearChainId,
15+
toAssetId,
16+
} from '@shapeshiftoss/caip'
1017
import type { HDWallet } from '@shapeshiftoss/hdwallet-core'
1118
import type { Bip44Params, RootBip44Params } from '@shapeshiftoss/types'
1219
import { KnownChainIds } from '@shapeshiftoss/types'
1320
import { TransferType, TxStatus } from '@shapeshiftoss/unchained-client'
14-
import bs58 from 'bs58'
1521

1622
import type { ChainAdapter as IChainAdapter } from '../api'
1723
import { ChainAdapterError, ErrorHandler } from '../error/ErrorHandler'
@@ -41,6 +47,25 @@ interface NearWallet extends HDWallet {
4147
}): Promise<{ signature: string; publicKey: string } | null>
4248
}
4349

50+
interface NearBlocksFtMeta {
51+
name: string
52+
symbol: string
53+
decimals: number
54+
icon: string | null
55+
reference: string | null
56+
price: string | null
57+
}
58+
59+
interface NearBlocksFtBalance {
60+
contract: string
61+
amount: string
62+
ft_meta: NearBlocksFtMeta
63+
}
64+
65+
interface NearBlocksInventoryResponse {
66+
fts: NearBlocksFtBalance[]
67+
}
68+
4469
const supportsNear = (wallet: HDWallet): wallet is NearWallet => {
4570
return '_supportsNear' in wallet && (wallet as any)._supportsNear === true
4671
}
@@ -139,6 +164,8 @@ interface NearFullTxResult {
139164
}[]
140165
}
141166

167+
const NEARBLOCKS_API_URL = 'https://api.nearblocks.io/v1'
168+
142169
export class ChainAdapter implements IChainAdapter<KnownChainIds.NearMainnet> {
143170
static readonly rootBip44Params: RootBip44Params = {
144171
purpose: 44,
@@ -244,21 +271,60 @@ export class ChainAdapter implements IChainAdapter<KnownChainIds.NearMainnet> {
244271
}
245272
}
246273

274+
private async fetchTokenBalances(accountId: string): Promise<
275+
{
276+
assetId: AssetId
277+
balance: string
278+
name: string
279+
precision: number
280+
symbol: string
281+
}[]
282+
> {
283+
try {
284+
const response = await fetch(`${NEARBLOCKS_API_URL}/account/${accountId}/inventory`)
285+
if (!response.ok) {
286+
console.warn(`Failed to fetch NEAR token balances: ${response.status}`)
287+
return []
288+
}
289+
290+
const data = (await response.json()) as { inventory: NearBlocksInventoryResponse }
291+
const fts = data.inventory?.fts ?? []
292+
293+
return fts.map(ft => ({
294+
assetId: toAssetId({
295+
chainId: this.chainId,
296+
assetNamespace: ASSET_NAMESPACE.nep141,
297+
assetReference: ft.contract,
298+
}),
299+
balance: ft.amount,
300+
name: ft.ft_meta.name,
301+
precision: ft.ft_meta.decimals,
302+
symbol: ft.ft_meta.symbol,
303+
}))
304+
} catch (err) {
305+
console.warn('Failed to fetch NEAR token balances:', err)
306+
return []
307+
}
308+
}
309+
247310
async getAccount(pubkey: string): Promise<Account<KnownChainIds.NearMainnet>> {
248311
try {
249-
const accountResult = await this.rpcCall<NearAccountResult>('query', {
250-
request_type: 'view_account',
251-
finality: 'final',
252-
account_id: pubkey,
253-
})
312+
const [accountResult, tokens] = await Promise.all([
313+
this.rpcCall<NearAccountResult>('query', {
314+
request_type: 'view_account',
315+
finality: 'final',
316+
account_id: pubkey,
317+
}),
318+
this.fetchTokenBalances(pubkey),
319+
])
254320

255321
return {
256322
balance: accountResult.amount,
257323
chainId: this.chainId,
258324
assetId: this.assetId,
259325
chain: this.getType(),
260326
chainSpecific: {
261-
tokens: [],
327+
tokens,
262328
},
263329
pubkey,
264330
}
@@ -311,7 +377,7 @@ export class ChainAdapter implements IChainAdapter<KnownChainIds.NearMainnet> {
311377

312378
// Convert hex public key to bytes and create PublicKey using standard NEAR format
313379
const pubKeyBytes = Buffer.from(from, 'hex')
314-
const pubKeyBase58 = bs58.encode(pubKeyBytes)
380+
const pubKeyBase58 = baseEncode(pubKeyBytes)
315381
const publicKey = PublicKey.fromString(`ed25519:${pubKeyBase58}`)
316382

317383
// Get current nonce from access key
@@ -330,7 +396,7 @@ export class ChainAdapter implements IChainAdapter<KnownChainIds.NearMainnet> {
330396
[],
331397
)
332398
const blockHashBase58 = statusResult.sync_info.latest_block_hash
333-
const blockHash = bs58.decode(blockHashBase58)
399+
const blockHash = baseDecode(blockHashBase58)
334400

335401
// Build transfer action
336402
const actions = [actionCreators.transfer(BigInt(value))]

packages/swapper/src/swappers/ButterSwap/swapperApi/getTradeQuote.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe('getTradeQuote', () => {
4040
assertGetSolanaChainAdapter: () => vi.fn() as any,
4141
assertGetTronChainAdapter: () => vi.fn() as any,
4242
assertGetSuiChainAdapter: () => vi.fn() as any,
43+
assertGetNearChainAdapter: () => vi.fn() as any,
4344
config: {
4445
VITE_BUTTERSWAP_CLIENT_ID: 'test',
4546
} as any,

packages/swapper/src/swappers/ButterSwap/swapperApi/getTradeRate.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('getTradeRate', () => {
2929
assertGetSolanaChainAdapter: () => vi.fn() as any,
3030
assertGetTronChainAdapter: () => vi.fn() as any,
3131
assertGetSuiChainAdapter: () => vi.fn() as any,
32+
assertGetNearChainAdapter: () => vi.fn() as any,
3233
config: {
3334
VITE_BUTTERSWAP_CLIENT_ID: 'test',
3435
} as any,
@@ -70,6 +71,7 @@ describe('getTradeRate', () => {
7071
assertGetSolanaChainAdapter: () => vi.fn() as any,
7172
assertGetTronChainAdapter: () => vi.fn() as any,
7273
assertGetSuiChainAdapter: () => vi.fn() as any,
74+
assertGetNearChainAdapter: () => vi.fn() as any,
7375
config: {
7476
VITE_BUTTERSWAP_CLIENT_ID: 'test',
7577
} as any,

packages/swapper/src/swappers/NearIntentsSwapper/NearIntentsSwapper.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Swapper } from '../../types'
22
import {
33
executeEvmTransaction,
4+
executeNearTransaction,
45
executeSolanaTransaction,
56
executeSuiTransaction,
67
executeTronTransaction,
@@ -11,6 +12,7 @@ export const nearIntentsSwapper: Swapper = {
1112
executeSolanaTransaction,
1213
executeTronTransaction,
1314
executeSuiTransaction,
15+
executeNearTransaction,
1416
executeUtxoTransaction: (txToSign, { signAndBroadcastTransaction }) => {
1517
return signAndBroadcastTransaction(txToSign)
1618
},

packages/swapper/src/swappers/NearIntentsSwapper/endpoints.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,32 @@ export const nearIntentsApi: SwapperApi = {
246246
return Promise.resolve(step.feeData.networkFeeCryptoBaseUnit)
247247
},
248248

249+
getUnsignedNearTransaction: async ({
250+
stepIndex,
251+
tradeQuote,
252+
from,
253+
assertGetNearChainAdapter,
254+
}: GetUnsignedNearTransactionArgs) => {
255+
if (!isExecutableTradeQuote(tradeQuote)) throw new Error('Unable to execute a trade rate quote')
256+
257+
const step = getExecutableTradeStep(tradeQuote, stepIndex)
258+
259+
const { accountNumber, sellAsset, nearIntentsSpecific } = step
260+
if (!nearIntentsSpecific) throw new Error('nearIntentsSpecific is required')
261+
262+
const adapter = assertGetNearChainAdapter(sellAsset.chainId)
263+
264+
const to = nearIntentsSpecific.depositAddress
265+
const value = step.sellAmountIncludingProtocolFeesCryptoBaseUnit
266+
267+
return await adapter.buildSendApiTransaction({
268+
to,
269+
from,
270+
value,
271+
accountNumber,
272+
})
273+
},
274+
249275
getNearTransactionFees: ({ tradeQuote, stepIndex }: GetUnsignedNearTransactionArgs) => {
250276
if (!isExecutableTradeQuote(tradeQuote)) throw new Error('Unable to execute a trade rate quote')
251277

0 commit comments

Comments
 (0)