diff --git a/accounts.js b/accounts.js
index 4eff736..59f65a6 100644
--- a/accounts.js
+++ b/accounts.js
@@ -49,6 +49,7 @@ const J = {
try { this.db = JSON.parse(fs.readFileSync(this.FILE(), 'utf8')); } catch (e) {}
if (!this.db || !this.db.v) this.db = { v: 2, byEmail: {}, byAddress: {}, byCode: {}, joins: 0 };
if (!this.db.byCode) this.db.byCode = {};
+ if (!this.db.positions) this.db.positions = {};
for (const a of Object.values(this.db.byEmail)) {
if (!a.code) { a.code = newCode(c => this.db.byCode[c]); this.db.byCode[a.code] = a.email; }
else if (!this.db.byCode[a.code]) this.db.byCode[a.code] = a.email;
@@ -160,6 +161,7 @@ const J = {
if (acct.address && acct.address !== a) return { error: 'This account is already linked to wallet '
+ acct.address.slice(0, 6) + '…' + acct.address.slice(-4) + '. Connect that wallet instead.' };
if (this.db.byAddress[a] && this.db.byAddress[a] !== e) return { error: 'That wallet is already linked to a different account.' };
+ if (this.db.positions[a]) return { error: 'That wallet is already a linked position' + (this.db.positions[a].email === e ? ' on this account.' : ' on a different account.') };
acct.address = a;
this.db.byAddress[a] = e;
this.save();
@@ -172,7 +174,32 @@ const J = {
acct.sponsorRef = ref; this.save();
return { ok: true, account: pub(acct) };
},
- async count() { return Object.keys(this.db.byEmail).length; }
+ async count() { return Object.keys(this.db.byEmail).length; },
+ // ---- linked positions (extra wallets on one account) ----
+ async positions(e) {
+ return Object.entries(this.db.positions).filter(([, p]) => p.email === e)
+ .map(([address, p]) => ({ address, email: p.email, memberId: p.memberId || 0, created: p.created }))
+ .sort((x, y) => x.created - y.created);
+ },
+ async addPosition(e, a) {
+ const acct = this.db.byEmail[e];
+ if (!acct) return { error: 'No such account.' };
+ if (!acct.address) return { error: 'Link your main wallet first.' };
+ if (acct.address === a) return { error: 'That is your main wallet. Switch to a different account in your wallet app, then try again.' };
+ if (this.db.byAddress[a]) return { error: 'That wallet is already the main wallet of another account.' };
+ const cur = this.db.positions[a];
+ if (cur && cur.email !== e) return { error: 'That wallet is already a position on a different account.' };
+ if (!cur) { this.db.positions[a] = { email: e, memberId: 0, created: Date.now() }; this.save(); }
+ return { ok: true, address: a, created: !cur };
+ },
+ async setPositionMember(a, id) { const p = this.db.positions[a]; if (p && p.memberId !== id) { p.memberId = id; this.save(); } },
+ async positionOwner(a) { const p = this.db.positions[a]; return p ? { address: a, email: p.email, memberId: p.memberId || 0 } : null; },
+ async removePosition(e, a) {
+ const p = this.db.positions[a];
+ if (!p || p.email !== e) return { error: 'No such position.' };
+ if (p.memberId) return { error: 'That position is already registered on-chain and cannot be unlinked.' };
+ delete this.db.positions[a]; this.save(); return { ok: true };
+ },
};
// ---- MySQL mode ----
@@ -278,6 +305,8 @@ const D = {
if (!cur) return { error: 'No such account.' };
if (cur.address && cur.address !== a) return { error: 'This account is already linked to wallet '
+ cur.address.slice(0, 6) + '…' + cur.address.slice(-4) + '. Connect that wallet instead.' };
+ const pos = (await db.q('SELECT email FROM positions WHERE address=?', [a]))[0];
+ if (pos) return { error: 'That wallet is already a linked position' + (pos.email === e ? ' on this account.' : ' on a different account.') };
try { await db.q('UPDATE accounts SET address=? WHERE email=?', [a, e]); }
catch (err) {
if (err.code === 'ER_DUP_ENTRY') return { error: 'That wallet is already linked to a different account.' };
@@ -291,7 +320,34 @@ const D = {
if (!r.affectedRows) return { error: 'No such account.' };
return { ok: true, account: await this.byEmail(e) };
},
- async count() { const r = await db.q('SELECT COUNT(*) n FROM accounts'); return Number(r[0].n); }
+ async count() { const r = await db.q('SELECT COUNT(*) n FROM accounts'); return Number(r[0].n); },
+ // ---- linked positions (extra wallets on one account) ----
+ async positions(e) {
+ const rows = await db.q('SELECT * FROM positions WHERE email=? ORDER BY created', [e]);
+ return rows.map(r => ({ address: r.address, email: r.email, memberId: r.member_id || 0, created: Number(r.created) }));
+ },
+ async addPosition(e, a) {
+ const acct = await this.byEmail(e);
+ if (!acct) return { error: 'No such account.' };
+ if (!acct.address) return { error: 'Link your main wallet first.' };
+ if (acct.address === a) return { error: 'That is your main wallet. Switch to a different account in your wallet app, then try again.' };
+ if (await this.byAddress(a)) return { error: 'That wallet is already the main wallet of another account.' };
+ const cur = (await db.q('SELECT * FROM positions WHERE address=?', [a]))[0];
+ if (cur && cur.email !== e) return { error: 'That wallet is already a position on a different account.' };
+ if (!cur) await db.q('INSERT INTO positions (address,email,member_id,created) VALUES (?,?,0,?)', [a, e, Date.now()]);
+ return { ok: true, address: a, created: !cur };
+ },
+ async setPositionMember(a, id) { await db.q('UPDATE positions SET member_id=? WHERE address=? AND member_id<>?', [id, a, id]); },
+ async positionOwner(a) {
+ const r = (await db.q('SELECT * FROM positions WHERE address=?', [a]))[0];
+ return r ? { address: r.address, email: r.email, memberId: r.member_id || 0 } : null;
+ },
+ async removePosition(e, a) {
+ const r = (await db.q('SELECT * FROM positions WHERE address=? AND email=?', [a, e]))[0];
+ if (!r) return { error: 'No such position.' };
+ if (r.member_id) return { error: 'That position is already registered on-chain and cannot be unlinked.' };
+ await db.q('DELETE FROM positions WHERE address=?', [a]); return { ok: true };
+ },
};
const impl = () => db.enabled() ? D : J;
@@ -392,4 +448,9 @@ module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUs
getMutes: e => impl().getMutes(String(e || '').toLowerCase()),
setMute: (o, t, m) => impl().setMute(String(o || '').toLowerCase(), String(t || '').toLowerCase(), m),
sponsorOf, isDownlineOf, getChatSettings,
+ positions: e => impl().positions(normEmail(e)),
+ addPosition: (e, a) => { const x = normAddr(a); return /^0x[0-9a-f]{40}$/.test(x) ? impl().addPosition(normEmail(e), x) : Promise.resolve({ error: 'Bad wallet address.' }); },
+ setPositionMember: (a, id) => impl().setPositionMember(normAddr(a), Number(id) || 0),
+ positionOwner: a => impl().positionOwner(normAddr(a)),
+ removePosition: (e, a) => impl().removePosition(normEmail(e), normAddr(a)),
byMemberId: id => impl().byMemberId(id) };
diff --git a/ads.js b/ads.js
index f980759..11f784b 100644
Binary files a/ads.js and b/ads.js differ
diff --git a/chatbot.js b/chatbot.js
index 0900a96..682c35e 100644
--- a/chatbot.js
+++ b/chatbot.js
@@ -28,7 +28,7 @@ const CANNED = [
{ re: /(how (much|do i) earn|commission|percent|split)/i,
a: 'Every package splits the same way: 50 percent to the direct sponsor, 20 percent to level 2, 10 percent to level 3, 20 percent to the platform. Those numbers are constants in the contract and cannot be changed. Play with scenarios on the home page calculator. No income is promised; nobody earns unless real ad buying happens.' },
{ re: /(qualif|unlock level|level 2|level 3|pass.?up)/i,
- a: 'Level 1 is open to every member. Bring 2 buyers of $20 or more and level 2 unlocks; 5 unlock level 3. When a level is not qualified, its share climbs the sponsor line, checking up to 25 positions, and pays the first qualified person. Qualification never expires and cannot be bought.' },
+ a: 'Level 1 is open to every member. Bring 2 buyers of $20 or more and level 2 unlocks; 5 unlock level 3. When a level is not qualified, its share climbs the sponsor line, checking up to 25 positions, and pays the first qualified person. Qualification never expires and cannot be bought. Qualified Start: you may link extra wallets of your own as positions under your account (Buy packages > Qualified Start); each one that buys a $20+ package counts as a qualifying buyer, its credits pool with yours, and 50% of its purchase comes back to your main wallet. Your own money, your own wallets, a faster start, never an income promise.' },
{ re: /(need (a )?wallet|crypto experience|metamask|how (do i|to) join|sign ?up|register)/i,
a: 'Join free with just your email at https://instantadpay.com/my, no wallet and no password needed. Your wallet only comes out when you buy a package or switch on payouts, and the site walks you through it.' },
{ re: /(referral link|invite link|share link|refer)/i,
diff --git a/db.js b/db.js
index 521f7db..7770e74 100644
--- a/db.js
+++ b/db.js
@@ -120,6 +120,15 @@ async function bootstrap() {
await alterSafe('ALTER TABLE campaigns ADD COLUMN expires BIGINT NULL'); // featured rotation end time
await alterSafe('ALTER TABLE campaigns ADD COLUMN starts BIGINT NULL'); // featured run start (booked day)
await alterSafe('ALTER TABLE campaigns ADD COLUMN house TINYINT NOT NULL DEFAULT 0'); // admin house ad: free, never charged
+ // linked positions: extra wallets owned by one email account (Qualified Start).
+ // Each is its own on-chain member sponsored by the account's main member.
+ await q(`CREATE TABLE IF NOT EXISTS positions (
+ address VARCHAR(64) PRIMARY KEY,
+ email VARCHAR(190) NOT NULL,
+ member_id INT NOT NULL DEFAULT 0,
+ created BIGINT NOT NULL,
+ INDEX (email)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await q(`CREATE TABLE IF NOT EXISTS visit_seen (
campaign_id INT NOT NULL,
email VARCHAR(190) NOT NULL,
diff --git a/docs/TEAM-BUILDING-PLAYS.md b/docs/TEAM-BUILDING-PLAYS.md
new file mode 100644
index 0000000..c30e974
--- /dev/null
+++ b/docs/TEAM-BUILDING-PLAYS.md
@@ -0,0 +1,80 @@
+# InstantAdPay team-building plays
+
+Working doc, 2026-09-09. Three ways to build a line, written so a member can pick one and run it. Every number below comes from the live contract and rate table (50 / 20 / 10 / 20 split; qualifying buyer = a direct who buys a $20+ package; level 2 opens at 2 qualifying, level 3 at 5; milestone credits 10 / 25 / 50 / 100; featured strip 40 credits a day; one broadcast a day; wall position 2 at 2 qualifying, position 3 at 5).
+
+## The one rule under all three plays
+
+Unqualified levels pass up. If someone on your level 2 buys before you have 2 qualifying buyers, that 20% does not wait for you. It goes to the next qualified sponsor above you (or the platform). Same for level 3 and 5. So whatever play you run, the first job is the same: get qualified before your line gets busy. The dashboard "Your next move" card is the ladder; the plays are how you climb it.
+
+## Play 1: Wide and teach ("the fifty play")
+
+Who it fits: someone with an audience, a list, a group, or traffic they can point somewhere. Time-rich or reach-rich.
+
+Why it works: every direct who buys is 50% to you, instantly, forever. Directs are the only thing that qualifies you. And the teaching is what fills levels 2 and 3 without you doing anything extra: your directs' buyers are your 20%, their buyers are your 10%.
+
+The move:
+1. One new conversation a day, minimum. Use the Text a friend and Social posts in Promo tools; every piece already carries your link.
+2. Point paid traffic at an angle lander, not the bare link: `?v=adspend` for advertisers, `?v=free` for freebie seekers, `?v=instant` for the crypto-curious.
+3. Every new direct gets the same three sentences from you inside 24 hours (sponsor chat or one broadcast): "Pick your username. Link your wallet and switch on payouts. Send your link to one person today." That is the whole teaching. They teach it to their people.
+4. Run the network's own ads at your link: buy a package, or claim the daily 5 credits, and spend the credits on a Featured link (40 credits a day) or a text ad pointed at your angle lander. Recruits from inside the network already understand the product.
+
+Scoreboard: Joined your line (should climb daily), Qualifying buyers (2 then 5), then watch level 2 and 3 rows appear in My line.
+
+Ceiling: none on width. Weakness: shallow lines churn if you skip step 3.
+
+## Play 2: Two, then down ("the depth play")
+
+Who it fits: someone with a small circle who would rather coach two people well than pitch twenty.
+
+Why it works: two qualifying buyers open level 2 (20%), position 2 on your wall, the Circuit badge, and 50 bonus credits. From there, every person your two bring in pays you 20%, and every person those people bring in pays you 10% (once you reach 5). Your effort goes into two relationships instead of a funnel.
+
+The move:
+1. Get two directs to a $20+ package. Sit with them on the buy if you have to (Trust Wallet needs a POL cushion; SafePal or MetaMask are smoother).
+2. Coach them to their two. Sponsor chat daily for the first week. One broadcast a day to your directs with a single ask each time.
+3. Set your line banner to your team's meeting place (Telegram group, a training page). Every new member three levels down meets it on their welcome tour. That is how your coaching reaches people you never directly recruited.
+4. Keep adding directs until you have five. This is the catch in the depth play: level 3 only opens on five qualifying directs of your own. Two deep, coached well, gets you a healthy 20% level. It does not get you the 10% level.
+
+Scoreboard: Qualifying buyers 2, then level 2 count in My line rising, then your directs' own Qualifying buyers (ask them, or read their wall page).
+
+Ceiling: level 2 income until you personally hit five. Strength: the stickiest lines come from this play.
+
+## Play 3: Five and wide ("the combination", recommended default)
+
+Who it fits: anyone willing to do both. This is the play the dashboard ladder is actually built for.
+
+The move, in order:
+1. Sprint to five qualifying directs. Nothing else matters until level 3 is open: that is Nexus, wall position 3 (your whole public page runs your own links), 100 bonus credits, and the full 50/20/10.
+2. Then split the day. Mornings wide: one new conversation, one post, one ad running. Evenings deep: read My line, message the three newest directs, send the broadcast.
+3. Coach the 2-then-5 rule down the line. Each of your five gets pushed to two (your level 2 fills), then to five (your level 3 fills). Use the achievements Share links; people copy what they see rewarded.
+4. Book the featured strip for 7 days whenever you have 280 credits spare. Ten slots a day, every member sees it.
+
+Scoreboard: all four tiles, plus Earning levels on the Overview (buyers referred, level open, how many to next).
+
+## Which play fits you
+
+| You have | Run |
+|---|---|
+| A list, a group, or ad budget | Wide and teach |
+| A few close people and patience | Two, then down |
+| An hour a day and a phone | Five and wide |
+
+## First 30 days (any play)
+
+- Day 1: username, wallet linked, payouts on, welcome tour done (25 credits). Send the link to one person.
+- Days 2 to 7: one conversation a day. Claim the daily 5 credits. First buyer (25 bonus credits).
+- Days 8 to 14: second qualifying buyer. Level 2 open. Set your line banner. First broadcast.
+- Days 15 to 30: coach the two to their two. Add directs three, four, five. Level 3 open by day 30 is the stretch goal.
+
+## Messages that fit the plays
+
+- Wide: "I run ads anyway. This one pays me in the same transaction the buyer's package sells, on a public ledger. Free to join by email: {{link}}"
+- Depth: "I need two people who will actually do this with me, not twenty who will look at it. You are one of the two I thought of. {{link}}"
+- Combo, to a new direct: "Three things today: username, wallet on, one person. I will check in tomorrow."
+
+No income is guaranteed. Results depend on your effort. Crypto involves risk of loss. InstantAdPay sells advertising; it is not an investment.
+
+## Where this should live on the site (proposal, not built)
+
+- Promo tools: a "Plays" pill with the three plays and the fit table.
+- Sponsor coaching panel (pending build): the coach sees which play each direct is on, from their buyer count and line shape.
+- Training: a short video per play once the plays are approved.
diff --git a/public/assets/my.js b/public/assets/my.js
index ae50681..ec5c9ae 100644
--- a/public/assets/my.js
+++ b/public/assets/my.js
@@ -500,6 +500,7 @@
$('posLine').innerHTML = who.join('
');
$('creditLine').textContent = (me.credits || 0).toLocaleString();
+ loadPositions(me);
$('linkCard').hidden = !!me.address;
$('activateCard').hidden = !(me.address && !me.memberId);
$('activityArea').hidden = !me.memberId;
@@ -956,7 +957,7 @@
}
el.innerHTML = r.levels.map(L => !L.members.length ? '' :
'
Pooled credits: ' + (r.totalCredits || 0).toLocaleString() + '. A campaign budget spends from one position at a time.
' : ''); + if ($('qsList')) $('qsList').innerHTML = html; + if ($('posList')) $('posList').innerHTML = html; + if ($('posCard')) $('posCard').hidden = !list.length; + // "Buy from" picker: main + every position + const sel = $('buyFrom'); + if (sel) { + const keep = sel.value; + sel.innerHTML = '' + + list.map((p, i) => '').join(''); + if (keep && [...sel.options].some(o => o.value === keep)) sel.value = keep; + $('buyFromWrap').hidden = !list.length; + } + document.querySelectorAll('[data-unlink]').forEach(b => b.addEventListener('click', async () => { + if (!confirm('Unlink ' + short(b.dataset.unlink) + ' from your account?')) return; + try { await api('/api/my/positions/remove', { address: b.dataset.unlink }); IAP.status('Position unlinked.', 'ok'); loadPositions(); } + catch (e) { IAP.status(e.message, 'bad'); } + })); + } catch (e) {} + } + if ($('qsAddBtn')) $('qsAddBtn').addEventListener('click', busy2($('qsAddBtn'), async () => { + const me = await (await fetch('/api/me')).json(); + if (!me.address) throw new Error('Link your main wallet first (Wallet tab), then add positions under it.'); + if (!me.memberId) throw new Error('Switch on payouts for your main wallet first (Wallet tab). Positions register under your member number.'); + if (!confirm('In your wallet app, switch to a DIFFERENT account than ' + short(me.address) + ' first (MetaMask: account menu, Add account. Trust or SafePal: switch wallet). The wallet picker will open again so you can connect that account.\n\nReady?')) return; + await IAPWallet.disconnect(); + IAP.status('Connect the new account in the picker, then sign once…'); + const r = await IAPWallet.signIn({ asPosition: true }); + $('qsHint').textContent = 'Added ' + short(r.address) + '. Now choose it under "Buy from" and buy a $20 or larger package.'; + IAP.status('Position added: ' + short(r.address) + '. Pick it under "Buy from" above and buy a $20+ package to count it.', 'ok'); + await loadPositions(); + const sel = $('buyFrom'); if (sel) sel.value = r.address.toLowerCase(); + try { $('buyFrom').scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (e) {} + })); async function loadBuyTiles() { try { const { products } = await (await fetch('/api/catalog')).json(); @@ -1233,17 +1285,33 @@ // fetches when the page returns from the wallet app-switch const jretry = async url => { let err; for (let i = 0; i < 4; i++) { try { return await (await fetch(url)).json(); } catch (e) { err = e; await new Promise(s => setTimeout(s, 500 * (i + 1))); } } throw err; }; const meNow = await jretry('/api/me'); - if (!meNow.address) { + // which of the member's wallets is buying: the main wallet (default) or a + // linked position (Qualified Start). A position registers under the main + // member id on its first buy, so its sponsor is always this member. + const fromSel = $('buyFrom'); + const fromPos = (fromSel && !$('buyFromWrap').hidden && fromSel.value && fromSel.value !== 'main') ? fromSel.value.toLowerCase() : null; + if (fromPos && !meNow.memberId) { IAP.status('Switch on payouts for your main wallet first (Wallet tab), so this position can register under you.', 'bad'); return; } + if (!fromPos && !meNow.address) { IAP.status('Link your wallet first — one quick signature…'); await IAPWallet.signIn(); } - const spNow = await jretry('/api/sponsor'); + const wantAddr = fromPos || (meNow.address ? meNow.address.toLowerCase() : null); + if (wantAddr) { // never buy from a wallet other than the one selected: a stray wallet would register a brand-new member + let cur = (IAPWallet.address() || await IAPWallet.connect() || '').toLowerCase(); + if (cur !== wantAddr) { + IAP.status('Switch your wallet app to ' + wantAddr.slice(0, 6) + '…' + wantAddr.slice(-4) + ', then pick it in the picker…'); + await IAPWallet.disconnect(); + cur = String(await IAPWallet.connect() || '').toLowerCase(); + } + if (cur !== wantAddr) throw new Error('Your wallet connected as ' + cur.slice(0, 6) + '…' + cur.slice(-4) + ' but you chose ' + wantAddr.slice(0, 6) + '…' + wantAddr.slice(-4) + '. Switch accounts in your wallet app and try again.'); + } + const spNow = fromPos ? { sponsorId: meNow.memberId } : await jretry('/api/sponsor'); // pre-flight: stop early if the POL is not there. Trust Wallet also hard-blocks any // transaction that spends most of the balance ("drain your wallet"), so Trust users // get a heads-up first; other wallets go straight to the confirmation. try { const need = BigInt(b.dataset.cost) + BigInt(b.dataset.cost) / 50n; // same 2% pad as buy() - const bal = await IAPWallet.balance(IAPWallet.address() || meNow.address); + const bal = await IAPWallet.balance(wantAddr || IAPWallet.address() || meNow.address); if (bal < need) { IAP.status('That wallet holds ' + IAP.fmtPol(bal.toString()) + ' POL, but this package needs about ' + IAP.fmtPol(need.toString()) + ' POL plus a little for gas. Top it up and try again.', 'bad'); return; @@ -1258,7 +1326,7 @@ IAP.status('Confirm the purchase in your wallet…'); const r = await IAPWallet.buy(Number(b.dataset.id), spNow.sponsorId || 0, b.dataset.cost); if (r.status !== '0x1' && r.receipt && r.receipt.status !== '0x1') throw new Error('Transaction reverted.'); - IAP.status('Purchase settled on-chain. Credits are in your account.', 'ok'); + IAP.status(fromPos ? 'Purchase settled on-chain. That position now counts toward your qualification, and its credits pool with yours.' : 'Purchase settled on-chain. Credits are in your account.', 'ok'); await render(); loadBuyTiles(); } catch (e) { IAP.status('Purchase failed: ' + ((e && e.message) || e), 'bad'); } diff --git a/public/assets/wallet.js b/public/assets/wallet.js index bca3dd7..a945e1b 100644 --- a/public/assets/wallet.js +++ b/public/assets/wallet.js @@ -148,7 +148,7 @@ window.IAPWallet = (function () { } // SIWE: challenge -> personal_sign -> verify (server sets the session cookie) - async function signIn() { + async function signIn(opts) { const addr = await connect(); const ch = await (await fetch('/api/auth/challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr }) })).json(); @@ -157,7 +157,7 @@ window.IAPWallet = (function () { const hexMsg = '0x' + Array.from(new TextEncoder().encode(ch.message)).map(b => b.toString(16).padStart(2, '0')).join(''); const sig = await eth().request({ method: 'personal_sign', params: [hexMsg, addr] }); const r = await (await fetch('/api/auth/verify', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr, signature: sig }) })).json(); + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr, signature: sig, asPosition: !!(opts && opts.asPosition) }) })).json(); if (r.error) throw new Error(r.error); return r; } diff --git a/public/my.html b/public/my.html index ac0a40f..caab69e 100644 --- a/public/my.html +++ b/public/my.html @@ -324,8 +324,27 @@ to this account instantly, and every payout in your sponsor line lands the moment you confirm.Using Trust Wallet? It blocks purchases that spend most of the POL in the wallet (a "drain your wallet" warning). Start with a smaller package, keep a little extra POL, or connect MetaMask, Phantom or SafePal instead. Extra POL always stays yours.
+Qualification is earned by buyers you refer, and it is never for sale. But nothing stops you from being your own first buyers, openly. Link a second wallet of your own as a position under your account. When it buys a $20 or larger package, the contract counts it as a qualifying buyer, half the purchase comes straight back to your main wallet, and the credits it mints pool with yours. Two positions open level 2 the same day. Five open level 3.
++
+ +Your own money, your own wallets, a faster start. It is not an income promise: qualification only pays on future purchases in your line, and every purchase here is a real ad package you can spend.
+
Extra wallets you own, registered under your main member. Their credits pool with yours and each one that buys $20 or more counts toward your qualification.
+ + +…