nara-sdk 1.0.82 → 1.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nara-sdk",
3
- "version": "1.0.82",
3
+ "version": "1.0.84",
4
4
  "description": "SDK for the Nara chain (Solana-compatible)",
5
5
  "module": "index.ts",
6
6
  "main": "index.ts",
package/src/bridge.ts CHANGED
@@ -51,6 +51,8 @@ export interface BridgeTokenConfig {
51
51
  decimals: number;
52
52
  /** Minimum per-transfer amount in raw units (rejected if below) */
53
53
  minAmount: bigint;
54
+ /** Minimum fee floor in raw units (fee = max(amount * bps, minFee)) */
55
+ minFee: bigint;
54
56
  solana: BridgeTokenSide;
55
57
  nara: BridgeTokenSide;
56
58
  }
@@ -130,7 +132,8 @@ export const BRIDGE_TOKENS: Record<string, BridgeTokenConfig> = {
130
132
  USDC: {
131
133
  symbol: "USDC",
132
134
  decimals: 6,
133
- minAmount: 100_000n, // 0.1 USDC
135
+ minAmount: 5_000_000n, // 5 USDC
136
+ minFee: 500_000n, // 0.5 USDC (fee floor)
134
137
  solana: {
135
138
  warpProgram: new PublicKey("4GcZJTa8s9vxtTz97Vj1RrwKMqPkT3DiiJkvUQDwsuZP"),
136
139
  mode: "collateral",
@@ -147,7 +150,8 @@ export const BRIDGE_TOKENS: Record<string, BridgeTokenConfig> = {
147
150
  USDT: {
148
151
  symbol: "USDT",
149
152
  decimals: 6,
150
- minAmount: 100_000n, // 0.1 USDT
153
+ minAmount: 5_000_000n, // 5 USDT
154
+ minFee: 500_000n, // 0.5 USDT (fee floor)
151
155
  solana: {
152
156
  warpProgram: new PublicKey("DCTt9H3pwwU89qC3Z4voYNThZypV68AwhYNzMNBxWXoy"),
153
157
  mode: "collateral",
@@ -164,7 +168,8 @@ export const BRIDGE_TOKENS: Record<string, BridgeTokenConfig> = {
164
168
  SOL: {
165
169
  symbol: "SOL",
166
170
  decimals: 9,
167
- minAmount: 1_000_000n, // 0.001 SOL
171
+ minAmount: 100_000_000n, // 0.1 SOL
172
+ minFee: 10_000_000n, // 0.01 SOL (fee floor)
168
173
  solana: {
169
174
  warpProgram: new PublicKey("46MmAWwKRAt9uvn7m44NXbVq2DCWBQE2r1TDw25nyXrt"),
170
175
  mode: "native",
@@ -326,19 +331,32 @@ export function encodeTransferRemote(
326
331
  // ─── Fee calculation ──────────────────────────────────────────────
327
332
 
328
333
  export interface FeeSplit {
334
+ /** Total fee (max of percentage and minFee) in raw units */
329
335
  feeAmount: bigint;
336
+ /** Net amount bridged after fee */
330
337
  bridgeAmount: bigint;
338
+ /** Percentage part (bps) used */
331
339
  feeBps: number;
340
+ /** Fee floor in raw units */
341
+ minFee: bigint;
332
342
  }
333
343
 
334
- export function calculateBridgeFee(amount: bigint, feeBps?: number): FeeSplit {
344
+ /**
345
+ * Calculate the bridge fee: max(amount * bps / 10000, minFee)
346
+ */
347
+ export function calculateBridgeFee(
348
+ amount: bigint,
349
+ feeBps?: number,
350
+ minFee: bigint = 0n
351
+ ): FeeSplit {
335
352
  const bps = feeBps ?? DEFAULT_BRIDGE_FEE_BPS;
336
353
  if (bps < 0 || bps > BRIDGE_FEE_BPS_DENOMINATOR) {
337
354
  throw new Error(`Invalid feeBps: ${bps}`);
338
355
  }
339
- const feeAmount = (amount * BigInt(bps)) / BigInt(BRIDGE_FEE_BPS_DENOMINATOR);
356
+ const pctFee = (amount * BigInt(bps)) / BigInt(BRIDGE_FEE_BPS_DENOMINATOR);
357
+ const feeAmount = pctFee > minFee ? pctFee : minFee;
340
358
  const bridgeAmount = amount - feeAmount;
341
- return { feeAmount, bridgeAmount, feeBps: bps };
359
+ return { feeAmount, bridgeAmount, feeBps: bps, minFee };
342
360
  }
343
361
 
344
362
  // ─── Fee instruction builder ──────────────────────────────────────
@@ -538,11 +556,11 @@ export function makeBridgeIxs(params: BridgeTransferParams): BridgeIxsResult {
538
556
  }
539
557
 
540
558
  const split = skipFee
541
- ? { feeAmount: 0n, bridgeAmount: amount, feeBps: 0 }
542
- : calculateBridgeFee(amount, feeBps);
559
+ ? { feeAmount: 0n, bridgeAmount: amount, feeBps: 0, minFee: 0n }
560
+ : calculateBridgeFee(amount, feeBps, tokenCfg.minFee);
543
561
 
544
562
  if (split.bridgeAmount <= 0n) {
545
- throw new Error("bridge amount after fee is zero — increase amount or lower feeBps");
563
+ throw new Error("bridge amount after fee is zero — increase amount or lower fee");
546
564
  }
547
565
 
548
566
  const recipientForFee = feeRecipient ?? getBridgeFeeRecipient(fromChain);
@@ -761,6 +761,10 @@
761
761
  {
762
762
  "name": "max_reward_count",
763
763
  "type": "u32"
764
+ },
765
+ {
766
+ "name": "free_stake_multiplier",
767
+ "type": "u32"
764
768
  }
765
769
  ]
766
770
  },
@@ -1910,6 +1914,11 @@
1910
1914
  "code": 6022,
1911
1915
  "name": "InsufficientAirdrop",
1912
1916
  "msg": "Airdrop fund has insufficient balance"
1917
+ },
1918
+ {
1919
+ "code": 6023,
1920
+ "name": "InvalidMultiplier",
1921
+ "msg": "Multiplier must be >= 1"
1913
1922
  }
1914
1923
  ],
1915
1924
  "types": [
@@ -2006,12 +2015,16 @@
2006
2015
  "name": "max_airdrop_count",
2007
2016
  "type": "u32"
2008
2017
  },
2018
+ {
2019
+ "name": "free_stake_multiplier",
2020
+ "type": "u32"
2021
+ },
2009
2022
  {
2010
2023
  "name": "_padding",
2011
2024
  "type": {
2012
2025
  "array": [
2013
2026
  "u8",
2014
- 20
2027
+ 16
2015
2028
  ]
2016
2029
  }
2017
2030
  }
@@ -2049,15 +2062,15 @@
2049
2062
  "type": "u64"
2050
2063
  },
2051
2064
  {
2052
- "name": "reward_count",
2065
+ "name": "stake_reward_count",
2053
2066
  "type": "u32"
2054
2067
  },
2055
2068
  {
2056
- "name": "reward_per_winner",
2069
+ "name": "stake_reward_per_winner",
2057
2070
  "type": "u64"
2058
2071
  },
2059
2072
  {
2060
- "name": "winner_count",
2073
+ "name": "stake_winner_count",
2061
2074
  "type": "u32"
2062
2075
  },
2063
2076
  {
@@ -2080,12 +2093,24 @@
2080
2093
  "name": "avg_participant_stake",
2081
2094
  "type": "u64"
2082
2095
  },
2096
+ {
2097
+ "name": "free_reward_count",
2098
+ "type": "u32"
2099
+ },
2100
+ {
2101
+ "name": "free_reward_per_winner",
2102
+ "type": "u64"
2103
+ },
2104
+ {
2105
+ "name": "free_winner_count",
2106
+ "type": "u32"
2107
+ },
2083
2108
  {
2084
2109
  "name": "_padding",
2085
2110
  "type": {
2086
2111
  "array": [
2087
2112
  "u8",
2088
- 64
2113
+ 48
2089
2114
  ]
2090
2115
  }
2091
2116
  }
@@ -767,6 +767,10 @@ export type NaraQuest = {
767
767
  {
768
768
  "name": "maxRewardCount",
769
769
  "type": "u32"
770
+ },
771
+ {
772
+ "name": "freeStakeMultiplier",
773
+ "type": "u32"
770
774
  }
771
775
  ]
772
776
  },
@@ -1916,6 +1920,11 @@ export type NaraQuest = {
1916
1920
  "code": 6022,
1917
1921
  "name": "insufficientAirdrop",
1918
1922
  "msg": "Airdrop fund has insufficient balance"
1923
+ },
1924
+ {
1925
+ "code": 6023,
1926
+ "name": "invalidMultiplier",
1927
+ "msg": "Multiplier must be >= 1"
1919
1928
  }
1920
1929
  ],
1921
1930
  "types": [
@@ -2012,12 +2021,16 @@ export type NaraQuest = {
2012
2021
  "name": "maxAirdropCount",
2013
2022
  "type": "u32"
2014
2023
  },
2024
+ {
2025
+ "name": "freeStakeMultiplier",
2026
+ "type": "u32"
2027
+ },
2015
2028
  {
2016
2029
  "name": "padding",
2017
2030
  "type": {
2018
2031
  "array": [
2019
2032
  "u8",
2020
- 20
2033
+ 16
2021
2034
  ]
2022
2035
  }
2023
2036
  }
@@ -2055,15 +2068,15 @@ export type NaraQuest = {
2055
2068
  "type": "u64"
2056
2069
  },
2057
2070
  {
2058
- "name": "rewardCount",
2071
+ "name": "stakeRewardCount",
2059
2072
  "type": "u32"
2060
2073
  },
2061
2074
  {
2062
- "name": "rewardPerWinner",
2075
+ "name": "stakeRewardPerWinner",
2063
2076
  "type": "u64"
2064
2077
  },
2065
2078
  {
2066
- "name": "winnerCount",
2079
+ "name": "stakeWinnerCount",
2067
2080
  "type": "u32"
2068
2081
  },
2069
2082
  {
@@ -2086,12 +2099,24 @@ export type NaraQuest = {
2086
2099
  "name": "avgParticipantStake",
2087
2100
  "type": "u64"
2088
2101
  },
2102
+ {
2103
+ "name": "freeRewardCount",
2104
+ "type": "u32"
2105
+ },
2106
+ {
2107
+ "name": "freeRewardPerWinner",
2108
+ "type": "u64"
2109
+ },
2110
+ {
2111
+ "name": "freeWinnerCount",
2112
+ "type": "u32"
2113
+ },
2089
2114
  {
2090
2115
  "name": "padding",
2091
2116
  "type": {
2092
2117
  "array": [
2093
2118
  "u8",
2094
- 64
2119
+ 48
2095
2120
  ]
2096
2121
  }
2097
2122
  }
package/src/quest.ts CHANGED
@@ -45,11 +45,18 @@ export interface QuestInfo {
45
45
  round: string;
46
46
  question: string;
47
47
  answerHash: number[];
48
- rewardPerWinner: number;
48
+ /** Total reward pool for the round (stake + free), in NARA */
49
49
  totalReward: number;
50
- rewardCount: number;
51
- winnerCount: number;
52
- remainingSlots: number;
50
+ /** Stake-gated reward bucket */
51
+ stakeRewardCount: number;
52
+ stakeWinnerCount: number;
53
+ stakeRewardPerWinner: number;
54
+ stakeRemainingSlots: number;
55
+ /** Credit (free-stake) reward bucket — consumed by stakeInfo.freeCredits */
56
+ creditRewardCount: number;
57
+ creditWinnerCount: number;
58
+ creditRewardPerWinner: number;
59
+ creditRemainingSlots: number;
53
60
  difficulty: number;
54
61
  deadline: number;
55
62
  timeRemaining: number;
@@ -323,16 +330,25 @@ export async function getQuestInfo(
323
330
  stakeHigh, stakeLow, createdAtMs, decayMs, nowMs
324
331
  );
325
332
 
333
+ const stakeRewardCount = pool.stakeRewardCount;
334
+ const stakeWinnerCount = pool.stakeWinnerCount;
335
+ const creditRewardCount = pool.freeRewardCount;
336
+ const creditWinnerCount = pool.freeWinnerCount;
337
+
326
338
  return {
327
339
  active,
328
340
  round: pool.round.toString(),
329
341
  question: pool.question,
330
342
  answerHash: Array.from(pool.answerHash),
331
- rewardPerWinner: pool.rewardPerWinner.toNumber() / LAMPORTS_PER_SOL,
332
343
  totalReward: pool.rewardAmount.toNumber() / LAMPORTS_PER_SOL,
333
- rewardCount: pool.rewardCount,
334
- winnerCount: pool.winnerCount,
335
- remainingSlots: Math.max(0, pool.rewardCount - pool.winnerCount),
344
+ stakeRewardCount,
345
+ stakeWinnerCount,
346
+ stakeRewardPerWinner: pool.stakeRewardPerWinner.toNumber() / LAMPORTS_PER_SOL,
347
+ stakeRemainingSlots: Math.max(0, stakeRewardCount - stakeWinnerCount),
348
+ creditRewardCount,
349
+ creditWinnerCount,
350
+ creditRewardPerWinner: pool.freeRewardPerWinner.toNumber() / LAMPORTS_PER_SOL,
351
+ creditRemainingSlots: Math.max(0, creditRewardCount - creditWinnerCount),
336
352
  difficulty: pool.difficulty,
337
353
  deadline,
338
354
  timeRemaining: secsLeft,
@@ -758,17 +774,21 @@ export async function initializeQuest(
758
774
 
759
775
  /**
760
776
  * Set the reward config (authority only).
777
+ *
778
+ * @param freeStakeMultiplier - Multiplier for users answering with free credits
779
+ * vs stake-based winners (must be >= 1).
761
780
  */
762
781
  export async function setRewardConfig(
763
782
  connection: Connection,
764
783
  wallet: Keypair,
765
784
  minRewardCount: number,
766
785
  maxRewardCount: number,
786
+ freeStakeMultiplier: number,
767
787
  options?: QuestOptions
768
788
  ): Promise<string> {
769
789
  const program = createProgram(connection, wallet, options?.programId);
770
790
  const ix = await program.methods
771
- .setRewardConfig(minRewardCount, maxRewardCount)
791
+ .setRewardConfig(minRewardCount, maxRewardCount, freeStakeMultiplier)
772
792
  .accounts({ authority: wallet.publicKey } as any)
773
793
  .instruction();
774
794
  return sendTx(connection, wallet, [ix]);
@@ -888,6 +908,7 @@ export async function getQuestConfig(
888
908
  stakeAuthority: PublicKey;
889
909
  airdropAmount: number;
890
910
  maxAirdropCount: number;
911
+ freeStakeMultiplier: number;
891
912
  }> {
892
913
  const kp = Keypair.generate();
893
914
  const program = createProgram(connection, kp, options?.programId);
@@ -912,6 +933,7 @@ export async function getQuestConfig(
912
933
  stakeAuthority: config.stakeAuthority,
913
934
  airdropAmount: Number(config.airdropAmount.toString()),
914
935
  maxAirdropCount: config.maxAirdropCount,
936
+ freeStakeMultiplier: config.freeStakeMultiplier,
915
937
  };
916
938
  }
917
939