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 +1 -1
- package/src/bridge.ts +27 -9
- package/src/idls/nara_quest.json +30 -5
- package/src/idls/nara_quest.ts +30 -5
- package/src/quest.ts +31 -9
package/package.json
CHANGED
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|
package/src/idls/nara_quest.json
CHANGED
|
@@ -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
|
-
|
|
2027
|
+
16
|
|
2015
2028
|
]
|
|
2016
2029
|
}
|
|
2017
2030
|
}
|
|
@@ -2049,15 +2062,15 @@
|
|
|
2049
2062
|
"type": "u64"
|
|
2050
2063
|
},
|
|
2051
2064
|
{
|
|
2052
|
-
"name": "
|
|
2065
|
+
"name": "stake_reward_count",
|
|
2053
2066
|
"type": "u32"
|
|
2054
2067
|
},
|
|
2055
2068
|
{
|
|
2056
|
-
"name": "
|
|
2069
|
+
"name": "stake_reward_per_winner",
|
|
2057
2070
|
"type": "u64"
|
|
2058
2071
|
},
|
|
2059
2072
|
{
|
|
2060
|
-
"name": "
|
|
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
|
-
|
|
2113
|
+
48
|
|
2089
2114
|
]
|
|
2090
2115
|
}
|
|
2091
2116
|
}
|
package/src/idls/nara_quest.ts
CHANGED
|
@@ -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
|
-
|
|
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": "
|
|
2071
|
+
"name": "stakeRewardCount",
|
|
2059
2072
|
"type": "u32"
|
|
2060
2073
|
},
|
|
2061
2074
|
{
|
|
2062
|
-
"name": "
|
|
2075
|
+
"name": "stakeRewardPerWinner",
|
|
2063
2076
|
"type": "u64"
|
|
2064
2077
|
},
|
|
2065
2078
|
{
|
|
2066
|
-
"name": "
|
|
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
|
-
|
|
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
|
-
|
|
48
|
+
/** Total reward pool for the round (stake + free), in NARA */
|
|
49
49
|
totalReward: number;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|