Skip to content

Commit d3e12f3

Browse files
committed
Reshape ArmSwap as explicit token in/out swap with per-side rates
1 parent ab8d1b9 commit d3e12f3

5 files changed

Lines changed: 160 additions & 61 deletions

File tree

Lines changed: 7 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schema.graphql

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -832,10 +832,21 @@ type ArmSwap @entity {
832832
blockNumber: Int! @index
833833
address: String! @index
834834
from: String!
835-
# One row per transaction (aggregated). Amounts are paired from the tx's ERC20
836-
# Transfer logs and normalized to liquidity-asset terms. Per-action decoding via
837-
# trace return data was reverted: the portal serves callResultOutput only for
838-
# recent blocks, which silently dropped most pre-Dec-2025 swaps.
835+
# One row per (transaction, base asset, direction), paired from the tx's ERC20 Transfer logs.
836+
# Modeled as an explicit swap: tokenIn/amountIn = what the trader supplied (ARM received);
837+
# tokenOut/amountOut = what the trader received (ARM sent). amount* are RAW token units; rate*
838+
# is each token's asset0 (liquidity) rate at the swap block, 1e18-scaled (== 1e18 for the
839+
# liquidity asset and pegged base assets). Every swap has the liquidity asset on exactly one
840+
# side (the contract forbids base<->base). Derived: asset0 value = amount x rate / 1e18;
841+
# execution price = amountOut / amountIn; spread = valueOut / valueIn.
842+
tokenIn: String! @index
843+
tokenOut: String! @index
844+
amountIn: BigInt!
845+
amountOut: BigInt!
846+
rateIn: BigInt!
847+
rateOut: BigInt!
848+
# Legacy ARM-perspective signed deltas in asset0 terms (assets1 = base x rate). Kept for
849+
# back-compat; superseded by the token*/amount*/rate* fields above.
839850
assets0: BigInt!
840851
assets1: BigInt!
841852
}

src/model/generated/armSwap.model.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,26 @@ export class ArmSwap {
3838
@StringColumn_({nullable: false})
3939
from!: string
4040

41+
@Index_()
42+
@StringColumn_({nullable: false})
43+
tokenIn!: string
44+
45+
@Index_()
46+
@StringColumn_({nullable: false})
47+
tokenOut!: string
48+
49+
@BigIntColumn_({nullable: false})
50+
amountIn!: bigint
51+
52+
@BigIntColumn_({nullable: false})
53+
amountOut!: bigint
54+
55+
@BigIntColumn_({nullable: false})
56+
rateIn!: bigint
57+
58+
@BigIntColumn_({nullable: false})
59+
rateOut!: bigint
60+
4161
@BigIntColumn_({nullable: false})
4262
assets0!: bigint
4363

src/templates/origin-arm/origin-arm.graphql

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,21 @@ type ArmSwap @entity {
152152
blockNumber: Int! @index
153153
address: String! @index
154154
from: String!
155-
# One row per transaction (aggregated). Amounts are paired from the tx's ERC20
156-
# Transfer logs and normalized to liquidity-asset terms. Per-action decoding via
157-
# trace return data was reverted: the portal serves callResultOutput only for
158-
# recent blocks, which silently dropped most pre-Dec-2025 swaps.
155+
# One row per (transaction, base asset, direction), paired from the tx's ERC20 Transfer logs.
156+
# Modeled as an explicit swap: tokenIn/amountIn = what the trader supplied (ARM received);
157+
# tokenOut/amountOut = what the trader received (ARM sent). amount* are RAW token units; rate*
158+
# is each token's asset0 (liquidity) rate at the swap block, 1e18-scaled (== 1e18 for the
159+
# liquidity asset and pegged base assets). Every swap has the liquidity asset on exactly one
160+
# side (the contract forbids base<->base). Derived: asset0 value = amount x rate / 1e18;
161+
# execution price = amountOut / amountIn; spread = valueOut / valueIn.
162+
tokenIn: String! @index
163+
tokenOut: String! @index
164+
amountIn: BigInt!
165+
amountOut: BigInt!
166+
rateIn: BigInt!
167+
rateOut: BigInt!
168+
# Legacy ARM-perspective signed deltas in asset0 terms (assets1 = base x rate). Kept for
169+
# back-compat; superseded by the token*/amount*/rate* fields above.
159170
assets0: BigInt!
160171
assets1: BigInt!
161172
}

src/templates/origin-arm/origin-arm.ts

Lines changed: 103 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,21 @@ export const createOriginARMProcessors = ({
421421
}
422422
const calculateTotalYield = (state: ArmState) =>
423423
state.totalAssets - state.totalDeposits + state.totalWithdrawals
424+
// A base asset's rate in asset0 (liquidity) terms, 1e18-scaled (token0 per base).
425+
// Mirrors the assetRates logic: pegged base assets and the liquidity asset are 1:1;
426+
// appreciating assets use their adapter (post-upgrade) or getRate1 (pre-upgrade single base).
427+
const getBaseAssetRate = async (block: Block, assetIdx: number): Promise<bigint> => {
428+
if (assetIdx === 0 || armEntity.assetPegged[assetIdx]) return 10n ** 18n
429+
const upgraded = armEntity.upgradeBlock != null && block.header.height >= armEntity.upgradeBlock
430+
if (upgraded) {
431+
return new originMultibaseArmAbi.Contract(
432+
ctx,
433+
block.header,
434+
armEntity.assetAdapters[assetIdx],
435+
).convertToAssets(10n ** 18n)
436+
}
437+
return getRate1 ? await getRate1(ctx, block) : 10n ** 18n
438+
}
424439
const checkpoint = (
425440
account: string,
426441
block: Block,
@@ -607,66 +622,104 @@ export const createOriginARMProcessors = ({
607622
const transfers0 = transfers
608623
.filter((log) => log.address === armEntity.token0)
609624
.map((log) => erc20Abi.events.Transfer.decode(log))
610-
const transfers1 = transfers
611-
.filter((log) => log.address === armEntity.token1)
612-
.map((log) => erc20Abi.events.Transfer.decode(log))
613625

614-
const pairs: { assets0: bigint; assets1: bigint }[] = []
626+
const ONE = 10n ** 18n
627+
const swapMeta = {
628+
chainId: ctx.chain.id,
629+
txHash: transactionHash,
630+
txFrom: trace.transaction?.from ?? '',
631+
txTo: trace.transaction?.to ?? '',
632+
timestamp: new Date(block.header.timestamp),
633+
blockNumber: block.header.height,
634+
address: armAddress,
635+
from: trace.action.from,
636+
}
615637

616-
const transfersOut0 = transfers0.filter((t) => t.from.toLowerCase() === armAddress)
617-
const transfersIn1 = transfers1.filter((t) => t.to.toLowerCase() === armAddress)
638+
// Every swap is liquidityAsset(token0) <-> one base asset (the contract forbids
639+
// base<->base). Pair token0 transfers against each base asset and emit one ArmSwap
640+
// per (tx, baseAsset, direction) with raw amounts and each side's asset0 rate.
641+
for (let assetIdx = 1; assetIdx < armEntity.assets.length; assetIdx++) {
642+
const baseAsset = armEntity.assets[assetIdx]
643+
const transfersB = transfers
644+
.filter((log) => log.address === baseAsset)
645+
.map((log) => erc20Abi.events.Transfer.decode(log))
646+
if (transfersB.length === 0) continue
618647

619-
const rate1 = getRate1 ? await getRate1(ctx, block) : 10n ** 18n
620-
const rate1Number = +formatEther(rate1)
648+
const rateB = await getBaseAssetRate(block, assetIdx) // token0 per base
649+
const rateBNumber = +formatEther(rateB)
621650

622-
for (let i = 0; i < transfersOut0.length; i++) {
623-
for (let j = 0; j < transfersIn1.length; j++) {
624-
const out0 = transfersOut0[i]
625-
const in1 = transfersIn1[j]
626-
if (out0.value === 0n || in1.value === 0n) continue
627-
const rate = +formatEther((out0.value * 10n ** 18n) / in1.value)
628-
if (Math.abs(rate - rate1Number) <= 0.01) {
629-
pairs.push({ assets0: -out0.value, assets1: (in1.value * rate1) / 10n ** 18n })
630-
transfersIn1.splice(j, 1)
631-
break
651+
// Trader sells base for token0: ARM receives base, sends token0.
652+
const transfersOut0 = transfers0.filter((t) => t.from.toLowerCase() === armAddress)
653+
const transfersInB = transfersB.filter((t) => t.to.toLowerCase() === armAddress)
654+
let sellBaseIn = 0n
655+
let sellToken0Out = 0n
656+
for (let i = 0; i < transfersOut0.length; i++) {
657+
for (let j = 0; j < transfersInB.length; j++) {
658+
const out0 = transfersOut0[i]
659+
const inB = transfersInB[j]
660+
if (out0.value === 0n || inB.value === 0n) continue
661+
const rate = +formatEther((out0.value * ONE) / inB.value) // token0 per base
662+
if (Math.abs(rate - rateBNumber) <= 0.01) {
663+
sellBaseIn += inB.value
664+
sellToken0Out += out0.value
665+
transfersInB.splice(j, 1)
666+
break
667+
}
632668
}
633669
}
634-
}
670+
if (sellBaseIn > 0n) {
671+
swaps.push(
672+
new ArmSwap({
673+
...swapMeta,
674+
id: `${ctx.chain.id}::${transactionHash}:${baseAsset}:sell`,
675+
tokenIn: baseAsset,
676+
amountIn: sellBaseIn,
677+
rateIn: rateB,
678+
tokenOut: armEntity.token0,
679+
amountOut: sellToken0Out,
680+
rateOut: ONE,
681+
assets0: -sellToken0Out,
682+
assets1: (sellBaseIn * rateB) / ONE,
683+
}),
684+
)
685+
}
635686

636-
const transfersOut1 = transfers1.filter((t) => t.from.toLowerCase() === armAddress)
637-
const transfersIn0 = transfers0.filter((t) => t.to.toLowerCase() === armAddress)
638-
for (let i = 0; i < transfersOut1.length; i++) {
639-
for (let j = 0; j < transfersIn0.length; j++) {
640-
const out1 = transfersOut1[i]
641-
const in0 = transfersIn0[j]
642-
if (out1.value === 0n || in0.value === 0n) continue
643-
const rate = +formatEther((out1.value * 10n ** 18n) / in0.value)
644-
if (Math.abs(rate - rate1Number) <= 0.01) {
645-
pairs.push({ assets0: in0.value, assets1: (-out1.value * rate1) / 10n ** 18n })
646-
transfersIn0.splice(j, 1)
647-
break
687+
// Trader buys base with token0: ARM receives token0, sends base.
688+
const transfersOutB = transfersB.filter((t) => t.from.toLowerCase() === armAddress)
689+
const transfersIn0 = transfers0.filter((t) => t.to.toLowerCase() === armAddress)
690+
let buyBaseOut = 0n
691+
let buyToken0In = 0n
692+
for (let i = 0; i < transfersOutB.length; i++) {
693+
for (let j = 0; j < transfersIn0.length; j++) {
694+
const outB = transfersOutB[i]
695+
const in0 = transfersIn0[j]
696+
if (outB.value === 0n || in0.value === 0n) continue
697+
const rate = +formatEther((in0.value * ONE) / outB.value) // token0 per base
698+
if (Math.abs(rate - rateBNumber) <= 0.01) {
699+
buyBaseOut += outB.value
700+
buyToken0In += in0.value
701+
transfersIn0.splice(j, 1)
702+
break
703+
}
648704
}
649705
}
706+
if (buyBaseOut > 0n) {
707+
swaps.push(
708+
new ArmSwap({
709+
...swapMeta,
710+
id: `${ctx.chain.id}::${transactionHash}:${baseAsset}:buy`,
711+
tokenIn: armEntity.token0,
712+
amountIn: buyToken0In,
713+
rateIn: ONE,
714+
tokenOut: baseAsset,
715+
amountOut: buyBaseOut,
716+
rateOut: rateB,
717+
assets0: buyToken0In,
718+
assets1: -(buyBaseOut * rateB) / ONE,
719+
}),
720+
)
721+
}
650722
}
651-
652-
const assets0 = pairs.reduce((acc, pair) => acc + pair.assets0, 0n)
653-
const assets1 = pairs.reduce((acc, pair) => acc + pair.assets1, 0n)
654-
655-
swaps.push(
656-
new ArmSwap({
657-
id: `${ctx.chain.id}::${transactionHash}:${trace.traceAddress.join(':')}`,
658-
chainId: ctx.chain.id,
659-
txHash: transactionHash,
660-
txFrom: trace.transaction?.from ?? '',
661-
txTo: trace.transaction?.to ?? '',
662-
timestamp: new Date(block.header.timestamp),
663-
blockNumber: block.header.height,
664-
address: armAddress,
665-
from: trace.action.from,
666-
assets0,
667-
assets1,
668-
}),
669-
)
670723
}
671724
}
672725
}

0 commit comments

Comments
 (0)